diff --git a/DEPS b/DEPS
index c41b436..f53d49a1 100644
--- a/DEPS
+++ b/DEPS
@@ -299,7 +299,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'cf6fac0c7d5f887092053869e35cccc3a8b2b567',
+  'v8_revision': '9dcc25911c1ed7e2c5bbf5a1741e8e9b46b58da9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -311,7 +311,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'd0f42d7bfb7dc2bd44b5a988647a5fa056b5376d',
+  'pdfium_revision': 'f52c2a7cb8b00d37cac0636085cf6c918a407503',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -366,7 +366,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': '8a3feaaaab63b383fb6b138be2986e9ad6dcaca2',
+  'catapult_revision': '20cf5c69af5d31638182e2a5ab2093dea67ffbc9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -434,7 +434,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'c50cdc5671f17a2180c39d30414d3a1397592568',
+  'nearby_revision': 'f73b23e720a9c587e1d4663b77775edb30aed4ae',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -477,7 +477,7 @@
   'libcxx_revision':       'c9944183144174e86d5edef03e81a38cd13db4c9',
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:9bffaa2162842f94d9444a9b3cea00dcf137cd04',
+  'gn_version': 'git_revision:5e19d2fb166fbd4f6f32147fbb2f497091a54ad8',
 
   # ninja CIPD package version.
   # https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja
@@ -764,12 +764,12 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'c431c03effdf8eb3722766b58ef60ef31f42568e',
+    '621806c82e5aeeb0a96a44fe836e3c80384f9a7b',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'f4045351063427a57a8d0c019983483b450f50f7',
+    'url': Var('chromium_git') + '/website.git' + '@' + '1922effc3b5149dec4cd14c87385055eb75f646f',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -953,7 +953,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'CNCDyM7GRT4mcYOGp9Ioochm9rMDUoeqoi8NKqoVFHIC',
+          'version': 'pQKsjiiz1nkGWtGNFWM1RU3zQe8J3zyCH6rt8JN1OMMC',
       },
     ],
     'condition': 'checkout_android',
@@ -1198,7 +1198,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd291058e4780c0f330dcc906cff3c9472aa7c1cf',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e1c8efebe0a3cce42ca46d6057b6d4bd909ad203',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1419,7 +1419,7 @@
     Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'df35d6c42da4fa2759e4cfb592afe33817993b89',
 
   'src/third_party/libaom/source/libaom':
-    Var('aomedia_git') + '/aom.git' + '@' +  'c0239a23c24796ddc003f2a3199a9014a1930a80',
+    Var('aomedia_git') + '/aom.git' + '@' +  '55e7b1c59920923eb46060870096a1e411f3cb98',
 
   'src/third_party/libavif/src':
     Var('chromium_git') + '/external/github.com/AOMediaCodec/libavif.git' + '@' + Var('libavif_revision'),
@@ -1616,7 +1616,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + 'db956674bbdfbaab5acdd3fdb4117c2fef5527e9',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '898f4f0553dcbefc0146688b4e51a5066d7ff299',
+    Var('chromium_git') + '/openscreen' + '@' + '9942fb6d07c9c72b9dcee9e777c544e256f1fb61',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1765,7 +1765,7 @@
     Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + 'c036420683f672d685e27415de0a5f5e85bdc23f',
 
   'src/third_party/tflite/src':
-    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '0ac64752b31fd6f9ccb0209a48453c6b110f75e1',
+    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + 'be3be938f3c349daf0b0ef4b717fc2fc7010a974',
 
   'src/third_party/turbine': {
       'packages': [
@@ -1888,7 +1888,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e854458f7e872fdb6ca34e588856bca3264d3307',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@acfddfeb9d6828fdcd5ab3ff32a5427df6ebdb37',
     'condition': 'checkout_src_internal',
   },
 
@@ -1907,7 +1907,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'JA9dGKbE1cW72yIirAVthc43vzG7Gtochihlyqsc5VsC',
+        'version': 'ZGDsqIK_92a4UNC4ptPI4QAm9fh5sI8oXlK1gr40StkC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_content_browser_client_receiver_bindings.cc b/android_webview/browser/aw_content_browser_client_receiver_bindings.cc
index 73129a69..b30b9c3 100644
--- a/android_webview/browser/aw_content_browser_client_receiver_bindings.cc
+++ b/android_webview/browser/aw_content_browser_client_receiver_bindings.cc
@@ -19,6 +19,7 @@
 #include "content/public/browser/browser_associated_interface.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/resource_context.h"
 #include "media/mojo/buildflags.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
diff --git a/android_webview/browser/gfx/viz_compositor_thread_runner_webview.cc b/android_webview/browser/gfx/viz_compositor_thread_runner_webview.cc
index 8af6ba4..044f3d9 100644
--- a/android_webview/browser/gfx/viz_compositor_thread_runner_webview.cc
+++ b/android_webview/browser/gfx/viz_compositor_thread_runner_webview.cc
@@ -16,6 +16,7 @@
 #include "components/viz/common/features.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "components/viz/service/gl/gpu_service_impl.h"
 
 namespace android_webview {
 
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc
index 3d7aedeb..3211f591 100644
--- a/apps/saved_files_service.cc
+++ b/apps/saved_files_service.cc
@@ -63,17 +63,15 @@
   ExtensionPrefs::ScopedDictionaryUpdate update(
       prefs, extension_id, kFileEntries);
   auto file_entries = update.Create();
-  DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL));
+  DCHECK(
+      !file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, nullptr));
 
-  std::unique_ptr<base::DictionaryValue> file_entry_dict =
-      std::make_unique<base::DictionaryValue>();
-  file_entry_dict->SetKey(kFileEntryPath,
-                          base::FilePathToValue(file_entry.path));
-  file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory);
-  file_entry_dict->SetInteger(kFileEntrySequenceNumber,
-                              file_entry.sequence_number);
-  file_entries->SetWithoutPathExpansion(file_entry.id,
-                                        std::move(file_entry_dict));
+  base::Value::Dict file_entry_dict;
+  file_entry_dict.Set(kFileEntryPath, base::FilePathToValue(file_entry.path));
+  file_entry_dict.Set(kFileEntryIsDirectory, file_entry.is_directory);
+  file_entry_dict.Set(kFileEntrySequenceNumber, file_entry.sequence_number);
+  file_entries->SetDictionaryWithoutPathExpansion(file_entry.id,
+                                                  std::move(file_entry_dict));
 }
 
 // Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs.
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 6ca0fd7a8..7414121 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -336,8 +336,12 @@
     "capture_mode/key_combo_view.h",
     "capture_mode/key_item_view.cc",
     "capture_mode/key_item_view.h",
+    "capture_mode/pointer_highlight_layer.cc",
+    "capture_mode/pointer_highlight_layer.h",
     "capture_mode/recording_overlay_controller.cc",
     "capture_mode/recording_overlay_controller.h",
+    "capture_mode/recording_type_menu_view.cc",
+    "capture_mode/recording_type_menu_view.h",
     "capture_mode/stop_recording_button_tray.cc",
     "capture_mode/stop_recording_button_tray.h",
     "capture_mode/user_nudge_controller.cc",
@@ -2774,6 +2778,7 @@
     "capture_mode/capture_mode_test_util.cc",
     "capture_mode/capture_mode_test_util.h",
     "capture_mode/capture_mode_unittests.cc",
+    "capture_mode/gif_recording_unittests.cc",
     "child_accounts/parent_access_controller_impl_unittest.cc",
     "clipboard/clipboard_history_controller_unittest.cc",
     "clipboard/clipboard_history_resource_manager_unittest.cc",
diff --git a/ash/accelerators/accelerator_commands.cc b/ash/accelerators/accelerator_commands.cc
index 41ad94c..58f45c8 100644
--- a/ash/accelerators/accelerator_commands.cc
+++ b/ash/accelerators/accelerator_commands.cc
@@ -1438,15 +1438,14 @@
   if (audio_handler->IsOutputMuted()) {
     audio_handler->SetOutputVolumePercent(0);
   } else {
-    if (features::IsAudioPeripheralVolumeGranularityEnabled())
-      audio_handler->DecreaseOutputVolumeByOneStep(kStepPercentage);
-    else
-      audio_handler->AdjustOutputVolumeByPercent(-kStepPercentage);
-
     if (audio_handler->IsOutputVolumeBelowDefaultMuteLevel())
       audio_handler->SetOutputMute(true);
     else
       AcceleratorController::PlayVolumeAdjustmentSound();
+    if (features::IsAudioPeripheralVolumeGranularityEnabled())
+      audio_handler->DecreaseOutputVolumeByOneStep(kStepPercentage);
+    else
+      audio_handler->AdjustOutputVolumeByPercent(-kStepPercentage);
   }
 }
 
diff --git a/ash/accelerators/accelerator_commands_unittest.cc b/ash/accelerators/accelerator_commands_unittest.cc
index b4f63fa..6a879a2 100644
--- a/ash/accelerators/accelerator_commands_unittest.cc
+++ b/ash/accelerators/accelerator_commands_unittest.cc
@@ -9,6 +9,9 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "chromeos/ash/components/audio/cras_audio_handler.h"
+#include "chromeos/ash/components/dbus/audio/audio_node.h"
+#include "chromeos/ash/components/dbus/audio/fake_cras_audio_client.h"
 #include "ui/aura/window.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
@@ -138,5 +141,46 @@
   EXPECT_EQ(id_list[2], primary_id);
 }
 
+class AcceleratorCommandsAudioTest : public AcceleratorCommandsTest {
+ public:
+  void SetUpAudioNode() {
+    auto* client = FakeCrasAudioClient::Get();
+    client->SetAudioNodesForTesting({NewAudioNode(false, "INTERNAL_SPEAKER")});
+  }
+
+ protected:
+  AudioNode NewAudioNode(bool is_input, const std::string& type) {
+    ++node_count_;
+    const std::string name =
+        base::StringPrintf("%s-%" PRIu64, type.c_str(), node_count_);
+    return AudioNode(is_input, node_count_, true, node_count_, node_count_,
+                     name, type, name, false, 0, 2, 0, 0);
+  }
+
+  unsigned long node_count_ = 0;
+};
+
+TEST_F(AcceleratorCommandsAudioTest, VolumeSetToZeroAndThenMute) {
+  SetUpAudioNode();
+  auto* audio_handler = CrasAudioHandler::Get();
+  // Make sure that volume is 0 and enter mute state.
+  audio_handler->SetOutputVolumePercent(0);
+  audio_handler->SetOutputMute(true);
+  EXPECT_TRUE(audio_handler->IsOutputMuted());
+  EXPECT_EQ(audio_handler->GetOutputVolumePercent(), 0);
+  // Unmute and increase volume one step.
+  PressAndReleaseKey(ui::VKEY_VOLUME_UP, ui::EF_NONE);
+  EXPECT_FALSE(audio_handler->IsOutputMuted());
+  EXPECT_GE(audio_handler->GetOutputVolumePercent(), 0);
+  // Volume down, should decrease to zero and no mute.
+  PressAndReleaseKey(ui::VKEY_VOLUME_DOWN, ui::EF_NONE);
+  EXPECT_EQ(audio_handler->GetOutputVolumePercent(), 0);
+  EXPECT_FALSE(audio_handler->IsOutputMuted());
+  // Volume down again, should decrease to zero and mute.
+  PressAndReleaseKey(ui::VKEY_VOLUME_DOWN, ui::EF_NONE);
+  EXPECT_EQ(audio_handler->GetOutputVolumePercent(), 0);
+  EXPECT_TRUE(audio_handler->IsOutputMuted());
+}
+
 }  // namespace accelerators
 }  // namespace ash
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 12c0448..c10a0b0a 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -310,8 +310,10 @@
       return kNotificationAccessibilityBrailleIcon;
     case A11yNotificationType::kSwitchAccessEnabled:
       return kSwitchAccessIcon;
-    case A11yNotificationType::kSpeechRecognitionFilesDownloaded:
-    case A11yNotificationType::kSpeechRecognitionFilesFailed:
+    case A11yNotificationType::kDictationAllDlcsDownloaded:
+    case A11yNotificationType::kDictationNoDlcsDownloaded:
+    case A11yNotificationType::kDicationOnlyPumpkinDownloaded:
+    case A11yNotificationType::kDictationOnlySodaDownloaded:
       return kDictationMenuIcon;
     default:
       return kNotificationChromevoxIcon;
@@ -341,33 +343,59 @@
     text = l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_BRAILLE_DISPLAY_CONNECTED);
     catalog_name = NotificationCatalogName::kBrailleDisplayConnected;
+  } else if (type == A11yNotificationType::kDictationAllDlcsDownloaded) {
+    display_source =
+        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
+    title = l10n_util::GetStringFUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE,
+        replacements, nullptr);
+    text = l10n_util::GetStringUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC);
+    catalog_name = NotificationCatalogName::kDictationAllDlcsDownloaded;
+    pinned = false;
+  } else if (type == A11yNotificationType::kDictationNoDlcsDownloaded) {
+    display_source =
+        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
+    title = l10n_util::GetStringFUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE,
+        replacements, nullptr);
+    text = l10n_util::GetStringUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC);
+    catalog_name = NotificationCatalogName::kDictationNoDlcsDownloaded;
+    pinned = false;
+    // Use CRITICAL_WARNING to force the notification color to red.
+    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
+  } else if (type == A11yNotificationType::kDicationOnlyPumpkinDownloaded) {
+    display_source =
+        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
+
+    title = l10n_util::GetStringFUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE,
+        replacements, nullptr);
+    text = l10n_util::GetStringUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC);
+
+    catalog_name = NotificationCatalogName::kDicationOnlyPumpkinDownloaded;
+    pinned = false;
+    // Use CRITICAL_WARNING to force the notification color to red.
+    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
+  } else if (type == A11yNotificationType::kDictationOnlySodaDownloaded) {
+    display_source =
+        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
+    title = l10n_util::GetStringFUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE,
+        replacements, nullptr);
+    text = l10n_util::GetStringUTF16(
+        IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC);
+    catalog_name = NotificationCatalogName::kDictationOnlySodaDownloaded;
+    pinned = false;
+    // Use CRITICAL_WARNING to force the notification color to red.
+    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
   } else if (type == A11yNotificationType::kSwitchAccessEnabled) {
     title = l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_SWITCH_ACCESS_ENABLED_TITLE);
     text = l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SWITCH_ACCESS_ENABLED);
     catalog_name = NotificationCatalogName::kSwitchAccessEnabled;
-  } else if (type == A11yNotificationType::kSpeechRecognitionFilesDownloaded) {
-    display_source =
-        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
-    title = l10n_util::GetStringFUTF16(
-        IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_TITLE,
-        replacements, nullptr);
-    text = l10n_util::GetStringUTF16(
-        IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_DESC);
-    pinned = false;
-    catalog_name = NotificationCatalogName::kSpeechRecognitionFilesDownloaded;
-  } else if (type == A11yNotificationType::kSpeechRecognitionFilesFailed) {
-    display_source =
-        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION);
-    title = l10n_util::GetStringFUTF16(
-        IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_TITLE,
-        replacements, nullptr);
-    text = l10n_util::GetStringUTF16(
-        IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_DESC);
-    // Use CRITICAL_WARNING to force the notification color to red.
-    warning = message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
-    pinned = false;
-    catalog_name = NotificationCatalogName::kSpeechRecognitionFilesFailed;
   } else {
     bool is_tablet = Shell::Get()->tablet_mode_controller()->InTabletMode();
 
@@ -382,6 +410,7 @@
                        ? NotificationCatalogName::kSpokenFeedbackBrailleEnabled
                        : NotificationCatalogName::kSpokenFeedbackEnabled;
   }
+
   message_center::RichNotificationData options;
   options.should_make_spoken_feedback_for_popup_updates = false;
   std::unique_ptr<message_center::Notification> notification =
@@ -2412,15 +2441,27 @@
       ->UpdateOnSpeechRecognitionDownloadChanged(download_progress);
 }
 
-void AccessibilityControllerImpl::
-    ShowSpeechRecognitionDownloadNotificationForDictation(
-        bool succeeded,
-        const std::u16string& display_language) {
-  A11yNotificationType type =
-      succeeded ? A11yNotificationType::kSpeechRecognitionFilesDownloaded
-                : A11yNotificationType::kSpeechRecognitionFilesFailed;
+void AccessibilityControllerImpl::ShowNotificationForDictation(
+    DictationNotificationType type,
+    const std::u16string& display_language) {
+  A11yNotificationType notification_type;
+  switch (type) {
+    case DictationNotificationType::kAllDlcsDownloaded:
+      notification_type = A11yNotificationType::kDictationAllDlcsDownloaded;
+      break;
+    case DictationNotificationType::kNoDlcsDownloaded:
+      notification_type = A11yNotificationType::kDictationNoDlcsDownloaded;
+      break;
+    case DictationNotificationType::kOnlySodaDownloaded:
+      notification_type = A11yNotificationType::kDictationOnlySodaDownloaded;
+      break;
+    case DictationNotificationType::kOnlyPumpkinDownloaded:
+      notification_type = A11yNotificationType::kDicationOnlyPumpkinDownloaded;
+      break;
+  }
+
   ShowAccessibilityNotification(A11yNotificationWrapper(
-      type, std::vector<std::u16string>{display_language}));
+      notification_type, std::vector<std::u16string>{display_language}));
 }
 
 AccessibilityControllerImpl::A11yNotificationWrapper::
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 1a1faf2..aebdb25 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -67,16 +67,20 @@
   kSpokenFeedbackEnabled,
   // Shown when braille display is connected while spoken feedback is enabled.
   kBrailleDisplayConnected,
+  // Shown when all Dictation-related DLCs have downloaded successfully.
+  kDictationAllDlcsDownloaded,
+  // Shown when all Dictation-related DLCs failed to download.
+  kDictationNoDlcsDownloaded,
+  // Shown when the Pumpkin DLC (but no other DLCs) have downloaded.
+  kDicationOnlyPumpkinDownloaded,
+  // Shown when the SODA DLC (but no other DLCs) have downloaded.
+  kDictationOnlySodaDownloaded,
   // Shown when braille display is connected while spoken feedback is not
   // enabled yet. Note: in this case braille display connected would enable
   // spoken feedback.
   kSpokenFeedbackBrailleEnabled,
   // Shown when Switch Access is enabled.
   kSwitchAccessEnabled,
-  // Shown when speech recognition files download successfully.
-  kSpeechRecognitionFilesDownloaded,
-  // Shown when speech recognition files download fails.
-  kSpeechRecognitionFilesFailed,
 };
 
 // The controller for accessibility features in ash. Features can be enabled
@@ -423,8 +427,8 @@
   void EnableChromeVoxVolumeSlideGesture() override;
   void UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
       int download_progress) override;
-  void ShowSpeechRecognitionDownloadNotificationForDictation(
-      bool succeeded,
+  void ShowNotificationForDictation(
+      DictationNotificationType type,
       const std::u16string& display_language) override;
   void UpdateDictationBubble(
       bool visible,
diff --git a/ash/accessibility/accessibility_controller_unittest.cc b/ash/accessibility/accessibility_controller_unittest.cc
index 2f6ca75..a7e14db 100644
--- a/ash/accessibility/accessibility_controller_unittest.cc
+++ b/ash/accessibility/accessibility_controller_unittest.cc
@@ -1141,21 +1141,23 @@
 
 TEST_F(AccessibilityControllerTest,
        SpeechRecognitionDownloadSucceededNotification) {
-  const std::u16string kSucceededTitle = u"English speech files downloaded";
+  const std::u16string kSucceededTitle =
+      u"English speech files partially downloaded";
   const std::u16string kSucceededDescription =
-      u"Speech is now processed locally and Dictation works offline";
+      u"Speech is processed locally and dictation works offline, but some "
+      u"voice commands won’t work.";
   AccessibilityControllerImpl* controller =
       Shell::Get()->accessibility_controller();
 
-  controller->ShowSpeechRecognitionDownloadNotificationForDictation(true,
-                                                                    u"English");
+  controller->ShowNotificationForDictation(
+      DictationNotificationType::kOnlySodaDownloaded, u"English");
   message_center::NotificationList::Notifications notifications =
       MessageCenter::Get()->GetVisibleNotifications();
   ASSERT_EQ(1u, notifications.size());
   EXPECT_EQ(kSucceededTitle, (*notifications.begin())->title());
   EXPECT_EQ(kSucceededDescription, (*notifications.begin())->message());
   EXPECT_EQ(u"Dictation", (*notifications.begin())->display_source());
-  EXPECT_EQ(message_center::SystemNotificationWarningLevel::NORMAL,
+  EXPECT_EQ(message_center::SystemNotificationWarningLevel::CRITICAL_WARNING,
             (*notifications.begin())->system_notification_warning_level());
 }
 
@@ -1164,12 +1166,12 @@
   const std::u16string kFailedTitle = u"Couldn't download English speech files";
   const std::u16string kFailedDescription =
       u"Download will be attempted later. Speech will be sent to Google for "
-      u"processing until download is completed.";
+      u"processing for now.";
   AccessibilityControllerImpl* controller =
       Shell::Get()->accessibility_controller();
 
-  controller->ShowSpeechRecognitionDownloadNotificationForDictation(false,
-                                                                    u"English");
+  controller->ShowNotificationForDictation(
+      DictationNotificationType::kNoDlcsDownloaded, u"English");
   message_center::NotificationList::Notifications notifications =
       MessageCenter::Get()->GetVisibleNotifications();
   ASSERT_EQ(1u, notifications.size());
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 452a77ba..78905fb46d 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -377,6 +377,11 @@
        /*should_record_metrics=*/true);
 }
 
+AppListShowSource AppListControllerImpl::LastAppListShowSource() {
+  DCHECK(last_open_source_.has_value());
+  return last_open_source_.value();
+}
+
 aura::Window* AppListControllerImpl::GetWindow() {
   if (IsTabletMode())
     return fullscreen_presenter_->GetWindow();
@@ -538,8 +543,6 @@
   if (IsKioskSession())
     return SHELF_ACTION_APP_LIST_DISMISSED;
 
-  last_open_source_ = show_source;
-
   if (IsTabletMode()) {
     bool handled = GoHome(display_id);
 
@@ -549,12 +552,15 @@
       return SHELF_ACTION_APP_LIST_BACK;
     }
     LogAppListShowSource(show_source, /*app_list_bubble=*/false);
+    last_open_source_ = show_source;
     return SHELF_ACTION_APP_LIST_SHOWN;
   }
 
   ShelfAction action = bubble_presenter_->Toggle(display_id);
-  if (action == SHELF_ACTION_APP_LIST_SHOWN)
+  if (action == SHELF_ACTION_APP_LIST_SHOWN) {
     LogAppListShowSource(show_source, /*app_list_bubble=*/true);
+    last_open_source_ = show_source;
+  }
   return action;
 }
 
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 7389a1c..eaeaa137 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -105,6 +105,7 @@
   void DismissAppList() override;
   void GetAppInfoDialogBounds(GetAppInfoDialogBoundsCallback callback) override;
   void ShowAppList(AppListShowSource source) override;
+  AppListShowSource LastAppListShowSource() override;
   aura::Window* GetWindow() override;
   bool IsVisible(const absl::optional<int64_t>& display_id) override;
   bool IsVisible() override;
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 8d0783a..9f5db0a 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -555,6 +555,9 @@
       <message name="IDS_ASH_STATUS_TRAY_CAST_STOP" desc="The label used in the tray popup to stop casting.">
         Stop
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING" desc="Label for a button in the system tray popup to stop casting to a Chromecast device.">
+        Stop casting
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_QUIET_MODE_TOOLTIP" desc="The tooltip text for the status area icon to tell do-not-disturb mode is currently on.">
         Do Not Disturb is on
       </message>
@@ -4893,8 +4896,11 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_IMAGE_CAPTURE" desc="The capture label message which shows in the middle of the captured region in region image capture mode.">
         Capture
       </message>
-      <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD" desc="The capture label message which shows in the middel of the captured region in region video record mode or shows in the middle of the screen in fullscreen video record mode.">
-        Record
+      <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD" desc="The capture label message which shows in the middel of the captured region in region video recording mode.">
+        Record video
+      </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_LABEL_GIF_RECORD" desc="The capture label message which shows in the middel of the captured region in region GIF recording mode.">
+        Record GIF
       </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_SAVE_TO_DIALOG_TITLE" desc="The title of the folder selection dialog, where users can select a location to save their captured images and screen recordings.">
         Select a folder to save to
@@ -5112,6 +5118,9 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_SETTINGS_A11Y_TITLE" desc="The label of window hosting the screen capture settings menu that will be read by ChromeVox when prompted for title.">
         Screen capture settings
       </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE" desc="The label of window hosting the recording type drop down menu that will be read by ChromeVox when prompted for title.">
+        Recording format menu
+      </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE" desc="The label of the menu item button for selecting the root of Google Drive to store the captured images and videos.">
         Google Drive
       </message>
@@ -5305,19 +5314,30 @@
       <message name="IDS_ASH_ACCESSIBILITY_DICTATION_BUTTON_TOOLTIP_SODA_DOWNLOADING" desc="A tooltip explaining that speech recognition files are downloading." >
         Downloading speech files
       </message>
-      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_TITLE" desc="The title for a notification that is shown when speech recognition files download successfully.">
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE" desc="The title for a notification that is shown when all Dictation assets download successfully.">
         <ph name="Language">$1<ex>English</ex></ph> speech files downloaded
       </message>
-      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_DESC" desc="The text for a notification that is shown when speech recognition files download successfully.">
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC" desc="The text for a notification that is shown when all Dictation assets download successfully.">
         Speech is now processed locally and Dictation works offline
       </message>
-      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_TITLE" desc="The title for a notification that is shown when speech recognition files fail to download.">
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE" desc="The title for a notification that is shown when all Dictation assets fail to download.">
         Couldn't download <ph name="Language">$1<ex>English</ex></ph> speech files
       </message>
-      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_DESC" desc="The text for a notification that is shown when speech recognition files fail to download.">
-        Download will be attempted later. Speech will be sent to Google for processing until download is completed.
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC" desc="The text for a notification that is shown when all Dictation assets fail to download.">
+        Download will be attempted later. Speech will be sent to Google for processing for now.
       </message>
-
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE" desc="The title for a notification that is shown when command parsing assets download successfully. This notification is only shown if command parsing assets are downloaded and speech recognition assets are not.">
+        <ph name="Language">$1<ex>English</ex></ph> speech files partially downloaded
+      </message>
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC" desc="The description for a notification that is shown when command parsing assets download successfully. This notification is only shown if command parsing assets are downloaded and speech recognition assets are not.">
+        Download will be attempted later. Speech will be sent to Google for processing for now.
+      </message>
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE" desc="The title for a notification that is shown when speech recognition files download successfully. This notification is only shown if speech recognition assets are downloaded and command parsing assets are not.">
+        <ph name="Language">$1<ex>English</ex></ph> speech files partially downloaded
+      </message>
+      <message name="IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC" desc="The description for a notification that is shown when speech recognition files download successfully. This notification is only shown if speech recognition assets are downloaded and command parsing assets are not.">
+        Speech is processed locally and dictation works offline, but some voice commands won’t work.
+      </message>
       <!-- Strings for persistent desks bar-->
       <message name="IDS_ASH_PERSISTENT_DESKS_BAR_CONTEXT_MENU_FEEDBACK" desc="Title of the menu item in the context menu to send feedback.">
         Feedback
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC.png.sha1
new file mode 100644
index 0000000..bab04cc
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_DESC.png.sha1
@@ -0,0 +1 @@
+da2649add3c3e89f6a07af97a7d9ca82809f061e
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE.png.sha1
new file mode 100644
index 0000000..bab04cc
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ALL_DLCS_DOWNLOADED_TITLE.png.sha1
@@ -0,0 +1 @@
+da2649add3c3e89f6a07af97a7d9ca82809f061e
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC.png.sha1
new file mode 100644
index 0000000..15fcc1e
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_DESC.png.sha1
@@ -0,0 +1 @@
+52ebb71e71a7ef3f7dbdc208ff66e9b90bc4d359
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE.png.sha1
new file mode 100644
index 0000000..15fcc1e
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_NO_DLCS_DOWNLOADED_TITLE.png.sha1
@@ -0,0 +1 @@
+52ebb71e71a7ef3f7dbdc208ff66e9b90bc4d359
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC.png.sha1
new file mode 100644
index 0000000..dcb427a5
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_DESC.png.sha1
@@ -0,0 +1 @@
+7e7b638b53f33749e7473f4e242f6342f090f2c9
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE.png.sha1
new file mode 100644
index 0000000..dcb427a5
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_PUMPKIN_DOWNLOADED_TITLE.png.sha1
@@ -0,0 +1 @@
+7e7b638b53f33749e7473f4e242f6342f090f2c9
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC.png.sha1
new file mode 100644
index 0000000..387bfb7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_DESC.png.sha1
@@ -0,0 +1 @@
+4c4a6c719652bd36c08b2a2d1cba45edc70b3e8f
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE.png.sha1
new file mode 100644
index 0000000..387bfb7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_ONLY_SODA_DOWNLOADED_TITLE.png.sha1
@@ -0,0 +1 @@
+4c4a6c719652bd36c08b2a2d1cba45edc70b3e8f
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_DESC.png.sha1
deleted file mode 100644
index ee84079..0000000
--- a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_DESC.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ef0046d8341f28ea6bbfb857e6fbf9be582c65fe
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_TITLE.png.sha1
deleted file mode 100644
index 869033f..0000000
--- a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_FAILED_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-3cc8b3db0fe17cce62761440b2fcc58d1596e543
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_DESC.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_DESC.png.sha1
deleted file mode 100644
index 939ed24..0000000
--- a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_DESC.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-96a5bc112508020ed4764f19a922437b1f9a0c86
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_TITLE.png.sha1
deleted file mode 100644
index 939ed24..0000000
--- a/ash/ash_strings_grd/IDS_ASH_A11Y_DICTATION_NOTIFICATION_SODA_DOWNLOAD_SUCCEEDED_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-96a5bc112508020ed4764f19a922437b1f9a0c86
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_GIF_RECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_GIF_RECORD.png.sha1
new file mode 100644
index 0000000..9cb735b
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_GIF_RECORD.png.sha1
@@ -0,0 +1 @@
+0a240badf861c44ad33c16f26826936a9a1301ac
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD.png.sha1
index b244956e..9cb735b 100644
--- a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD.png.sha1
@@ -1 +1 @@
-5cc44477bc0dde6b2231bca438cae4eebcf8bdc3
\ No newline at end of file
+0a240badf861c44ad33c16f26826936a9a1301ac
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE.png.sha1
new file mode 100644
index 0000000..9cb735b
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE.png.sha1
@@ -0,0 +1 @@
+0a240badf861c44ad33c16f26826936a9a1301ac
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING.png.sha1
new file mode 100644
index 0000000..ade7b32
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING.png.sha1
@@ -0,0 +1 @@
+2999314bee94b92915fb911d27bffa7de134572b
\ No newline at end of file
diff --git a/ash/capture_mode/capture_button_view.h b/ash/capture_mode/capture_button_view.h
index 6f5e366..7c1e391 100644
--- a/ash/capture_mode/capture_button_view.h
+++ b/ash/capture_mode/capture_button_view.h
@@ -32,6 +32,7 @@
   ~CaptureButtonView() override = default;
 
   views::LabelButton* capture_button() { return capture_button_; }
+  views::ImageButton* drop_down_button() { return drop_down_button_; }
 
   // Updates the icon and text of `capture_button_`, as well as the visibility
   // of the `separator_` and `drop_down_button_` depending on the current type
diff --git a/ash/capture_mode/capture_label_view.cc b/ash/capture_mode/capture_label_view.cc
index 604510f..00282936 100644
--- a/ash/capture_mode/capture_label_view.cc
+++ b/ash/capture_mode/capture_label_view.cc
@@ -26,6 +26,7 @@
 #include "ui/gfx/geometry/transform.h"
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
+#include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/focus_ring.h"
 #include "ui/views/controls/highlight_path_generator.h"
@@ -144,7 +145,8 @@
 
 CaptureLabelView::CaptureLabelView(
     CaptureModeSession* capture_mode_session,
-    base::RepeatingClosure on_capture_button_container_pressed)
+    base::RepeatingClosure on_capture_button_pressed,
+    base::RepeatingClosure on_drop_down_button_pressed)
     : capture_mode_session_(capture_mode_session) {
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
@@ -155,7 +157,8 @@
   layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
 
   capture_button_container_ = AddChildView(std::make_unique<CaptureButtonView>(
-      std::move(on_capture_button_container_pressed), base::DoNothing()));
+      std::move(on_capture_button_pressed),
+      std::move(on_drop_down_button_pressed)));
   capture_button_container_->SetPaintToLayer();
   capture_button_container_->layer()->SetFillsBoundsOpaquely(false);
   capture_button_container_->SetNotifyEnterExitOnChild(true);
@@ -179,6 +182,19 @@
   return capture_button_container_->GetVisible();
 }
 
+bool CaptureLabelView::IsPointOnRecordingTypeDropDownButton(
+    const gfx::Point& screen_location) const {
+  auto* drop_down_button = capture_button_container_->drop_down_button();
+  return drop_down_button &&
+         drop_down_button->GetBoundsInScreen().Contains(screen_location);
+}
+
+bool CaptureLabelView::IsRecordingTypeDropDownButtonVisible() const {
+  auto* drop_down_button = capture_button_container_->drop_down_button();
+  return capture_button_container_->GetVisible() && drop_down_button &&
+         drop_down_button->GetVisible();
+}
+
 void CaptureLabelView::UpdateIconAndText() {
   CaptureModeController* controller = CaptureModeController::Get();
   const CaptureModeSource source = controller->source();
diff --git a/ash/capture_mode/capture_label_view.h b/ash/capture_mode/capture_label_view.h
index 3b1a0bd8..1fb2af16 100644
--- a/ash/capture_mode/capture_label_view.h
+++ b/ash/capture_mode/capture_label_view.h
@@ -35,11 +35,24 @@
   METADATA_HEADER(CaptureLabelView);
 
   CaptureLabelView(CaptureModeSession* capture_mode_session,
-                   base::RepeatingClosure on_capture_button_container_pressed);
+                   base::RepeatingClosure on_capture_button_pressed,
+                   base::RepeatingClosure on_drop_down_button_pressed);
   CaptureLabelView(const CaptureLabelView&) = delete;
   CaptureLabelView& operator=(const CaptureLabelView&) = delete;
   ~CaptureLabelView() override;
 
+  CaptureButtonView* capture_button_container() {
+    return capture_button_container_;
+  }
+
+  // Returns true if the given `screen_location` is on the drop down button,
+  // which when clicked opens the recording type menu.
+  bool IsPointOnRecordingTypeDropDownButton(
+      const gfx::Point& screen_location) const;
+
+  // Returns true if the recording drop down button is available and visible.
+  bool IsRecordingTypeDropDownButtonVisible() const;
+
   // Returns true if this view is hosting the capture button instead of just a
   // label, and can be interacted with by the user. In this case, this view has
   // views that are a11y highlightable.
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.cc b/ash/capture_mode/capture_mode_demo_tools_controller.cc
index 4a6ee1d..8cb8010 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.cc
@@ -7,16 +7,22 @@
 #include <memory>
 
 #include "ash/capture_mode/capture_mode_constants.h"
+#include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/key_combo_view.h"
+#include "ash/capture_mode/pointer_highlight_layer.h"
 #include "ash/capture_mode/video_recording_watcher.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
+#include "base/containers/unique_ptr_adapters.h"
 #include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -25,6 +31,11 @@
 
 constexpr int kDistanceFromBottom = 24;
 
+constexpr float kHighlightLayerFinalOpacity = 0.f;
+constexpr float kHighlightLayerInitialScale = 0.1f;
+constexpr float kHighlightLayerFinalScale = 1.0f;
+constexpr base::TimeDelta kScaleUpDuration = base::Milliseconds(1500);
+
 int GetModifierFlagForKeyCode(ui::KeyboardCode key_code) {
   switch (key_code) {
     case ui::VKEY_COMMAND:
@@ -103,6 +114,38 @@
   OnKeyDownEvent(event);
 }
 
+void CaptureModeDemoToolsController::PerformMousePressAnimation(
+    const gfx::PointF& event_location_in_window) {
+  std::unique_ptr<PointerHighlightLayer> mouse_highlight_layer =
+      std::make_unique<PointerHighlightLayer>(
+          event_location_in_window,
+          video_recording_watcher_->GetOnCaptureSurfaceWidgetParentWindow()
+              ->layer());
+  PointerHighlightLayer* mouse_highlight_layer_ptr =
+      mouse_highlight_layer.get();
+  mouse_highlight_layers_.push_back(std::move(mouse_highlight_layer));
+
+  ui::Layer* highlight_layer = mouse_highlight_layer_ptr->layer();
+  highlight_layer->SetTransform(capture_mode_util::GetScaleTransformAboutCenter(
+      highlight_layer, kHighlightLayerInitialScale));
+  const gfx::Transform scale_up_transform =
+      capture_mode_util::GetScaleTransformAboutCenter(
+          highlight_layer, kHighlightLayerFinalScale);
+
+  views::AnimationBuilder()
+      .OnEnded(base::BindOnce(
+          &CaptureModeDemoToolsController::OnMouseHighlightAnimationEnded,
+          weak_ptr_factory_.GetWeakPtr(), mouse_highlight_layer_ptr))
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .Once()
+      .SetDuration(kScaleUpDuration)
+      .SetTransform(highlight_layer, scale_up_transform,
+                    gfx::Tween::ACCEL_0_40_DECEL_100)
+      .SetOpacity(highlight_layer, kHighlightLayerFinalOpacity,
+                  gfx::Tween::ACCEL_0_80_DECEL_80);
+}
+
 void CaptureModeDemoToolsController::OnKeyUpEvent(ui::KeyEvent* event) {
   const ui::KeyboardCode key_code = event->key_code();
   const int modifier_flag = GetModifierFlagForKeyCode(key_code);
@@ -188,4 +231,10 @@
   key_combo_view_ = nullptr;
 }
 
+void CaptureModeDemoToolsController::OnMouseHighlightAnimationEnded(
+    PointerHighlightLayer* pointer_highlight_layer_ptr) {
+  base::EraseIf(mouse_highlight_layers_,
+                base::MatchesUniquePtr(pointer_highlight_layer_ptr));
+}
+
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.h b/ash/capture_mode/capture_mode_demo_tools_controller.h
index 3060b58..f2791d9cc 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.h
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.h
@@ -5,6 +5,7 @@
 #ifndef ASH_CAPTURE_MODE_CAPTURE_MODE_DEMO_TOOLS_CONTROLLER_H_
 #define ASH_CAPTURE_MODE_CAPTURE_MODE_DEMO_TOOLS_CONTROLLER_H_
 
+#include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/views/widget/unique_widget_ptr.h"
@@ -15,9 +16,13 @@
 
 namespace ash {
 
+class PointerHighlightLayer;
 class KeyComboView;
 class VideoRecordingWatcher;
 
+using MouseHighlightLayers =
+    std::vector<std::unique_ptr<PointerHighlightLayer>>;
+
 // Observes and decides whether to show a helper widget representing the
 // currently pressed key combination or not. The key combination will be used to
 // construct or modify the `KeyComboViewer`. The
@@ -36,6 +41,14 @@
   // Decides whether to show a helper widget for the `event` or not.
   void OnKeyEvent(ui::KeyEvent* event);
 
+  // Creates a new highlight layer each time it gets called and performs the
+  // grow-and-fade-out animation on it.
+  void PerformMousePressAnimation(const gfx::PointF& event_location_in_window);
+
+  const MouseHighlightLayers& mouse_highlight_layers_for_testing() const {
+    return mouse_highlight_layers_;
+  }
+
  private:
   friend class CaptureModeDemoToolsTestApi;
 
@@ -51,6 +64,11 @@
   // Resets the `demo_tools_widget_` when the `hide_timer_` expires.
   void AnimateToResetTheWidget();
 
+  // Called when the mouse highlight animation ends to remove the corresponding
+  // pointer highlight from the `mouse_highlight_layers_`.
+  void OnMouseHighlightAnimationEnded(
+      PointerHighlightLayer* pointer_highlight_layer_ptr);
+
   VideoRecordingWatcher* const video_recording_watcher_;
   views::UniqueWidgetPtr demo_tools_widget_;
   KeyComboView* key_combo_view_ = nullptr;
@@ -64,6 +82,11 @@
   // Starts on key up of the last non-modifier key and the `key_combo_view_`
   // will disappear when it expires.
   base::OneShotTimer hide_timer_;
+
+  // Contains all the mouse highlight layers that are being animated.
+  MouseHighlightLayers mouse_highlight_layers_;
+
+  base::WeakPtrFactory<CaptureModeDemoToolsController> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_demo_tools_unittests.cc b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
index a01de1ea..b816cb5 100644
--- a/ash/capture_mode/capture_mode_demo_tools_unittests.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
@@ -17,17 +17,23 @@
 #include "ash/capture_mode/capture_mode_test_util.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/key_combo_view.h"
+#include "ash/capture_mode/pointer_highlight_layer.h"
+#include "ash/capture_mode/video_recording_watcher.h"
 #include "ash/constants/ash_features.h"
-#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/icon_button.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
 
@@ -52,7 +58,8 @@
 
 class CaptureModeDemoToolsTest : public AshTestBase {
  public:
-  CaptureModeDemoToolsTest() = default;
+  CaptureModeDemoToolsTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
   CaptureModeDemoToolsTest(const CaptureModeDemoToolsTest&) = delete;
   CaptureModeDemoToolsTest& operator=(const CaptureModeDemoToolsTest&) = delete;
   ~CaptureModeDemoToolsTest() override = default;
@@ -407,6 +414,16 @@
     EXPECT_TRUE(controller->is_recording_in_progress());
     return controller;
   }
+
+  gfx::Rect GetDemoToolsConfinedBoundsInScreenCoordinates() {
+    auto* recording_watcher =
+        CaptureModeController::Get()->video_recording_watcher_for_testing();
+    gfx::Rect confined_bounds_in_screen =
+        recording_watcher->GetCaptureSurfaceConfineBounds();
+    wm::ConvertRectToScreen(recording_watcher->window_being_recorded(),
+                            &confined_bounds_in_screen);
+    return confined_bounds_in_screen;
+  }
 };
 
 // Tests that the key combo viewer widget should be centered within its confined
@@ -417,17 +434,8 @@
   auto* demo_tools_controller = GetCaptureModeDemoToolsController();
   EXPECT_TRUE(demo_tools_controller);
 
-  auto* recording_watcher =
-      CaptureModeController::Get()->video_recording_watcher_for_testing();
   gfx::Rect confined_bounds_in_screen =
-      recording_watcher->GetCaptureSurfaceConfineBounds();
-
-  // Converts the bounds if it is in the window's coordinate to screen
-  // coordinate.
-  if (GetParam() == CaptureModeSource::kWindow) {
-    auto window_bounds = window()->GetBoundsInScreen();
-    confined_bounds_in_screen.Offset(window_bounds.x(), window_bounds.y());
-  }
+      GetDemoToolsConfinedBoundsInScreenCoordinates();
 
   // Verifies that the `demo_tools_widget` is positioned in the middle
   // horizontally within the confined bounds.
@@ -458,6 +466,68 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
+// Tests that the mouse highlight layer will be created on mouse down and
+// will disappear after the animation.
+TEST_P(CaptureModeDemoToolsTestWithAllSources, MouseHighlightTest) {
+  ui::ScopedAnimationDurationScaleMode normal_animation(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+  StartDemoToolsEnabledVideoRecordingWithParam();
+  auto* demo_tools_controller = GetCaptureModeDemoToolsController();
+  EXPECT_TRUE(demo_tools_controller);
+
+  gfx::Rect confined_bounds_in_screen =
+      GetDemoToolsConfinedBoundsInScreenCoordinates();
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(confined_bounds_in_screen.CenterPoint());
+  event_generator->PressLeftButton();
+  event_generator->ReleaseLeftButton();
+  EXPECT_FALSE(
+      demo_tools_controller->mouse_highlight_layers_for_testing().empty());
+  task_environment()->FastForwardBy(base::Milliseconds(3000));
+  EXPECT_TRUE(
+      demo_tools_controller->mouse_highlight_layers_for_testing().empty());
+}
+
+// Tests that multiple mouse highlight layers will be visible on consecutive
+// mouse press events when the whole duration are within the expiration of the
+// first animation expiration. It also tests that each mouse highlight layer
+// will be centered on its mouse event location.
+TEST_P(CaptureModeDemoToolsTestWithAllSources,
+       MouseHighlightShouldBeCenteredWithMouseClick) {
+  ui::ScopedAnimationDurationScaleMode normal_animation(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+  StartDemoToolsEnabledVideoRecordingWithParam();
+  auto* recording_watcher =
+      CaptureModeController::Get()->video_recording_watcher_for_testing();
+  auto* window_being_recorded = recording_watcher->window_being_recorded();
+  auto* demo_tools_controller = GetCaptureModeDemoToolsController();
+  EXPECT_TRUE(demo_tools_controller);
+
+  gfx::Rect inner_rect = GetDemoToolsConfinedBoundsInScreenCoordinates();
+  inner_rect.Inset(5);
+
+  auto& layers_vector =
+      demo_tools_controller->mouse_highlight_layers_for_testing();
+  auto* event_generator = GetEventGenerator();
+
+  for (auto point : {inner_rect.CenterPoint(), inner_rect.origin(),
+                     inner_rect.bottom_right()}) {
+    event_generator->MoveMouseTo(point);
+    event_generator->PressLeftButton();
+    event_generator->ReleaseLeftButton();
+    auto* highlight_layer = layers_vector.back().get();
+    auto highlight_center_point =
+        highlight_layer->layer()->bounds().CenterPoint();
+
+    // Convert the highlight layer center pointer to screen coordinates.
+    wm::ConvertPointToScreen(window_being_recorded, &highlight_center_point);
+
+    EXPECT_EQ(highlight_center_point, point);
+  }
+
+  EXPECT_EQ(layers_vector.size(), 3u);
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          CaptureModeDemoToolsTestWithAllSources,
                          testing::Values(CaptureModeSource::kFullscreen,
diff --git a/ash/capture_mode/capture_mode_menu_group.cc b/ash/capture_mode/capture_mode_menu_group.cc
index 6224362d..5471076 100644
--- a/ash/capture_mode/capture_mode_menu_group.cc
+++ b/ash/capture_mode/capture_mode_menu_group.cc
@@ -346,8 +346,12 @@
 // -----------------------------------------------------------------------------
 // CaptureModeMenuGroup:
 
-CaptureModeMenuGroup::CaptureModeMenuGroup(Delegate* delegate)
-    : CaptureModeMenuGroup(delegate, /*menu_header=*/nullptr) {}
+CaptureModeMenuGroup::CaptureModeMenuGroup(
+    Delegate* delegate,
+    const gfx::Insets& inside_border_insets)
+    : CaptureModeMenuGroup(delegate,
+                           /*menu_header=*/nullptr,
+                           inside_border_insets) {}
 
 CaptureModeMenuGroup::CaptureModeMenuGroup(Delegate* delegate,
                                            const gfx::VectorIcon& header_icon,
@@ -357,7 +361,8 @@
           delegate,
           std::make_unique<CaptureModeMenuHeader>(header_icon,
                                                   std::move(header_label),
-                                                  managed_by_policy)) {}
+                                                  managed_by_policy),
+          kMenuGroupPadding) {}
 
 CaptureModeMenuGroup::~CaptureModeMenuGroup() = default;
 
@@ -468,7 +473,8 @@
 
 CaptureModeMenuGroup::CaptureModeMenuGroup(
     Delegate* delegate,
-    std::unique_ptr<CaptureModeMenuHeader> menu_header)
+    std::unique_ptr<CaptureModeMenuHeader> menu_header,
+    const gfx::Insets& inside_border_insets)
     : delegate_(delegate),
       menu_header_(menu_header ? AddChildView(std::move(menu_header))
                                : nullptr),
@@ -477,7 +483,7 @@
   options_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical));
   SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical, kMenuGroupPadding,
+      views::BoxLayout::Orientation::kVertical, inside_border_insets,
       kSpaceBetweenMenuItem));
 }
 
diff --git a/ash/capture_mode/capture_mode_menu_group.h b/ash/capture_mode/capture_mode_menu_group.h
index d91fa99c..365b3466 100644
--- a/ash/capture_mode/capture_mode_menu_group.h
+++ b/ash/capture_mode/capture_mode_menu_group.h
@@ -48,8 +48,10 @@
 
   // This version of the constructor creates a header-less menu group. Note that
   // menu groups without headers is not designed for settings that are managed
-  // by policy.
-  explicit CaptureModeMenuGroup(Delegate* delegate);
+  // by policy. The `inside_border_insets` are used as paddings around the menu
+  // options and items in this group.
+  CaptureModeMenuGroup(Delegate* delegate,
+                       const gfx::Insets& inside_border_insets);
 
   // If `managed_by_policy` is true, the header of this menu group will show an
   // enterprise-managed feature icon next to the `header_label`.
@@ -128,7 +130,8 @@
   // Acts as a common constructor that's called by the above public
   // constructors.
   CaptureModeMenuGroup(Delegate* delegate,
-                       std::unique_ptr<CaptureModeMenuHeader> menu_header);
+                       std::unique_ptr<CaptureModeMenuHeader> menu_header,
+                       const gfx::Insets& inside_border_insets);
 
   // Returns the option whose ID is |option_id|, and nullptr if no such option
   // exists.
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index b5d426a..ae969dc 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -14,15 +14,14 @@
 #include "ash/capture_mode/capture_mode_camera_preview_view.h"
 #include "ash/capture_mode/capture_mode_constants.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/capture_mode/capture_mode_menu_group.h"
 #include "ash/capture_mode/capture_mode_session_focus_cycler.h"
 #include "ash/capture_mode/capture_mode_settings_view.h"
 #include "ash/capture_mode/capture_mode_type_view.h"
 #include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/capture_window_observer.h"
 #include "ash/capture_mode/folder_selection_dialog_controller.h"
+#include "ash/capture_mode/recording_type_menu_view.h"
 #include "ash/capture_mode/user_nudge_controller.h"
-#include "ash/constants/ash_features.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/window_tree_host_manager.h"
@@ -30,18 +29,17 @@
 #include "ash/projector/projector_controller_impl.h"
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/color_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_dimmer.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/check.h"
+#include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
 #include "cc/paint/paint_flags.h"
 #include "ui/aura/client/aura_constants.h"
@@ -67,13 +65,11 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/transform_util.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/shadow_value.h"
 #include "ui/gfx/skia_paint_util.h"
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
-#include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
@@ -367,13 +363,6 @@
              widget->GetWindowBoundsInScreen());
 }
 
-// Returns the color provider for the native theme.
-ui::ColorProvider* GetColorProvider() {
-  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
-  return ui::ColorProviderManager::Get().GetColorProviderFor(
-      native_theme->GetColorProviderKey(nullptr));
-}
-
 }  // namespace
 
 // -----------------------------------------------------------------------------
@@ -853,6 +842,10 @@
   }
 
   if (!capture_mode_settings_widget_) {
+    // Close the recording type menu if any. There can be only one menu visible
+    // at any time.
+    SetRecordingTypeMenuShown(false);
+
     auto* parent = GetParentContainer(current_root_);
     capture_mode_settings_widget_ = std::make_unique<views::Widget>();
     MaybeDismissUserNudgeForever();
@@ -907,19 +900,21 @@
   if (!capture_label_widget_->IsVisible())
     capture_label_widget_->Show();
 
-  CaptureLabelView* label_view =
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
-  label_view->StartCountDown(std::move(countdown_finished_callback));
+  DCHECK(capture_label_view_);
+  capture_label_view_->StartCountDown(std::move(countdown_finished_callback));
   UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kCountdownStart);
 
   UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
                /*is_touch=*/false);
 
-  // Fade out the capture bar, capture settings and capture toast if they exist.
+  // Fade out the capture bar, capture settings, recording type menu, and the
+  // capture toast if they exist.
   std::vector<ui::Layer*> layers_to_fade_out{
       capture_mode_bar_widget_->GetLayer()};
   if (capture_mode_settings_widget_)
     layers_to_fade_out.push_back(capture_mode_settings_widget_->GetLayer());
+  if (recording_type_menu_widget_)
+    layers_to_fade_out.push_back(recording_type_menu_widget_->GetLayer());
   if (auto* toast_layer = capture_toast_controller_.MaybeGetToastLayer())
     layers_to_fade_out.push_back(toast_layer);
 
@@ -965,9 +960,8 @@
   if (is_shutting_down_)
     return false;
 
-  CaptureLabelView* label_view =
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
-  return label_view->IsInCountDownAnimation();
+  DCHECK(capture_label_view_);
+  return capture_label_view_->IsInCountDownAnimation();
 }
 
 void CaptureModeSession::OnCaptureFolderMayHaveChanged() {
@@ -1124,10 +1118,12 @@
       event->StopPropagation();
       *should_update_opacity_ptr = true;
 
-      // We only dismiss the settings menu or clear the focus on ESC key if the
-      // count down is not in progress.
+      // We only dismiss the settings / recording type menus or clear the focus
+      // on ESC key if the count down is not in progress.
       const bool is_in_count_down = IsInCountDownAnimation();
-      if (capture_mode_settings_widget_ && !is_in_count_down)
+      if (recording_type_menu_widget_ && !is_in_count_down)
+        SetRecordingTypeMenuShown(false);
+      else if (capture_mode_settings_widget_ && !is_in_count_down)
         SetSettingsMenuShown(false);
       else if (focus_cycler_->HasFocus() && !is_in_count_down)
         focus_cycler_->ClearFocus();
@@ -1258,11 +1254,7 @@
   DCHECK_EQ(parent->layer(), layer()->parent());
   layer()->SetBounds(parent->bounds());
 
-  // We need to update the capture bar bounds first and then settings bounds.
-  // The sequence matters here since settings bounds depend on capture bar
-  // bounds.
   RefreshBarWidgetBounds();
-  MaybeUpdateSettingsBounds();
 
   // Only need to update the camera preview's bounds if the capture source is
   // `kFullscreen`, since `ClampCaptureRegionToRootWindowSize` will take care of
@@ -1350,26 +1342,28 @@
 
   // If the current mouse event is on capture label button, and capture label
   // button can handle the event, show the hand mouse cursor.
+  DCHECK(capture_label_view_);
   const bool is_event_on_capture_button =
       capture_label_widget_->GetWindowBoundsInScreen().Contains(
           location_in_screen) &&
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView())
-          ->ShouldHandleEvent();
+      capture_label_view_->ShouldHandleEvent();
   if (is_event_on_capture_button) {
     cursor_setter_->UpdateCursor(ui::mojom::CursorType::kHand);
     return;
   }
 
-  // As long as the settings menu is open, a pointer cursor should be used as
-  // long as the cursor is not on top of the capture button, since clicking
-  // anywhere outside the bounds of either of them (the menu or the clickable
-  // capture button) will dismiss the menu. Also if the event is on the bar, a
-  // pointer will also be used, as long as the bar is visible.
+  // As long as the settings menu, or the recording type menu are open, a
+  // pointer cursor should be used as long as the cursor is not on top of the
+  // capture button, since clicking anywhere outside the bounds of either of
+  // them (the menus or the clickable capture button) will dismiss the menus.
+  // Also if the event is on the bar, a pointer will also be used, as long as
+  // the bar is visible.
   const bool is_event_on_capture_bar =
       capture_mode_bar_widget_->GetLayer()->GetTargetOpacity() &&
       capture_mode_bar_widget_->GetWindowBoundsInScreen().Contains(
           location_in_screen);
-  if (capture_mode_settings_widget_ || is_event_on_capture_bar) {
+  if (capture_mode_settings_widget_ || is_event_on_capture_bar ||
+      recording_type_menu_widget_) {
     cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
     return;
   }
@@ -1441,6 +1435,8 @@
 
   const bool is_settings_visible = capture_mode_settings_widget_ &&
                                    capture_mode_settings_widget_->IsVisible();
+  const bool is_recording_type_menu_visible =
+      recording_type_menu_widget_ && recording_type_menu_widget_->IsVisible();
   gfx::Rect capture_region = controller_->user_capture_region();
   wm::ConvertRectToScreen(current_root_, &capture_region);
 
@@ -1492,7 +1488,8 @@
     }
 
     if (widget == capture_label_widget_.get() &&
-        (is_cursor_on_top_of_widget || focus_cycler_->CaptureLabelFocused())) {
+        (is_cursor_on_top_of_widget || focus_cycler_->CaptureLabelFocused() ||
+         is_recording_type_menu_visible)) {
       continue;
     }
 
@@ -1519,8 +1516,8 @@
   DCHECK(!controller_->is_recording_in_progress());
 
   // If settings menu is shown at the beginning of drag, we should close it.
-  if (capture_mode_settings_widget_)
-    SetSettingsMenuShown(false);
+  SetSettingsMenuShown(false);
+  SetRecordingTypeMenuShown(false);
 
   // Hide capture UIs while dragging camera preview.
   HideAllUis();
@@ -1592,6 +1589,8 @@
   result.push_back(capture_mode_bar_widget_.get());
   if (capture_label_widget_)
     result.push_back(capture_label_widget_.get());
+  if (recording_type_menu_widget_)
+    result.push_back(recording_type_menu_widget_.get());
   if (capture_mode_settings_widget_)
     result.push_back(capture_mode_settings_widget_.get());
   if (dimensions_label_widget_)
@@ -1660,10 +1659,12 @@
 
 void CaptureModeSession::RefreshBarWidgetBounds() {
   DCHECK(capture_mode_bar_widget_);
+  // We need to update the capture bar bounds first and then settings bounds.
+  // The sequence matters here since settings bounds depend on capture bar
+  // bounds.
   capture_mode_bar_widget_->SetBounds(
       CaptureModeBarView::GetBounds(current_root_, is_in_projector_mode_));
-  auto* parent = GetParentContainer(current_root_);
-  parent->StackChildAtTop(capture_mode_bar_widget_->GetNativeWindow());
+  MaybeUpdateSettingsBounds();
   if (user_nudge_controller_)
     user_nudge_controller_->Reposition();
   capture_toast_controller_.MaybeRepositionCaptureToast();
@@ -1694,6 +1695,11 @@
   controller_->PerformCapture();  // `this` can be deleted after this.
 }
 
+void CaptureModeSession::OnRecordingTypeDropDownButtonPressed() {
+  SetRecordingTypeMenuShown(!recording_type_menu_widget_ ||
+                            !recording_type_menu_widget_->IsVisible());
+}
+
 gfx::Rect CaptureModeSession::GetSelectedWindowBounds() const {
   auto* window = GetSelectedWindow();
   return window ? window->bounds() : gfx::Rect();
@@ -1723,6 +1729,8 @@
     widget_in_order.emplace_back(capture_label_widget_.get());
   if (capture_mode_bar_widget_)
     widget_in_order.emplace_back(capture_mode_bar_widget_.get());
+  if (recording_type_menu_widget_)
+    widget_in_order.emplace_back(recording_type_menu_widget_.get());
   if (capture_mode_settings_widget_)
     widget_in_order.emplace_back(capture_mode_settings_widget_.get());
 
@@ -1765,7 +1773,8 @@
   const float dsf = canvas->UndoDeviceScaleFactor();
   region = gfx::ScaleToEnclosingRect(region, dsf);
 
-  const auto* color_provider = GetColorProvider();
+  const auto* color_provider =
+      capture_mode_util::GetColorProviderForNativeTheme();
 
   if (!adjustable_region) {
     canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
@@ -1945,9 +1954,17 @@
   if (ShouldCaptureLabelHandleEvent(event_target))
     return;
 
-  // Also allow events that target the settings menu (if present) to go through.
-  if (IsEventTargetedOnSettingsMenu(*event))
+  // Let the recording type menu handle its events if any.
+  if (capture_mode_util::IsEventTargetedOnWidget(
+          *event, recording_type_menu_widget_.get())) {
     return;
+  }
+
+  // Also allow events that target the settings menu (if present) to go through.
+  if (capture_mode_util::IsEventTargetedOnWidget(
+          *event, capture_mode_settings_widget_.get())) {
+    return;
+  }
 
   // Here we know that the event doesn't target the settings menu, so if it's a
   // press event, we will use it to dismiss the settings menu, unless it's on
@@ -1968,6 +1985,17 @@
     SetSettingsMenuShown(/*shown=*/false);
   }
 
+  // Similar to the above, we want a press event that is outside the recording
+  // type menu to close it, unless it is on on the drop down menu button if any.
+  const bool should_close_recording_type_menu =
+      is_press_event &&
+      !IsPointOnRecordingTypeDropDownButton(screen_location) &&
+      recording_type_menu_widget_;
+  if (should_close_recording_type_menu) {
+    ignore_located_events_ = true;
+    SetRecordingTypeMenuShown(false);
+  }
+
   const bool old_ignore_located_events = ignore_located_events_;
   if (ignore_located_events_) {
     if (is_release_event)
@@ -1975,13 +2003,16 @@
   }
 
   // Events targeting the capture bar should also go through.
-  if (IsEventTargetedOnCaptureBar(*event))
+  if (capture_mode_util::IsEventTargetedOnWidget(
+          *event, capture_mode_bar_widget_.get())) {
     return;
+  }
 
   event->SetHandled();
   event->StopPropagation();
 
-  if (should_close_settings || old_ignore_located_events) {
+  if (should_close_settings || old_ignore_located_events ||
+      should_close_recording_type_menu) {
     // Note that these ignored events have already been consumed above.
     return;
   }
@@ -2381,18 +2412,26 @@
     auto* parent = GetParentContainer(current_root_);
     capture_label_widget_->Init(
         CreateWidgetParams(parent, gfx::Rect(), "CaptureLabel"));
-    capture_label_widget_->SetContentsView(std::make_unique<CaptureLabelView>(
-        this, base::BindRepeating(&CaptureModeSession::DoPerformCapture,
-                                  base::Unretained(this))));
+    capture_label_view_ = capture_label_widget_->SetContentsView(
+        std::make_unique<CaptureLabelView>(
+            this,
+            base::BindRepeating(&CaptureModeSession::DoPerformCapture,
+                                base::Unretained(this)),
+            base::BindRepeating(
+                &CaptureModeSession::OnRecordingTypeDropDownButtonPressed,
+                base::Unretained(this))));
     capture_label_widget_->GetNativeWindow()->SetTitle(
         l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_A11Y_TITLE));
     capture_label_widget_->Show();
   }
 
-  CaptureLabelView* label_view =
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
-  label_view->UpdateIconAndText();
+  // Note that the order here matters. The bounds of the recording type menu
+  // widget is always relative to the bounds of the `capture_label_widget_`.
+  // Thus, the latter must be updated before the former. Also, the menu may need
+  // to close if the `label_view` becomes not interactable.
+  capture_label_view_->UpdateIconAndText();
   UpdateCaptureLabelWidgetBounds(animation_type);
+  MaybeUpdateRecordingTypeMenu();
 
   focus_cycler_->OnCaptureLabelWidgetUpdated();
 }
@@ -2475,10 +2514,9 @@
 
 gfx::Rect CaptureModeSession::CalculateCaptureLabelWidgetBounds() {
   DCHECK(capture_label_widget_);
-  CaptureLabelView* label_view =
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
+  DCHECK(capture_label_view_);
 
-  const gfx::Size preferred_size = label_view->GetPreferredSize();
+  const gfx::Size preferred_size = capture_label_view_->GetPreferredSize();
   const gfx::Rect capture_bar_bounds =
       capture_mode_bar_widget_->GetNativeWindow()->bounds();
 
@@ -2581,7 +2619,7 @@
   // away from one of the edges of the selected window.
   if (source == CaptureModeSource::kRegion && !is_selecting_region_ &&
       !capture_region.IsEmpty()) {
-    if (label_view->IsInCountDownAnimation()) {
+    if (capture_label_view_->IsInCountDownAnimation()) {
       // If countdown starts, calculate the bounds based on the old capture
       // label's position, otherwise, since the countdown label bounds is
       // smaller than the label bounds and may fit into the capture region even
@@ -2610,9 +2648,8 @@
     return false;
   }
 
-  CaptureLabelView* label_view =
-      static_cast<CaptureLabelView*>(capture_label_widget_->GetContentsView());
-  return label_view->ShouldHandleEvent();
+  DCHECK(capture_label_view_);
+  return capture_label_view_->ShouldHandleEvent();
 }
 
 void CaptureModeSession::MaybeChangeRoot(aura::Window* new_root) {
@@ -2654,8 +2691,11 @@
   UpdateCaptureRegion(gfx::Rect(), /*is_resizing=*/false, /*by_user=*/false);
 
   UpdateRootWindowDimmers();
-
   MaybeReparentCameraPreviewWidget();
+
+  // Changing the root window may require updating the stacking order on the new
+  // display.
+  RefreshStackingOrder();
 }
 
 void CaptureModeSession::UpdateRootWindowDimmers() {
@@ -2808,18 +2848,69 @@
   }
 }
 
-bool CaptureModeSession::IsEventTargetedOnCaptureBar(
-    const ui::LocatedEvent& event) const {
-  DCHECK(capture_mode_bar_widget_);
-  auto* target = static_cast<aura::Window*>(event.target());
-  return capture_mode_bar_widget_->GetNativeWindow()->Contains(target);
+void CaptureModeSession::SetRecordingTypeMenuShown(bool shown) {
+  if (!shown) {
+    recording_type_menu_widget_.reset();
+    return;
+  }
+
+  if (!recording_type_menu_widget_) {
+    DCHECK(features::IsGifRecordingEnabled());
+    DCHECK(capture_label_widget_);
+    DCHECK(capture_label_widget_->IsVisible());
+
+    // Close the settings widget if any. Only one menu at a time can be visible.
+    SetSettingsMenuShown(false);
+
+    auto* parent = GetParentContainer(current_root_);
+    recording_type_menu_widget_ = std::make_unique<views::Widget>();
+    MaybeDismissUserNudgeForever();
+    capture_toast_controller_.DismissCurrentToastIfAny();
+
+    recording_type_menu_widget_->Init(CreateWidgetParams(
+        parent,
+        RecordingTypeMenuView::GetIdealScreenBounds(
+            capture_label_widget_->GetWindowBoundsInScreen()),
+        "RecordingTypeMenuWidget"));
+    recording_type_menu_widget_->SetContentsView(
+        std::make_unique<RecordingTypeMenuView>());
+
+    auto* menu_window = recording_type_menu_widget_->GetNativeWindow();
+    parent->StackChildAtTop(menu_window);
+
+    menu_window->SetTitle(l10n_util::GetStringUTF16(
+        IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE));
+  }
+
+  recording_type_menu_widget_->Show();
 }
 
-bool CaptureModeSession::IsEventTargetedOnSettingsMenu(
-    const ui::LocatedEvent& event) const {
-  auto* target = static_cast<aura::Window*>(event.target());
-  return capture_mode_settings_widget_ &&
-         capture_mode_settings_widget_->GetNativeWindow()->Contains(target);
+bool CaptureModeSession::IsPointOnRecordingTypeDropDownButton(
+    const gfx::Point& screen_location) const {
+  if (!capture_label_widget_ || !capture_label_widget_->IsVisible())
+    return false;
+
+  DCHECK(capture_label_view_);
+  return capture_label_view_->IsPointOnRecordingTypeDropDownButton(
+      screen_location);
+}
+
+void CaptureModeSession::MaybeUpdateRecordingTypeMenu() {
+  if (!recording_type_menu_widget_)
+    return;
+
+  // If the the drop down button becomes hidden, the recording type menu widget
+  // should also hide.
+  if (!capture_label_widget_ ||
+      !capture_label_view_->IsRecordingTypeDropDownButtonVisible()) {
+    SetRecordingTypeMenuShown(false);
+    return;
+  }
+
+  recording_type_menu_widget_->SetBounds(
+      RecordingTypeMenuView::GetIdealScreenBounds(
+          capture_label_widget_->GetWindowBoundsInScreen(),
+          recording_type_menu_widget_->GetContentsView()));
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index b3febe7..51f599e9e 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -10,6 +10,7 @@
 
 #include "ash/accessibility/magnifier/magnifier_glass.h"
 #include "ash/ash_export.h"
+#include "ash/capture_mode/capture_label_view.h"
 #include "ash/capture_mode/capture_mode_toast_controller.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/folder_selection_dialog_controller.h"
@@ -294,6 +295,10 @@
   // record button in the capture label view.
   void DoPerformCapture();
 
+  // Called when the drop-down button in the `capture_label_widget_` is pressed
+  // which toggles the recording type menu on and off.
+  void OnRecordingTypeDropDownButtonPressed();
+
   // Gets the bounds of current window selected for |kWindow| capture source.
   gfx::Rect GetSelectedWindowBounds() const;
 
@@ -420,12 +425,19 @@
   // camera preview's bounds and visibility.
   void MaybeUpdateCameraPreviewBounds();
 
-  // Returns true if the given `event` is targeted on the capture bar.
-  bool IsEventTargetedOnCaptureBar(const ui::LocatedEvent& event) const;
+  // Creates or distroys the recording type menu widget based on the given
+  // `shown` value.
+  void SetRecordingTypeMenuShown(bool shown);
 
-  // Returns true if the given `event` is targeted on the setting menu if it
-  // exists.
-  bool IsEventTargetedOnSettingsMenu(const ui::LocatedEvent& event) const;
+  // Returns true if the given `screen_location` is on the drop down button in
+  // the `capture_label_widget_` which when clicked opens the recording type
+  // menu.
+  bool IsPointOnRecordingTypeDropDownButton(
+      const gfx::Point& screen_location) const;
+
+  // Updates the availability or bounds of the recording type menu widget
+  // according to the current state.
+  void MaybeUpdateRecordingTypeMenu();
 
   CaptureModeController* const controller_;
 
@@ -454,6 +466,11 @@
   // starting capturing, the widget will transform into a 3-second countdown
   // timer.
   views::UniqueWidgetPtr capture_label_widget_;
+  CaptureLabelView* capture_label_view_ = nullptr;
+
+  // Widget that hosts the recording type menu, from which the user can pick the
+  // desired recording format type.
+  views::UniqueWidgetPtr recording_type_menu_widget_;
 
   // Magnifier glass used during a region capture session.
   MagnifierGlass magnifier_glass_;
diff --git a/ash/capture_mode/capture_mode_session_test_api.cc b/ash/capture_mode/capture_mode_session_test_api.cc
index 669520c..3bb8a38 100644
--- a/ash/capture_mode/capture_mode_session_test_api.cc
+++ b/ash/capture_mode/capture_mode_session_test_api.cc
@@ -4,13 +4,22 @@
 
 #include "ash/capture_mode/capture_mode_session_test_api.h"
 
+#include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_session.h"
 
 namespace ash {
 
+CaptureModeSessionTestApi::CaptureModeSessionTestApi()
+    : session_(CaptureModeController::Get()->capture_mode_session()) {
+  DCHECK(CaptureModeController::Get()->IsActive());
+  DCHECK(session_);
+}
+
 CaptureModeSessionTestApi::CaptureModeSessionTestApi(
     CaptureModeSession* session)
-    : session_(session) {}
+    : session_(session) {
+  DCHECK(session_);
+}
 
 CaptureModeBarView* CaptureModeSessionTestApi::GetCaptureModeBarView() {
   return session_->capture_mode_bar_view_;
@@ -21,6 +30,10 @@
   return session_->capture_mode_settings_view_;
 }
 
+CaptureLabelView* CaptureModeSessionTestApi::GetCaptureLabelView() {
+  return session_->capture_label_view_;
+}
+
 views::Widget* CaptureModeSessionTestApi::GetCaptureModeSettingsWidget() {
   return session_->capture_mode_settings_widget_.get();
 }
@@ -29,6 +42,10 @@
   return session_->capture_label_widget_.get();
 }
 
+views::Widget* CaptureModeSessionTestApi::GetRecordingTypeMenuWidget() {
+  return session_->recording_type_menu_widget_.get();
+}
+
 views::Widget* CaptureModeSessionTestApi::GetDimensionsLabelWidget() {
   return session_->dimensions_label_widget_.get();
 }
diff --git a/ash/capture_mode/capture_mode_session_test_api.h b/ash/capture_mode/capture_mode_session_test_api.h
index 02c4955..883775d 100644
--- a/ash/capture_mode/capture_mode_session_test_api.h
+++ b/ash/capture_mode/capture_mode_session_test_api.h
@@ -9,15 +9,17 @@
 
 namespace ash {
 
-class CaptureModeSession;
+class CaptureLabelView;
 class CaptureModeBarView;
+class CaptureModeSession;
 class CaptureModeSettingsView;
-class UserNudgeController;
 class MagnifierGlass;
+class UserNudgeController;
 
 // Wrapper for CaptureModeSession that exposes internal state to test functions.
 class CaptureModeSessionTestApi {
  public:
+  CaptureModeSessionTestApi();
   explicit CaptureModeSessionTestApi(CaptureModeSession* session);
 
   CaptureModeSessionTestApi(CaptureModeSessionTestApi&) = delete;
@@ -28,10 +30,14 @@
 
   CaptureModeSettingsView* GetCaptureModeSettingsView();
 
+  CaptureLabelView* GetCaptureLabelView();
+
   views::Widget* GetCaptureModeSettingsWidget();
 
   views::Widget* GetCaptureLabelWidget();
 
+  views::Widget* GetRecordingTypeMenuWidget();
+
   views::Widget* GetDimensionsLabelWidget();
 
   UserNudgeController* GetUserNudgeController();
diff --git a/ash/capture_mode/capture_mode_test_util.cc b/ash/capture_mode/capture_mode_test_util.cc
index 9ca8b64..37a788c 100644
--- a/ash/capture_mode/capture_mode_test_util.cc
+++ b/ash/capture_mode/capture_mode_test_util.cc
@@ -194,9 +194,11 @@
           []() { ProjectorController::Get()->OnSpeechRecognitionStopped(); }));
 
   // Simulate the availability of speech recognition.
+  SpeechRecognitionAvailability availability;
+  availability.on_device_availability =
+      OnDeviceRecognitionAvailability::kAvailable;
   ON_CALL(projector_client_, GetSpeechRecognitionAvailability)
-      .WillByDefault(
-          testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
+      .WillByDefault(testing::Return(availability));
   EXPECT_CALL(projector_client_, IsDriveFsMounted())
       .WillRepeatedly(testing::Return(true));
 }
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index ab5cb231..de748cb 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -78,11 +78,6 @@
              ->CalculateCameraPreviewTargetVisibility();
 }
 
-// Returns the local center point of the given `layer`.
-gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
-  return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
-}
-
 void FadeInWidget(views::Widget* widget,
                   const AnimationParams& animation_params) {
   DCHECK(widget);
@@ -355,6 +350,10 @@
   return play_view;
 }
 
+gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
+  return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
+}
+
 gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale) {
   return gfx::GetScaleTransform(GetLocalCenterPoint(layer), scale);
 }
@@ -513,4 +512,16 @@
   }
 }
 
+ui::ColorProvider* GetColorProviderForNativeTheme() {
+  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
+  return ui::ColorProviderManager::Get().GetColorProviderFor(
+      native_theme->GetColorProviderKey(nullptr));
+}
+
+bool IsEventTargetedOnWidget(const ui::LocatedEvent& event,
+                             views::Widget* widget) {
+  auto* target = static_cast<aura::Window*>(event.target());
+  return widget && widget->GetNativeWindow()->Contains(target);
+}
+
 }  // namespace ash::capture_mode_util
diff --git a/ash/capture_mode/capture_mode_util.h b/ash/capture_mode/capture_mode_util.h
index ff56fda..1a5e3abe 100644
--- a/ash/capture_mode/capture_mode_util.h
+++ b/ash/capture_mode/capture_mode_util.h
@@ -26,7 +26,9 @@
 }  // namespace gfx
 
 namespace ui {
+class ColorProvider;
 class Layer;
+class LocatedEvent;
 }  // namespace ui
 
 namespace views {
@@ -108,6 +110,9 @@
 // notification.
 std::unique_ptr<views::View> CreatePlayIconView();
 
+// Returns the local center point of the given `layer`.
+gfx::Point GetLocalCenterPoint(ui::Layer* layer);
+
 // Returns a transform that scales the given `layer` by the given `scale` factor
 // in both X and Y around its local center point.
 gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale);
@@ -189,6 +194,14 @@
 void MaybeUpdateCameraPrivacyIndicator(bool camera_on);
 void MaybeUpdateMicrophonePrivacyIndicator(bool mic_on);
 
+ui::ColorProvider* GetColorProviderForNativeTheme();
+
+// Returns true if the given located `event` is targeted on a window that is a
+// descendant of the given `widget`. Note that `widget` can be provided as null
+// if it no longer exists, in this case this function returns false.
+bool IsEventTargetedOnWidget(const ui::LocatedEvent& event,
+                             views::Widget* widget);
+
 }  // namespace capture_mode_util
 
 }  // namespace ash
diff --git a/ash/capture_mode/gif_recording_unittests.cc b/ash/capture_mode/gif_recording_unittests.cc
new file mode 100644
index 0000000..794153c
--- /dev/null
+++ b/ash/capture_mode/gif_recording_unittests.cc
@@ -0,0 +1,164 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/capture_mode/capture_button_view.h"
+#include "ash/capture_mode/capture_label_view.h"
+#include "ash/capture_mode/capture_mode_bar_view.h"
+#include "ash/capture_mode/capture_mode_controller.h"
+#include "ash/capture_mode/capture_mode_session_test_api.h"
+#include "ash/capture_mode/capture_mode_test_util.h"
+#include "ash/capture_mode/capture_mode_types.h"
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
+#include "ash/style/icon_button.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+class GifRecordingTest : public AshTestBase {
+ public:
+  GifRecordingTest() : scoped_feature_list_(features::kGifRecording) {}
+  GifRecordingTest(const GifRecordingTest&) = delete;
+  GifRecordingTest& operator=(const GifRecordingTest&) = delete;
+  ~GifRecordingTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    CaptureModeController::Get()->SetUserCaptureRegion(gfx::Rect(200, 200),
+                                                       /*by_user=*/true);
+  }
+
+  CaptureModeController* StartRegionVideoCapture() {
+    return StartCaptureSession(CaptureModeSource::kRegion,
+                               CaptureModeType::kVideo);
+  }
+
+  CaptureLabelView* GetCaptureLabelView() {
+    return CaptureModeSessionTestApi().GetCaptureLabelView();
+  }
+
+  views::Widget* GetRecordingTypeMenuWidget() {
+    return CaptureModeSessionTestApi().GetRecordingTypeMenuWidget();
+  }
+
+  views::Widget* GetSettingsMenuWidget() {
+    return CaptureModeSessionTestApi().GetCaptureModeSettingsWidget();
+  }
+
+  void ClickOnDropDownButton() {
+    auto* label_view = GetCaptureLabelView();
+    ASSERT_TRUE(label_view->IsRecordingTypeDropDownButtonVisible());
+    CaptureButtonView* capture_button_container =
+        label_view->capture_button_container();
+    LeftClickOn(capture_button_container->drop_down_button());
+  }
+
+  void ClickOnSettingsButton() {
+    CaptureModeBarView* bar_view =
+        CaptureModeSessionTestApi().GetCaptureModeBarView();
+    LeftClickOn(bar_view->settings_button());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(GifRecordingTest, DropDownButtonVisibility) {
+  // With region video recording, the drop down button should be visible.
+  auto* controller = StartRegionVideoCapture();
+  auto* label_view = GetCaptureLabelView();
+  EXPECT_TRUE(label_view->IsRecordingTypeDropDownButtonVisible());
+
+  // It should hide, once we switch to image recording, but the label view
+  // should remain interactable.
+  controller->SetType(CaptureModeType::kImage);
+  EXPECT_FALSE(label_view->IsRecordingTypeDropDownButtonVisible());
+  EXPECT_TRUE(label_view->IsViewInteractable());
+
+  // Switching to a fullscreen source, the label view becomes no longer
+  // interactable, and the drop down button remains hidden.
+  controller->SetSource(CaptureModeSource::kFullscreen);
+  EXPECT_FALSE(label_view->IsRecordingTypeDropDownButtonVisible());
+  EXPECT_FALSE(label_view->IsViewInteractable());
+
+  // Even when we switch back to video recording.
+  controller->SetType(CaptureModeType::kVideo);
+  EXPECT_FALSE(label_view->IsRecordingTypeDropDownButtonVisible());
+  EXPECT_FALSE(label_view->IsViewInteractable());
+
+  // Only region recording in video mode, that the label view is interactable,
+  // and the button is visible.
+  controller->SetSource(CaptureModeSource::kRegion);
+  EXPECT_TRUE(label_view->IsRecordingTypeDropDownButtonVisible());
+  EXPECT_TRUE(label_view->IsViewInteractable());
+}
+
+TEST_F(GifRecordingTest, RecordingTypeMenuCreation) {
+  // The drop down button acts as a toggle.
+  StartRegionVideoCapture();
+  ClickOnDropDownButton();
+  EXPECT_TRUE(GetRecordingTypeMenuWidget());
+  ClickOnDropDownButton();
+  EXPECT_FALSE(GetRecordingTypeMenuWidget());
+
+  // The settings menu and the recording type menu are mutually exclusive,
+  // opening one closes the other.
+  ClickOnSettingsButton();
+  EXPECT_TRUE(GetSettingsMenuWidget());
+  ClickOnDropDownButton();
+  EXPECT_TRUE(GetRecordingTypeMenuWidget());
+  EXPECT_FALSE(GetSettingsMenuWidget());
+  ClickOnSettingsButton();
+  EXPECT_TRUE(GetSettingsMenuWidget());
+  EXPECT_FALSE(GetRecordingTypeMenuWidget());
+}
+
+TEST_F(GifRecordingTest, EscKeyClosesMenu) {
+  // Hitting the ESC key closes the recording type menu, but the session remains
+  // active.
+  auto* controller = StartRegionVideoCapture();
+  ClickOnDropDownButton();
+  EXPECT_TRUE(GetRecordingTypeMenuWidget());
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+  EXPECT_FALSE(GetRecordingTypeMenuWidget());
+  EXPECT_TRUE(controller->IsActive());
+}
+
+TEST_F(GifRecordingTest, EnterKeyHidesMenuAndStartsCountDown) {
+  StartRegionVideoCapture();
+  ClickOnDropDownButton();
+  auto* recording_type_menu = GetRecordingTypeMenuWidget();
+  EXPECT_TRUE(recording_type_menu);
+
+  // Pressing the ENTER key starts the recording count down, at which point, the
+  // menu remains open but fades out to an opacity of 0.
+  PressAndReleaseKey(ui::VKEY_RETURN);
+  EXPECT_TRUE(CaptureModeTestApi().IsInCountDownAnimation());
+  ASSERT_EQ(recording_type_menu, GetRecordingTypeMenuWidget());
+  EXPECT_FLOAT_EQ(recording_type_menu->GetLayer()->GetTargetOpacity(), 0);
+}
+
+TEST_F(GifRecordingTest, ClickingOutsideClosesMenu) {
+  auto* controller = StartRegionVideoCapture();
+  ClickOnDropDownButton();
+  EXPECT_TRUE(GetRecordingTypeMenuWidget());
+
+  // Clicking outside the menu widget should close it, but the region should not
+  // change.
+  const auto region = controller->user_capture_region();
+  auto* generator = GetEventGenerator();
+  generator->MoveMouseTo(region.bottom_right() + gfx::Vector2d(10, 10));
+  generator->ClickLeftButton();
+  EXPECT_FALSE(GetRecordingTypeMenuWidget());
+  EXPECT_EQ(region, controller->user_capture_region());
+}
+
+}  // namespace ash
diff --git a/ash/capture_mode/pointer_highlight_layer.cc b/ash/capture_mode/pointer_highlight_layer.cc
new file mode 100644
index 0000000..66673b2
--- /dev/null
+++ b/ash/capture_mode/pointer_highlight_layer.cc
@@ -0,0 +1,95 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/capture_mode/pointer_highlight_layer.h"
+
+#include "ash/capture_mode/capture_mode_util.h"
+#include "ash/style/dark_light_mode_controller_impl.h"
+#include "base/check.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/color/color_provider.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_type.h"
+#include "ui/compositor/paint_recorder.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/dip_util.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/scoped_canvas.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kHighlightLayerRadius = 36;
+constexpr float kHighlightLayerInitialOpacity = 1.f;
+constexpr float kLightModeBorderOpacityScaleFactor = 0.8f;
+const int kHighlightStrokeWidth = 2;
+constexpr int kFillsRadius = kHighlightLayerRadius - kHighlightStrokeWidth;
+
+// Calculates the layer bounds based on the event location in the coordinates of
+// the window being recorded.
+gfx::Rect CalculateHighlightLayerBounds(
+    const gfx::PointF& event_location_in_window) {
+  return gfx::Rect(event_location_in_window.x() - kHighlightLayerRadius,
+                   event_location_in_window.y() - kHighlightLayerRadius,
+                   kHighlightLayerRadius * 2, kHighlightLayerRadius * 2);
+}
+
+// Returns the color used for the highlight layer affordance and border.
+SkColor GetColor() {
+  return capture_mode_util::GetColorProviderForNativeTheme()->GetColor(
+      cros_tokens::kCrosSysOnSurface);
+}
+
+}  // namespace
+
+PointerHighlightLayer::PointerHighlightLayer(
+    const gfx::PointF& event_location_in_window,
+    ui::Layer* parent_layer) {
+  SetLayer(std::make_unique<ui::Layer>(ui::LAYER_TEXTURED));
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetBounds(CalculateHighlightLayerBounds(event_location_in_window));
+  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kHighlightLayerRadius));
+  layer()->SetOpacity(kHighlightLayerInitialOpacity);
+  layer()->set_delegate(this);
+  layer()->SetName("PointerHighlightLayer");
+
+  DCHECK(parent_layer);
+  parent_layer->Add(layer());
+  parent_layer->StackAtTop(layer());
+}
+
+PointerHighlightLayer::~PointerHighlightLayer() = default;
+
+void PointerHighlightLayer::OnPaintLayer(const ui::PaintContext& context) {
+  ui::PaintRecorder recorder(context, layer()->size());
+  gfx::ScopedCanvas scoped_canvas(recorder.canvas());
+  const float dsf = recorder.canvas()->UndoDeviceScaleFactor();
+  const float scaled_highlight_radius = dsf * kHighlightLayerRadius;
+  const float scaled_fills_radius = dsf * kFillsRadius;
+  const gfx::PointF scaled_highlight_center = gfx::ConvertPointToPixels(
+      capture_mode_util::GetLocalCenterPoint(layer()), dsf);
+
+  cc::PaintFlags flags;
+  const SkColor color = GetColor();
+
+  // 50% opacity.
+  flags.setColor(SkColorSetA(color, 128));
+  flags.setAntiAlias(true);
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  recorder.canvas()->DrawCircle(scaled_highlight_center,
+                                scaled_highlight_radius, flags);
+
+  flags.setColor(
+      SkColorSetA(color, DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
+                             ? 255
+                             : 255 * kLightModeBorderOpacityScaleFactor));
+  flags.setStyle(cc::PaintFlags::kStroke_Style);
+  flags.setStrokeWidth(kHighlightStrokeWidth);
+  recorder.canvas()->DrawCircle(scaled_highlight_center, scaled_fills_radius,
+                                flags);
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/pointer_highlight_layer.h b/ash/capture_mode/pointer_highlight_layer.h
new file mode 100644
index 0000000..082c4b4
--- /dev/null
+++ b/ash/capture_mode/pointer_highlight_layer.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
+#define ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
+
+#include "ui/aura/window.h"
+#include "ui/compositor/layer_delegate.h"
+#include "ui/compositor/layer_owner.h"
+#include "ui/compositor/paint_context.h"
+#include "ui/events/event.h"
+
+namespace gfx {
+class PointF;
+}  // namespace gfx
+
+namespace ash {
+
+// `PointerHighlightLayer` is a `LayerOwner` that owns a texture layer that is
+// added as a descendant of the window being recorded and on top of it (z-order)
+// such that it can be captured with it. This layer is used to highlight the
+// mouse or touch press events by painting a ring centered at the event
+// location. `PointerHighlightLayer` is owned by
+// `CaptureModeDemoToolsController` which will be created when animation starts
+// and destroyed when animation ends.
+class PointerHighlightLayer : public ui::LayerOwner, public ui::LayerDelegate {
+ public:
+  PointerHighlightLayer(const gfx::PointF& event_location_in_window,
+                        ui::Layer* parent_layer);
+  PointerHighlightLayer(const PointerHighlightLayer&) = delete;
+  PointerHighlightLayer& operator=(const PointerHighlightLayer&) = delete;
+  ~PointerHighlightLayer() override;
+
+  // ui::LayerDelegate:
+  void OnPaintLayer(const ui::PaintContext& context) override;
+  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
+                                  float new_device_scale_factor) override {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
\ No newline at end of file
diff --git a/ash/capture_mode/recording_type_menu_view.cc b/ash/capture_mode/recording_type_menu_view.cc
new file mode 100644
index 0000000..1b8985a
--- /dev/null
+++ b/ash/capture_mode/recording_type_menu_view.cc
@@ -0,0 +1,91 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/capture_mode/recording_type_menu_view.h"
+
+#include "ash/public/cpp/style/color_provider.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
+#include "ui/views/background.h"
+
+namespace ash {
+
+namespace {
+
+// The IDs of the options representing the available recording formats.
+enum RecordingTypeOption {
+  kWebM = 0,
+  kGif,
+};
+
+// The padding around the menu options.
+constexpr auto kMenuPadding = gfx::Insets::VH(12, 0);
+
+// The vertical space between the two nearest edges of the capture label widget
+// and the recording type menu widget.
+constexpr int kYOffsetFromLabelWidget = 8;
+
+constexpr int kMinimumWidth = 184;
+constexpr gfx::Size kIdealSize{kMinimumWidth, 96};
+
+constexpr gfx::RoundedCornersF kBorderRadius{12.f};
+
+// Gets the ideal size of the widget hosting the `RecordingTypeMenuView` either
+// from the preferred size of `contents_view` (if given), or the default size.
+gfx::Size GetIdealSize(views::View* contents_view) {
+  gfx::Size size =
+      contents_view ? contents_view->GetPreferredSize() : kIdealSize;
+  if (size.width() < kMinimumWidth)
+    size.set_width(kMinimumWidth);
+  return size;
+}
+
+}  // namespace
+
+RecordingTypeMenuView::RecordingTypeMenuView()
+    : CaptureModeMenuGroup(this, kMenuPadding) {
+  SetPaintToLayer();
+  SetBackground(views::CreateThemedSolidBackground(kColorAshShieldAndBase80));
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetRoundedCornerRadius(kBorderRadius);
+  layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
+  layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
+
+  AddOption(
+      &kCaptureModeVideoIcon,
+      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_LABEL_VIDEO_RECORD),
+      RecordingTypeOption::kWebM);
+  AddOption(&kCaptureGifIcon,
+            l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_LABEL_GIF_RECORD),
+            RecordingTypeOption::kGif);
+}
+
+// static
+gfx::Rect RecordingTypeMenuView::GetIdealScreenBounds(
+    const gfx::Rect& capture_label_widget_screen_bounds,
+    views::View* contents_view) {
+  const auto size = GetIdealSize(contents_view);
+  const auto bottom_center = capture_label_widget_screen_bounds.bottom_center();
+  const int y = bottom_center.y() + kYOffsetFromLabelWidget;
+  const int x = bottom_center.x() - (size.width() / 2);
+  return gfx::Rect(gfx::Point(x, y), size);
+}
+
+void RecordingTypeMenuView::OnOptionSelected(int option_id) const {}
+
+bool RecordingTypeMenuView::IsOptionChecked(int option_id) const {
+  return option_id == RecordingTypeOption::kWebM;
+}
+
+bool RecordingTypeMenuView::IsOptionEnabled(int option_id) const {
+  return true;
+}
+
+}  // namespace ash
diff --git a/ash/capture_mode/recording_type_menu_view.h b/ash/capture_mode/recording_type_menu_view.h
new file mode 100644
index 0000000..0831ccd
--- /dev/null
+++ b/ash/capture_mode/recording_type_menu_view.h
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_CAPTURE_MODE_RECORDING_TYPE_MENU_VIEW_H_
+#define ASH_CAPTURE_MODE_RECORDING_TYPE_MENU_VIEW_H_
+
+#include "ash/capture_mode/capture_mode_menu_group.h"
+
+namespace gfx {
+class Rect;
+}  // namespace gfx
+
+namespace ash {
+
+// Defines a view that will be the contents view of the recording type menu
+// widget, from which users can pick the desired recording format.
+class RecordingTypeMenuView : public CaptureModeMenuGroup,
+                              public CaptureModeMenuGroup::Delegate {
+ public:
+  RecordingTypeMenuView();
+  RecordingTypeMenuView(const RecordingTypeMenuView&) = delete;
+  RecordingTypeMenuView& operator=(const RecordingTypeMenuView&) = delete;
+  ~RecordingTypeMenuView() override = default;
+
+  // Returns the ideal bounds of the widget hosting this view, relative to the
+  // `capture_label_widget_screen_bounds` which hosts the drop down button that
+  // opens the recording type menu widget. If `contents_view` is provided, its
+  // preferred size will be used, otherwise, the default size will be used.
+  static gfx::Rect GetIdealScreenBounds(
+      const gfx::Rect& capture_label_widget_screen_bounds,
+      views::View* contents_view = nullptr);
+
+  // CaptureModeMenuGroup::Delegate:
+  void OnOptionSelected(int option_id) const override;
+  bool IsOptionChecked(int option_id) const override;
+  bool IsOptionEnabled(int option_id) const override;
+};
+
+}  // namespace ash
+
+#endif  // ASH_CAPTURE_MODE_RECORDING_TYPE_MENU_VIEW_H_
diff --git a/ash/capture_mode/video_recording_watcher.cc b/ash/capture_mode/video_recording_watcher.cc
index 7c3633d..4833f42 100644
--- a/ash/capture_mode/video_recording_watcher.cc
+++ b/ash/capture_mode/video_recording_watcher.cc
@@ -18,7 +18,6 @@
 #include "ash/projector/projector_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -29,12 +28,12 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/aura/client/cursor_shape_client.h"
-#include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/cursor/cursor.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/display/screen.h"
+#include "ui/events/types/event_type.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/point.h"
@@ -527,6 +526,8 @@
 }
 
 void VideoRecordingWatcher::OnMouseEvent(ui::MouseEvent* event) {
+  const gfx::PointF location_in_window =
+      GetEventLocationInWindow(window_being_recorded_, *event);
   switch (event->type()) {
     case ui::ET_MOUSEWHEEL:
     case ui::ET_MOUSE_CAPTURE_CHANGED:
@@ -536,17 +537,18 @@
       auto* camera_preview_view = GetCameraPreviewView();
       if (camera_preview_view)
         camera_preview_view->MaybeBlurFocus(*event);
+
+      if (demo_tools_controller_)
+        demo_tools_controller_->PerformMousePressAnimation(location_in_window);
     }
       [[fallthrough]];
     case ui::ET_MOUSE_RELEASED:
       // Pressed/released events are important, so we handle them immediately.
-      UpdateCursorOverlayNow(
-          GetEventLocationInWindow(window_being_recorded_, *event));
+      UpdateCursorOverlayNow(location_in_window);
       return;
 
     default:
-      UpdateOrThrottleCursorOverlay(
-          GetEventLocationInWindow(window_being_recorded_, *event));
+      UpdateOrThrottleCursorOverlay(location_in_window);
   }
 }
 
diff --git a/ash/capture_mode/video_recording_watcher.h b/ash/capture_mode/video_recording_watcher.h
index 2160b8f..4589ab0 100644
--- a/ash/capture_mode/video_recording_watcher.h
+++ b/ash/capture_mode/video_recording_watcher.h
@@ -149,7 +149,7 @@
 
   void SendThrottledWindowSizeChangedNowForTesting();
 
-  CaptureModeDemoToolsController* demo_tools_controller_for_testing() {
+  CaptureModeDemoToolsController* demo_tools_controller_for_testing() const {
     return demo_tools_controller_.get();
   }
 
diff --git a/ash/constants/notifier_catalogs.h b/ash/constants/notifier_catalogs.h
index fed308a1..262f10a 100644
--- a/ash/constants/notifier_catalogs.h
+++ b/ash/constants/notifier_catalogs.h
@@ -173,7 +173,11 @@
   kArcVmDataMigration = 158,
   kWebHid = 159,
   kDoNotDisturb = 160,
-  kMaxValue = kDoNotDisturb,
+  kDictationAllDlcsDownloaded = 161,
+  kDictationNoDlcsDownloaded = 162,
+  kDicationOnlyPumpkinDownloaded = 163,
+  kDictationOnlySodaDownloaded = 164,
+  kMaxValue = kDictationOnlySodaDownloaded
 };
 
 // A living catalog that registers system nudges.
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index a46cd55..51ef9c4 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -83,6 +83,69 @@
   return gfx::Image(image_skia).As1xPNGBytes();
 }
 
+NewScreencastPrecondition OnDeviceRecognitionAvailabilityToPrecondition(
+    OnDeviceRecognitionAvailability availability) {
+  NewScreencastPrecondition result;
+  switch (availability) {
+    case OnDeviceRecognitionAvailability::kAvailable:
+      result.state = NewScreencastPreconditionState::kEnabled;
+      result.reasons = {NewScreencastPreconditionReason::kEnabledBySoda};
+      return result;
+    case OnDeviceRecognitionAvailability::kSodaNotAvailable:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {NewScreencastPreconditionReason::
+                            kOnDeviceSpeechRecognitionNotSupported};
+      return result;
+    case OnDeviceRecognitionAvailability::kUserLanguageNotAvailable:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {
+          NewScreencastPreconditionReason::kUserLocaleNotSupported};
+      return result;
+
+    // We will attempt to install SODA.
+    case OnDeviceRecognitionAvailability::kSodaNotInstalled:
+    case OnDeviceRecognitionAvailability::kSodaInstalling:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {
+          NewScreencastPreconditionReason::kSodaDownloadInProgress};
+      return result;
+    case OnDeviceRecognitionAvailability::kSodaInstallationErrorUnspecified:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {
+          NewScreencastPreconditionReason::kSodaInstallationErrorUnspecified};
+      return result;
+    case OnDeviceRecognitionAvailability::kSodaInstallationErrorNeedsReboot:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {
+          NewScreencastPreconditionReason::kSodaInstallationErrorNeedsReboot};
+      return result;
+  }
+}
+
+NewScreencastPrecondition ServerBasedRecognitionAvailabilityToPrecondition(
+    ServerBasedRecognitionAvailability availability) {
+  NewScreencastPrecondition result;
+  switch (availability) {
+    case ServerBasedRecognitionAvailability::kAvailable:
+      result.state = NewScreencastPreconditionState::kEnabled;
+      result.reasons = {NewScreencastPreconditionReason::
+                            kEnabledByServerSideSpeechRecognition};
+      return result;
+    case ServerBasedRecognitionAvailability::kUserLanguageNotAvailable:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      result.reasons = {
+          NewScreencastPreconditionReason::kUserLocaleNotSupported};
+      return result;
+    case ServerBasedRecognitionAvailability::
+        kServerBasedRecognitionNotAvailable:
+      result.state = NewScreencastPreconditionState::kDisabled;
+      // TODO(b:245613717): Add a precondition reason for server based not
+      // available.
+      result.reasons = {NewScreencastPreconditionReason::kOthers};
+      return result;
+  }
+}
+
 }  // namespace
 
 ProjectorControllerImpl::ProjectorControllerImpl()
@@ -202,47 +265,18 @@
   // For development purposes on the x11 simulator, on-device speech recognition
   // and DriveFS are not supported.
   if (!ProjectorController::AreExtendedProjectorFeaturesDisabled()) {
-    switch (client_->GetSpeechRecognitionAvailability()) {
-      case SpeechRecognitionAvailability::kSodaAvailable:
-        result.state = NewScreencastPreconditionState::kEnabled;
-        result.reasons = {NewScreencastPreconditionReason::kEnabledBySoda};
-        break;
-      case SpeechRecognitionAvailability::kServerBasedRecognitionAvailable:
-        result.state = NewScreencastPreconditionState::kEnabled;
-        result.reasons = {NewScreencastPreconditionReason::
-                              kEnabledByServerSideSpeechRecognition};
-        break;
-      case SpeechRecognitionAvailability::kSodaNotAvailable:
-        result.state = NewScreencastPreconditionState::kDisabled;
-        result.reasons = {NewScreencastPreconditionReason::
-                              kOnDeviceSpeechRecognitionNotSupported};
-        return result;
-      case SpeechRecognitionAvailability::kUserLanguageNotAvailable:
-        result.state = NewScreencastPreconditionState::kDisabled;
-        result.reasons = {
-            NewScreencastPreconditionReason::kUserLocaleNotSupported};
-        return result;
-
-      // We will attempt to install SODA.
-      case SpeechRecognitionAvailability::kSodaNotInstalled:
-      case SpeechRecognitionAvailability::kSodaInstalling:
-        result.state = NewScreencastPreconditionState::kDisabled;
-        result.reasons = {
-            NewScreencastPreconditionReason::kSodaDownloadInProgress};
-        return result;
-      case SpeechRecognitionAvailability::kServerBasedRecognitionNotAvailable:
-      case SpeechRecognitionAvailability::kSodaInstallationErrorUnspecified:
-        result.state = NewScreencastPreconditionState::kDisabled;
-        result.reasons = {
-            NewScreencastPreconditionReason::kSodaInstallationErrorUnspecified};
-        return result;
-      case SpeechRecognitionAvailability::kSodaInstallationErrorNeedsReboot:
-        result.state = NewScreencastPreconditionState::kDisabled;
-        result.reasons = {
-            NewScreencastPreconditionReason::kSodaInstallationErrorNeedsReboot};
-        return result;
+    const auto availability = client_->GetSpeechRecognitionAvailability();
+    if (availability.use_on_device) {
+      result = OnDeviceRecognitionAvailabilityToPrecondition(
+          availability.on_device_availability);
+    } else {
+      result = ServerBasedRecognitionAvailabilityToPrecondition(
+          availability.server_based_availability);
     }
 
+    if (result.state != NewScreencastPreconditionState::kEnabled)
+      return result;
+
     if (!client_->IsDriveFsMounted()) {
       result.state = NewScreencastPreconditionState::kDisabled;
       result.reasons = {
@@ -456,8 +490,7 @@
   if (ProjectorController::AreExtendedProjectorFeaturesDisabled() || !client_)
     return;
 
-  DCHECK(ProjectorController::IsRecognitionAvailable(
-      client_->GetSpeechRecognitionAvailability()));
+  DCHECK(client_->GetSpeechRecognitionAvailability().IsAvailable());
 
   DCHECK(!is_speech_recognition_on_);
   client_->StartSpeechRecognition();
@@ -471,8 +504,7 @@
     return;
   }
 
-  DCHECK(ProjectorController::IsRecognitionAvailable(
-      client_->GetSpeechRecognitionAvailability()));
+  DCHECK(client_->GetSpeechRecognitionAvailability().IsAvailable());
 
   client_->StopSpeechRecognition();
   is_speech_recognition_on_ = false;
diff --git a/ash/projector/projector_controller_unittest.cc b/ash/projector/projector_controller_unittest.cc
index bc68c11..36fa5aa 100644
--- a/ash/projector/projector_controller_unittest.cc
+++ b/ash/projector/projector_controller_unittest.cc
@@ -151,9 +151,12 @@
     mock_metadata_controller_ = mock_metadata_controller.get();
     controller_->SetProjectorMetadataControllerForTest(
         std::move(mock_metadata_controller));
+
+    SpeechRecognitionAvailability availability;
+    availability.on_device_availability =
+        OnDeviceRecognitionAvailability::kAvailable;
     ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
-        .WillByDefault(
-            testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
+        .WillByDefault(testing::Return(availability));
     controller_->SetClient(&mock_client_);
   }
 
@@ -238,39 +241,45 @@
 }
 
 TEST_F(ProjectorControllerTest, OnSpeechRecognitionAvailabilityChanged) {
+  SpeechRecognitionAvailability availability;
+
+  // Soda is not available.
+  availability.use_on_device = true;
+  availability.on_device_availability =
+      OnDeviceRecognitionAvailability::kSodaNotAvailable;
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(testing::Return(availability));
+  ON_CALL(mock_client_, IsDriveFsMounted())
+      .WillByDefault(testing::Return(true));
   EXPECT_CALL(mock_client_,
               OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                   NewScreencastPreconditionState::kDisabled,
                   {NewScreencastPreconditionReason::
                        kOnDeviceSpeechRecognitionNotSupported})));
-  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
-      .WillByDefault(
-          testing::Return(SpeechRecognitionAvailability::kSodaNotAvailable));
-
   controller_->OnSpeechRecognitionAvailabilityChanged();
 
-  ON_CALL(mock_client_, IsDriveFsMounted())
-      .WillByDefault(testing::Return(true));
-
+  // Soda is available.
+  availability.on_device_availability =
+      OnDeviceRecognitionAvailability::kAvailable;
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(testing::Return(availability));
   EXPECT_CALL(mock_client_,
               OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                   NewScreencastPreconditionState::kEnabled,
                   {NewScreencastPreconditionReason::kEnabledBySoda})));
-
-  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
-      .WillByDefault(
-          testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
-
   controller_->OnSpeechRecognitionAvailabilityChanged();
 
+  // Server based available.
+  availability.use_on_device = false;
+  availability.server_based_availability =
+      ServerBasedRecognitionAvailability::kAvailable;
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(testing::Return(availability));
   EXPECT_CALL(mock_client_,
               OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                   NewScreencastPreconditionState::kEnabled,
                   {NewScreencastPreconditionReason::
                        kEnabledByServerSideSpeechRecognition})));
-  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
-      .WillByDefault(testing::Return(
-          SpeechRecognitionAvailability::kServerBasedRecognitionAvailable));
   controller_->OnSpeechRecognitionAvailabilityChanged();
 }
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index e9aa92d..70bf583 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -256,6 +256,7 @@
     "projector/projector_new_screencast_precondition.h",
     "projector/projector_session.cc",
     "projector/projector_session.h",
+    "projector/speech_recognition_availability.cc",
     "projector/speech_recognition_availability.h",
     "reauth_reason.h",
     "resize_shadow_type.h",
diff --git a/ash/public/cpp/accessibility_controller.h b/ash/public/cpp/accessibility_controller.h
index c4b5794..c09689b 100644
--- a/ash/public/cpp/accessibility_controller.h
+++ b/ash/public/cpp/accessibility_controller.h
@@ -25,6 +25,7 @@
 enum class DictationToggleSource;
 enum class DictationBubbleHintType;
 enum class DictationBubbleIconType;
+enum class DictationNotificationType;
 class SelectToSpeakEventHandlerDelegate;
 enum class SelectToSpeakState;
 
@@ -182,8 +183,8 @@
   // Shows a notification card in the message center informing the user that
   // speech recognition files have either downloaded successfully or failed.
   // Specific to the Dictation feature.
-  virtual void ShowSpeechRecognitionDownloadNotificationForDictation(
-      bool succeeded,
+  virtual void ShowNotificationForDictation(
+      DictationNotificationType type,
       const std::u16string& display_language) = 0;
 
   // Updates the Dictation UI bubble. `text` is optional to allow clients to
diff --git a/ash/public/cpp/accessibility_controller_enums.h b/ash/public/cpp/accessibility_controller_enums.h
index 5e3af97..8d645ab 100644
--- a/ash/public/cpp/accessibility_controller_enums.h
+++ b/ash/public/cpp/accessibility_controller_enums.h
@@ -224,6 +224,14 @@
   kCopy,
 };
 
+// The types of notifications that can be shown by Dictation.
+enum class DictationNotificationType {
+  kAllDlcsDownloaded,
+  kNoDlcsDownloaded,
+  kOnlySodaDownloaded,
+  kOnlyPumpkinDownloaded,
+};
+
 }  // namespace ash
 
 #endif  // ASH_PUBLIC_CPP_ACCESSIBILITY_CONTROLLER_ENUMS_H_
diff --git a/ash/public/cpp/app_list/app_list_controller.h b/ash/public/cpp/app_list/app_list_controller.h
index b956c89d..b3c7820 100644
--- a/ash/public/cpp/app_list/app_list_controller.h
+++ b/ash/public/cpp/app_list/app_list_controller.h
@@ -68,6 +68,8 @@
   // Shows the app list.
   virtual void ShowAppList(AppListShowSource source) = 0;
 
+  virtual AppListShowSource LastAppListShowSource() = 0;
+
   // Returns the app list window or nullptr if it is not visible.
   virtual aura::Window* GetWindow() = 0;
 
diff --git a/ash/public/cpp/app_list/app_list_metrics.h b/ash/public/cpp/app_list/app_list_metrics.h
index 859b3e9..7528ebf 100644
--- a/ash/public/cpp/app_list/app_list_metrics.h
+++ b/ash/public/cpp/app_list/app_list_metrics.h
@@ -61,14 +61,7 @@
   kLaunch = 0,
   kAnswerCardImpression = 1,
   kQuit = 2,
-};
-
-// Tracks whether a best match result was shown and whether it was launched at
-// the conclusion of each search session.
-enum class SearchBestMatchState {
-  kBestMatchShownAndLaunched = 0,
-  kBestMatchShownAndIgnored = 1,
-  kBestMatchNotShown = 2,
+  kMaxValue = kQuit,
 };
 
 // The type of the ChromeSearchResult. This is used for logging so do not
diff --git a/ash/public/cpp/projector/projector_client.h b/ash/public/cpp/projector/projector_client.h
index 94e1973..37a0bf2 100644
--- a/ash/public/cpp/projector/projector_client.h
+++ b/ash/public/cpp/projector/projector_client.h
@@ -16,7 +16,7 @@
 namespace ash {
 
 struct NewScreencastPrecondition;
-enum class SpeechRecognitionAvailability;
+struct SpeechRecognitionAvailability;
 
 // Creates interface to access Browser side functionalities for the
 // ProjectorControllerImpl.
diff --git a/ash/public/cpp/projector/projector_controller.cc b/ash/public/cpp/projector/projector_controller.cc
index a8a4357..3aa9d30 100644
--- a/ash/public/cpp/projector/projector_controller.cc
+++ b/ash/public/cpp/projector/projector_controller.cc
@@ -31,14 +31,6 @@
 }
 
 // static
-bool ProjectorController::IsRecognitionAvailable(
-    SpeechRecognitionAvailability availability) {
-  return availability == SpeechRecognitionAvailability::kSodaAvailable ||
-         availability ==
-             SpeechRecognitionAvailability::kServerBasedRecognitionAvailable;
-}
-
-// static
 ProjectorController* ProjectorController::Get() {
   return g_instance;
 }
diff --git a/ash/public/cpp/projector/projector_controller.h b/ash/public/cpp/projector/projector_controller.h
index 7023618..bf157af1d 100644
--- a/ash/public/cpp/projector/projector_controller.h
+++ b/ash/public/cpp/projector/projector_controller.h
@@ -15,8 +15,6 @@
 
 class ProjectorClient;
 
-enum class SpeechRecognitionAvailability;
-
 // Interface to control projector in ash.
 class ASH_PUBLIC_EXPORT ProjectorController {
  public:
@@ -25,9 +23,6 @@
   ProjectorController& operator=(const ProjectorController&) = delete;
   virtual ~ProjectorController();
 
-  static bool IsRecognitionAvailable(
-      SpeechRecognitionAvailability availability);
-
   static ProjectorController* Get();
 
   // Returns whether the extended features for projector are enabled. This is
diff --git a/ash/public/cpp/projector/speech_recognition_availability.cc b/ash/public/cpp/projector/speech_recognition_availability.cc
new file mode 100644
index 0000000..bf769551
--- /dev/null
+++ b/ash/public/cpp/projector/speech_recognition_availability.cc
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
+
+namespace ash {
+
+bool SpeechRecognitionAvailability::IsAvailable() const {
+  if (use_on_device) {
+    return on_device_availability ==
+           OnDeviceRecognitionAvailability::kAvailable;
+  }
+
+  return server_based_availability ==
+         ServerBasedRecognitionAvailability::kAvailable;
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/projector/speech_recognition_availability.h b/ash/public/cpp/projector/speech_recognition_availability.h
index 1813530..2601284 100644
--- a/ash/public/cpp/projector/speech_recognition_availability.h
+++ b/ash/public/cpp/projector/speech_recognition_availability.h
@@ -9,13 +9,10 @@
 
 namespace ash {
 
-// Enum class used to represent the availability of speech recognition.
-enum class ASH_PUBLIC_EXPORT SpeechRecognitionAvailability {
+enum class ASH_PUBLIC_EXPORT OnDeviceRecognitionAvailability : int {
   // Device does not support SODA (Speech on Device API)
-  kSodaNotAvailable,
-  // Server based feature is not supported.
-  kServerBasedRecognitionNotAvailable,
-  // User's language is not supported.
+  kSodaNotAvailable = 0,
+  // User's language is not supported by SODA.
   kUserLanguageNotAvailable,
   // SODA binary is not yet installed.
   kSodaNotInstalled,
@@ -26,9 +23,24 @@
   // SODA installation error needs reboot
   kSodaInstallationErrorNeedsReboot,
   // SODA is available to be used.
-  kSodaAvailable,
-  // Server based recognition is available.
-  kServerBasedRecognitionAvailable
+  kAvailable,
+};
+
+enum class ASH_PUBLIC_EXPORT ServerBasedRecognitionAvailability : int {
+  // Server based feature is not available.
+  kServerBasedRecognitionNotAvailable = 0,
+  // User's language is not supported by server based recognition.
+  kUserLanguageNotAvailable,
+  // Server based speech recognition is available.
+  kAvailable,
+};
+
+struct ASH_PUBLIC_EXPORT SpeechRecognitionAvailability {
+  bool use_on_device = true;
+  OnDeviceRecognitionAvailability on_device_availability;
+  ServerBasedRecognitionAvailability server_based_availability;
+
+  bool IsAvailable() const;
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/test/mock_projector_client.h b/ash/public/cpp/test/mock_projector_client.h
index be470de4..70c19ab 100644
--- a/ash/public/cpp/test/mock_projector_client.h
+++ b/ash/public/cpp/test/mock_projector_client.h
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/projector/projector_annotator_controller.h"
 #include "ash/public/cpp/projector/projector_client.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "base/files/scoped_temp_dir.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/ash/public/cpp/wallpaper/wallpaper_info.cc b/ash/public/cpp/wallpaper/wallpaper_info.cc
index e4a2213..c5eef54 100644
--- a/ash/public/cpp/wallpaper/wallpaper_info.cc
+++ b/ash/public/cpp/wallpaper/wallpaper_info.cc
@@ -60,7 +60,7 @@
 WallpaperInfo::WallpaperInfo(WallpaperInfo&& other) = default;
 WallpaperInfo& WallpaperInfo::operator=(WallpaperInfo&& other) = default;
 
-bool WallpaperInfo::operator==(const WallpaperInfo& other) const {
+bool WallpaperInfo::MatchesSelection(const WallpaperInfo& other) const {
   // |asset_id| and |location| are skipped on purpose in favor of |unit_id| as
   // online wallpapers can vary across devices due to their color mode. Other
   // wallpaper types still require location to be equal.
@@ -89,8 +89,25 @@
   }
 }
 
-bool WallpaperInfo::operator!=(const WallpaperInfo& other) const {
-  return !(*this == other);
+bool WallpaperInfo::MatchesAsset(const WallpaperInfo& other) const {
+  if (!MatchesSelection(other))
+    return false;
+
+  switch (type) {
+    case WallpaperType::kOnline:
+    case WallpaperType::kDaily:
+      return location == other.location && asset_id == other.asset_id;
+    case WallpaperType::kOnceGooglePhotos:
+    case WallpaperType::kDailyGooglePhotos:
+    case WallpaperType::kCustomized:
+    case WallpaperType::kDefault:
+    case WallpaperType::kPolicy:
+    case WallpaperType::kThirdParty:
+    case WallpaperType::kDevice:
+    case WallpaperType::kOneShot:
+    case WallpaperType::kCount:
+      return true;
+  }
 }
 
 WallpaperInfo::~WallpaperInfo() = default;
diff --git a/ash/public/cpp/wallpaper/wallpaper_info.h b/ash/public/cpp/wallpaper/wallpaper_info.h
index c4a91b8f..bc23cbf 100644
--- a/ash/public/cpp/wallpaper/wallpaper_info.h
+++ b/ash/public/cpp/wallpaper/wallpaper_info.h
@@ -37,9 +37,15 @@
   WallpaperInfo(WallpaperInfo&& other);
   WallpaperInfo& operator=(WallpaperInfo&& other);
 
-  bool operator==(const WallpaperInfo& other) const;
-
-  bool operator!=(const WallpaperInfo& other) const;
+  // MatchesAsset() takes the current wallpaper variant into account, whereas
+  // MatchesSelection() doesn't. For example if WallpaperInfo A has theme X with
+  // variant 1, and WallpaperInfo B has theme X with variant 2,
+  // MatchesSelection() will be true and MatchesAsset() will be false. Put
+  // differently, MatchesSelection() tells whether the same wallpaper has been
+  // selected, whereas MatchesAsset() tells whether the exact same wallpaper
+  // image is active.
+  bool MatchesSelection(const WallpaperInfo& other) const;
+  bool MatchesAsset(const WallpaperInfo& other) const;
 
   ~WallpaperInfo();
 
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl.cc b/ash/quick_pair/repository/fast_pair_repository_impl.cc
index 4979711..d3dff21 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl.cc
+++ b/ash/quick_pair/repository/fast_pair_repository_impl.cc
@@ -329,7 +329,8 @@
       device->metadata_id,
       base::BindOnce(&FastPairRepositoryImpl::WriteDeviceToFootprints,
                      weak_ptr_factory_.GetWeakPtr(), device->metadata_id,
-                     device->classic_address().value(), account_key));
+                     device->classic_address().value(), account_key,
+                     device->protocol));
 }
 
 bool FastPairRepositoryImpl::AssociateAccountKeyLocally(
@@ -353,6 +354,7 @@
     const std::string& hex_model_id,
     const std::string& mac_address,
     const std::vector<uint8_t>& account_key,
+    absl::optional<Protocol> device_protocol,
     DeviceMetadata* metadata,
     bool has_retryable_error) {
   if (!metadata) {
@@ -367,12 +369,14 @@
   footprints_fetcher_->AddUserFastPairInfo(
       fast_pair_info,
       base::BindOnce(&FastPairRepositoryImpl::OnWriteDeviceToFootprintsComplete,
-                     weak_ptr_factory_.GetWeakPtr(), mac_address, account_key));
+                     weak_ptr_factory_.GetWeakPtr(), mac_address, account_key,
+                     device_protocol));
 }
 
 void FastPairRepositoryImpl::OnWriteDeviceToFootprintsComplete(
     const std::string& mac_address,
     const std::vector<uint8_t>& account_key,
+    absl::optional<Protocol> device_protocol,
     bool success) {
   if (!success) {
     QP_LOG(WARNING)
@@ -601,7 +605,7 @@
         base::BindOnce(
             &FastPairRepositoryImpl::OnWriteDeviceToFootprintsComplete,
             weak_ptr_factory_.GetWeakPtr(), pending_write.mac_address,
-            account_key));
+            account_key, /*device_protocol=*/absl::nullopt));
   }
 }
 
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl.h b/ash/quick_pair/repository/fast_pair_repository_impl.h
index 7332bd6..37adcfc 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl.h
+++ b/ash/quick_pair/repository/fast_pair_repository_impl.h
@@ -124,11 +124,13 @@
   void WriteDeviceToFootprints(const std::string& hex_model_id,
                                const std::string& mac_address,
                                const std::vector<uint8_t>& account_key,
+                               absl::optional<Protocol> device_protocol,
                                DeviceMetadata* metadata,
                                bool has_retryable_error);
   void OnWriteDeviceToFootprintsComplete(
       const std::string& mac_address,
       const std::vector<uint8_t>& account_key,
+      absl::optional<Protocol> device_protocol,
       bool success);
 
   void OnCheckOptInStatus(
diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc
index 30c49bb..8f516fc 100644
--- a/ash/system/cast/tray_cast.cc
+++ b/ash/system/cast/tray_cast.cc
@@ -14,11 +14,13 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/pill_button.h"
 #include "ash/style/rounded_container.h"
 #include "ash/system/cast/cast_zero_state_view.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_detailed_view.h"
+#include "base/functional/bind.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/branding_buildflags.h"
@@ -27,6 +29,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/vector_icon_types.h"
+#include "ui/views/border.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/layout/box_layout.h"
 
@@ -132,10 +135,25 @@
   // Add a view for each receiver.
   for (auto& it : sinks_and_routes_) {
     const CastSink& sink = it.second.sink;
-    views::View* container = AddScrollListItem(
+    const CastRoute& route = it.second.route;
+    HoverHighlightView* container = AddScrollListItem(
         item_container, SinkIconTypeToIcon(sink.sink_icon_type),
         base::UTF8ToUTF16(sink.name));
     view_to_sink_map_[container] = sink.id;
+
+    // Add a stop casting button if this machine ("local source") is casting to
+    // the device. See also CastNotificationController::OnDevicesUpdated().
+    if (features::IsQsRevampEnabled() && !route.id.empty() &&
+        route.is_local_source) {
+      auto button = std::make_unique<PillButton>(
+          base::BindRepeating(&CastDetailedView::StopCasting,
+                              base::Unretained(this), route.id),
+          l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_STOP_CASTING),
+          PillButton::kPrimaryWithoutIcon);
+      container->AddRightView(
+          button.release(),
+          views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 8)));
+    }
   }
 
   // If there are no receiver views, show the zero state view.
@@ -178,6 +196,12 @@
     CloseBubble();  // Deletes `this`.
 }
 
+void CastDetailedView::StopCasting(const std::string& route_id) {
+  DCHECK(features::IsQsRevampEnabled());
+  CastConfigController::Get()->StopCasting(route_id);
+  CloseBubble();  // Deletes `this`.
+}
+
 BEGIN_METADATA(CastDetailedView, TrayDetailedView)
 END_METADATA
 
diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h
index 0bc8dac..8766c470e 100644
--- a/ash/system/cast/tray_cast.h
+++ b/ash/system/cast/tray_cast.h
@@ -54,6 +54,9 @@
   // TrayDetailedView:
   void HandleViewClicked(views::View* view) override;
 
+  // Stops casting the route identified by `route_id`.
+  void StopCasting(const std::string& route_id);
+
   // A mapping from the sink id to the receiver/activity data.
   std::map<std::string, SinkAndRoute> sinks_and_routes_;
 
diff --git a/ash/system/cast/tray_cast_unittest.cc b/ash/system/cast/tray_cast_unittest.cc
index 5c96432..d0d19c3a 100644
--- a/ash/system/cast/tray_cast_unittest.cc
+++ b/ash/system/cast/tray_cast_unittest.cc
@@ -9,10 +9,14 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/cast_config_controller.h"
+#include "ash/style/pill_button.h"
 #include "ash/system/tray/fake_detailed_view_delegate.h"
+#include "ash/system/tray/hover_highlight_view.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/views/view.h"
+#include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -39,10 +43,15 @@
   void CastToSink(const std::string& sink_id) override {
     ++cast_to_sink_count_;
   }
-  void StopCasting(const std::string& route_id) override {}
+  void StopCasting(const std::string& route_id) override {
+    ++stop_casting_count_;
+    stop_casting_route_id_ = route_id;
+  }
 
   std::vector<SinkAndRoute> sinks_and_routes_;
   size_t cast_to_sink_count_ = 0;
+  size_t stop_casting_count_ = 0;
+  std::string stop_casting_route_id_;
 };
 
 }  // namespace
@@ -100,6 +109,11 @@
     detailed_view_->OnDevicesUpdated(devices);
   }
 
+  // Adds simulated cast sinks and routes.
+  void OnDevicesUpdated(const std::vector<SinkAndRoute>& devices) {
+    detailed_view_->OnDevicesUpdated(devices);
+  }
+
   // Removes simulated cast devices.
   void ResetCastDevices() { detailed_view_->OnDevicesUpdated({}); }
 
@@ -115,9 +129,14 @@
   AddCastDevices();
   EXPECT_EQ(GetDeviceViews().size(), 2u);
 
-  // Device views are children of the rounded container.
   for (views::View* view : GetDeviceViews()) {
+    // Device views are children of the rounded container.
     EXPECT_STREQ(view->parent()->GetClassName(), "RoundedContainer");
+
+    // Device views don't have a "stop casting" button by default.
+    ASSERT_TRUE(views::IsViewClass<HoverHighlightView>(view));
+    HoverHighlightView* row = static_cast<HoverHighlightView*>(view);
+    EXPECT_FALSE(row->right_view());
   }
 }
 
@@ -147,4 +166,66 @@
   EXPECT_TRUE(GetZeroStateView());
 }
 
+TEST_F(CastDetailedViewTest, StopCastingButton) {
+  // Set up a fake sink and route, as if this Chromebook is casting to the
+  // device.
+  std::vector<SinkAndRoute> devices;
+  SinkAndRoute device;
+  device.sink.id = "fake_sink_id_1";
+  device.sink.name = "Sink Name 1";
+  device.sink.domain = "example.com";
+  device.sink.sink_icon_type = SinkIconType::kCast;
+  device.route.id = "fake_route_id_1";
+  device.route.title = "Title 1";
+  // Simulate a local source (this Chromebook).
+  device.route.is_local_source = true;
+  devices.push_back(device);
+  OnDevicesUpdated(devices);
+
+  std::vector<views::View*> views = GetDeviceViews();
+  ASSERT_EQ(views.size(), 1u);
+  ASSERT_TRUE(views::IsViewClass<HoverHighlightView>(views[0]));
+  HoverHighlightView* row = static_cast<HoverHighlightView*>(views[0]);
+  ASSERT_TRUE(row);
+
+  // The row contains a button on the right.
+  views::View* right_view = row->right_view();
+  ASSERT_TRUE(right_view);
+  EXPECT_TRUE(views::IsViewClass<PillButton>(right_view));
+  EXPECT_EQ(right_view->GetTooltipText(gfx::Point()), u"Stop casting");
+
+  // Clicking on the button stops casting.
+  LeftClickOn(right_view);
+  EXPECT_EQ(cast_config_.stop_casting_count_, 1u);
+  EXPECT_EQ(cast_config_.stop_casting_route_id_, "fake_route_id_1");
+  EXPECT_EQ(delegate_->close_bubble_call_count(), 1u);
+}
+
+TEST_F(CastDetailedViewTest, NoStopCastingButtonForNonLocalSource) {
+  // Set up a fake sink and a route as if some non-local source is casting to
+  // the device.
+  std::vector<SinkAndRoute> devices;
+  SinkAndRoute device;
+  device.sink.id = "fake_sink_id_1";
+  device.sink.name = "Sink Name 1";
+  device.sink.domain = "example.com";
+  device.sink.sink_icon_type = SinkIconType::kCast;
+  device.route.id = "fake_route_id_1";
+  device.route.title = "Title 1";
+  // Simulate a non-local source (not this Chromebook).
+  device.route.is_local_source = false;
+  devices.push_back(device);
+  OnDevicesUpdated(devices);
+
+  std::vector<views::View*> views = GetDeviceViews();
+  ASSERT_EQ(views.size(), 1u);
+  ASSERT_TRUE(views::IsViewClass<HoverHighlightView>(views[0]));
+  HoverHighlightView* row = static_cast<HoverHighlightView*>(views[0]);
+  ASSERT_TRUE(row);
+
+  // The row does not contains a right view because there is no stop casting
+  // button because the cast source is not the local machine.
+  EXPECT_FALSE(row->right_view());
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 39d7955f..533b7d0 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -1139,6 +1139,13 @@
     return;
   }
 
+  if (current_wallpaper_ && current_wallpaper_->wallpaper_info().MatchesAsset(
+                                WallpaperInfo(params))) {
+    DVLOG(1) << "Detected no change in online wallpaper";
+    std::move(callback).Run(/*success=*/true);
+    return;
+  }
+
   for (auto& observer : observers_)
     observer.OnOnlineWallpaperSet(params);
 
@@ -3060,7 +3067,7 @@
     HandleWallpaperInfoSyncedIn(account_id, synced_info);
     return;
   }
-  if (synced_info == local_info)
+  if (synced_info.MatchesSelection(local_info))
     return;
   if (synced_info.date >= local_info.date) {
     // If synced is newer or the same age, it wins.
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index de3afb6..b52f8406 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -1144,7 +1144,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info(params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 
   // Log in |kUser2|, and set another online wallpaper for |kUser1|. Verify that
   // the on-screen wallpaper doesn't change since |kUser1| is not active, but
@@ -1163,7 +1163,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info_2(new_params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info_2);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info_2));
 }
 
 TEST_F(WallpaperControllerTest, SetOnlineWallpaper) {
@@ -1196,7 +1196,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info(params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
   // Verify that wallpaper & collection metrics are logged.
   histogram_tester().ExpectBucketCount("Ash.Wallpaper.Image", kUnitId, 1);
   histogram_tester().ExpectBucketCount(
@@ -1249,7 +1249,7 @@
                                       WALLPAPER_LAYOUT_CENTER_CROPPED,
                                       WallpaperType::kPolicy,
                                       base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   EXPECT_TRUE(controller_->IsWallpaperControlledByPolicy(kAccountId1));
   // Verify the wallpaper is not updated since the user hasn't logged in.
   EXPECT_EQ(0, GetWallpaperCount());
@@ -1280,7 +1280,7 @@
   WallpaperInfo default_wallpaper_info(
       std::string(), WALLPAPER_LAYOUT_CENTER_CROPPED, WallpaperType::kDefault,
       base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
   EXPECT_FALSE(controller_->IsWallpaperControlledByPolicy(kAccountId1));
   // Verify the wallpaper is not updated since the user hasn't logged in (to
   // avoid abrupt wallpaper change in login screen).
@@ -1389,7 +1389,7 @@
         base::FilePath(kWallpaperFilesId1).Append(kFileName1).value(),
         WALLPAPER_LAYOUT_CENTER, WallpaperType::kCustomized,
         base::Time::Now().LocalMidnight());
-    EXPECT_EQ(expected_wallpaper_info, wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
   };
 
   // Set a custom wallpaper. Verify the user is not policy controlled and the
@@ -1434,7 +1434,7 @@
   WallpaperInfo expected_wallpaper_info(
       base::FilePath(kWallpaperFilesId1).Append(kFileName1).value(), layout,
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
   EXPECT_EQ(kAccountId1, client_.get_save_wallpaper_to_drive_fs_account_id());
 }
 
@@ -1459,7 +1459,7 @@
   WallpaperInfo expected_wallpaper_info_2(
       base::FilePath(kWallpaperFilesId1).Append(kFileName2).value(), layout,
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info_2);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info_2));
 }
 
 TEST_F(WallpaperControllerTest, SetThirdPartyWallpaper_PolicyWallpaper) {
@@ -1496,7 +1496,7 @@
                                       WALLPAPER_LAYOUT_CENTER_CROPPED,
                                       WallpaperType::kPolicy,
                                       base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, SetDefaultWallpaperForRegularAccount) {
@@ -1532,7 +1532,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   // The user wallpaper info has been reset to the default value.
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   SimulateSettingCustomWallpaper(kAccountId1);
   // Verify |SetDefaultWallpaper| removes the previously set custom wallpaper
@@ -1554,7 +1554,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   // The user wallpaper info has been reset to the default value.
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   SimulateSettingCustomWallpaper(kAccountId1);
   // Verify that when screen is rotated, |SetDefaultWallpaper| removes the
@@ -1576,7 +1576,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   // The user wallpaper info has been reset to the default value.
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, SetDefaultWallpaperForChildAccount) {
@@ -1650,7 +1650,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(guest_id, &wallpaper_info));
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
   ASSERT_EQ(1u, GetDecodeFilePaths().size());
   EXPECT_EQ(default_wallpaper_dir_.GetPath().Append(kGuestLargeWallpaperName),
             GetDecodeFilePaths()[0]);
@@ -1668,7 +1668,8 @@
           .value(),
       WALLPAPER_LAYOUT_CENTER_CROPPED, WallpaperType::kPolicy,
       base::Time::Now().LocalMidnight());
-  EXPECT_EQ(policy_wallpaper_info, expected_policy_wallpaper_info);
+  EXPECT_TRUE(
+      policy_wallpaper_info.MatchesSelection(expected_policy_wallpaper_info));
   EXPECT_TRUE(controller_->IsWallpaperControlledByPolicy(kAccountId1));
 
   // Finally, verifying that the guest session hasn't been affected by the new
@@ -1677,7 +1678,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(guest_id, &wallpaper_info));
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
   ASSERT_EQ(1u, GetDecodeFilePaths().size());
   EXPECT_EQ(default_wallpaper_dir_.GetPath().Append(kGuestLargeWallpaperName),
             GetDecodeFilePaths()[0]);
@@ -1726,7 +1727,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(guest_id, &wallpaper_info));
-  EXPECT_EQ(wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(default_wallpaper_info));
   ASSERT_EQ(1u, GetDecodeFilePaths().size());
   EXPECT_EQ(default_wallpaper_dir_.GetPath().Append(kGuestLargeWallpaperName),
             GetDecodeFilePaths()[0]);
@@ -1857,7 +1858,7 @@
     EXPECT_EQ(0, GetWallpaperCount());
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   }
 
   {
@@ -1876,7 +1877,7 @@
     EXPECT_EQ(0, GetWallpaperCount());
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   }
 
   {
@@ -1899,7 +1900,7 @@
     EXPECT_EQ(0, GetWallpaperCount());
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   }
 
   {
@@ -1922,7 +1923,7 @@
     EXPECT_EQ(0, GetWallpaperCount());
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   }
 
   {
@@ -1935,7 +1936,7 @@
     EXPECT_EQ(0, GetWallpaperCount());
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info, policy_wallpaper_info);
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(policy_wallpaper_info));
   }
 }
 
@@ -2174,7 +2175,7 @@
   WallpaperInfo expected_custom_wallpaper_info(
       base::FilePath(kWallpaperFilesId1).Append(kFileName1).value(), layout,
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, expected_custom_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_custom_wallpaper_info));
 
   // Now change to a different layout. Verify that the layout is updated for
   // both the current wallpaper and the saved wallpaper info.
@@ -2186,7 +2187,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   expected_custom_wallpaper_info.layout = new_layout;
-  EXPECT_EQ(wallpaper_info, expected_custom_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_custom_wallpaper_info));
 
   {
     // Now set a Google Photos wallpaper. Verify that it's set successfully and
@@ -2204,10 +2205,10 @@
     EXPECT_EQ(controller_->GetWallpaperLayout(), layout);
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info,
-              WallpaperInfo(GooglePhotosWallpaperParams(
-                  kAccountId1, "id", /*daily_refresh_enabled=*/false, layout,
-                  /*preview_mode=*/false, "dedup_key")));
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(
+        WallpaperInfo(GooglePhotosWallpaperParams(
+            kAccountId1, "id", /*daily_refresh_enabled=*/false, layout,
+            /*preview_mode=*/false, "dedup_key"))));
 
     // Now change to a different layout. Verify that the layout is updated for
     // both the current wallpaper and the saved wallpaper info.
@@ -2218,10 +2219,10 @@
     EXPECT_EQ(controller_->GetWallpaperLayout(), new_layout);
     EXPECT_TRUE(
         pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-    EXPECT_EQ(wallpaper_info,
-              WallpaperInfo(GooglePhotosWallpaperParams(
-                  kAccountId1, "id", /*daily_refresh_enabled=*/false,
-                  new_layout, /*preview_mode=*/false, "dedup_key")));
+    EXPECT_TRUE(wallpaper_info.MatchesSelection(
+        WallpaperInfo(GooglePhotosWallpaperParams(
+            kAccountId1, "id", /*daily_refresh_enabled=*/false, new_layout,
+            /*preview_mode=*/false, "dedup_key"))));
   }
 
   // Now set an online wallpaper. Verify that it's set successfully and the
@@ -2242,7 +2243,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_online_wallpaper_info(params);
-  EXPECT_EQ(wallpaper_info, expected_online_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_online_wallpaper_info));
 
   // Now change the layout of the online wallpaper. Verify that it's a no-op.
   ClearWallpaperCount();
@@ -2254,7 +2255,7 @@
   // The saved wallpaper info is not updated.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-  EXPECT_EQ(wallpaper_info, expected_online_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_online_wallpaper_info));
 }
 
 // Tests that if a user who has a custom wallpaper is removed from the device,
@@ -2609,7 +2610,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -2635,7 +2636,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now enter overview mode. Verify the wallpaper changes back to the default,
   // the user wallpaper info remains unchanged, and enters overview mode
@@ -2647,7 +2648,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
 }
 
@@ -2665,7 +2666,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -2691,7 +2692,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now start window cycle. Verify the wallpaper changes back to the default,
   // the user wallpaper info remains unchanged, and enters window cycle.
@@ -2703,7 +2704,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   EXPECT_TRUE(Shell::Get()->window_cycle_controller()->IsCycling());
 }
 
@@ -2722,7 +2723,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -2748,7 +2749,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now switch to another user. Verify the wallpaper changes back to the
   // default and the user wallpaper remains unchanged.
@@ -2762,7 +2763,7 @@
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId2, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, ConfirmPreviewWallpaper) {
@@ -2779,7 +2780,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -2802,7 +2803,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   histogram_tester().ExpectTotalCount("Ash.Wallpaper.Preview.Show", 1);
 
   // Now confirm the preview wallpaper, verify that there's no wallpaper change
@@ -2820,7 +2821,7 @@
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, custom_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(custom_wallpaper_info));
 
   // Set an empty online wallpaper for the user, verify it fails.
   ClearWallpaperCount();
@@ -2860,7 +2861,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, custom_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(custom_wallpaper_info));
 
   // Now confirm the preview wallpaper, verify that there's no wallpaper change
   // because the wallpaper is already shown.
@@ -2881,7 +2882,7 @@
       std::vector<OnlineWallpaperVariant>()));
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, online_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(online_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, CancelPreviewWallpaper) {
@@ -2898,7 +2899,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -2921,7 +2922,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now cancel the preview. Verify the wallpaper changes back to the default
   // and the user wallpaper info remains unchanged.
@@ -2931,7 +2932,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now set an online wallpaper for the user and enable preview. Verify that
   // the wallpaper is changed to the expected color.
@@ -2952,7 +2953,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now cancel the preview. Verify the wallpaper changes back to the default
   // and the user wallpaper info remains unchanged.
@@ -2962,7 +2963,7 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(online_wallpaper_color, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, WallpaperSyncedDuringPreview) {
@@ -2979,7 +2980,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -3002,7 +3003,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Now set another custom wallpaper for the user and disable preview (this
   // happens if a custom wallpaper set on another device is being synced).
@@ -3025,7 +3026,8 @@
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_custom_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_custom_wallpaper_info));
 
   // Now cancel the preview. Verify the synced custom wallpaper is shown instead
   // of the initial default wallpaper, and the user wallpaper info is still
@@ -3037,7 +3039,8 @@
   EXPECT_EQ(synced_custom_wallpaper_color, GetWallpaperColor());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_custom_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_custom_wallpaper_info));
 
   // Repeat the above steps for online wallpapers: set a online wallpaper for
   // the user and enable preview. Verify that the wallpaper is changed to the
@@ -3058,7 +3061,8 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_custom_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_custom_wallpaper_info));
 
   // Now set another online wallpaper for the user and disable preview. Verify
   // there's no wallpaper change since preview mode shouldn't be interrupted.
@@ -3088,7 +3092,8 @@
           std::vector<OnlineWallpaperVariant>()));
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_online_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_online_wallpaper_info));
 
   // Now cancel the preview. Verify the synced online wallpaper is shown instead
   // of the previous custom wallpaper, and the user wallpaper info is still
@@ -3100,7 +3105,8 @@
   EXPECT_EQ(synced_online_wallpaper_color, GetWallpaperColor());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_online_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_online_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerTest, AddFirstWallpaperAnimationEndCallback) {
@@ -3161,7 +3167,7 @@
   WallpaperInfo wallpaper_info;
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-  EXPECT_EQ(expected_wallpaper_info, wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 
   // Show a one-shot wallpaper. Verify it is shown successfully.
   ClearWallpaperCount();
@@ -3190,7 +3196,7 @@
   // can be replaced by the user wallpaper.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-  EXPECT_EQ(expected_wallpaper_info, wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
   ClearWallpaperCount();
   controller_->ShowUserWallpaper(kAccountId1);
   RunAllTasksUntilIdle();
@@ -3407,7 +3413,7 @@
   WallpaperInfo expected_wallpaper_info(
       base::FilePath(kWallpaperFilesId1).Append(kFileName1).value(), layout,
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
   EXPECT_EQ(kAccountId1, client_.get_save_wallpaper_to_drive_fs_account_id());
 
   // Now set another custom wallpaper for |kUser1|. Verify that the on-screen
@@ -3426,7 +3432,7 @@
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 
   // Verify the updated wallpaper is shown after |kUser1| becomes active again.
   SimulateUserLogin(kAccountId1);
@@ -3475,7 +3481,7 @@
   SimulateUserLogin(kAccountId1);
   WallpaperInfo info;
   ASSERT_TRUE(pref_manager_->GetSyncedWallpaperInfo(kAccountId1, &info));
-  EXPECT_EQ(expected_info, info);
+  EXPECT_TRUE(info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperControllerTest, MigrateWallpaperInfoCustomized) {
@@ -3484,7 +3490,7 @@
   SimulateUserLogin(kAccountId1);
   WallpaperInfo info;
   ASSERT_TRUE(pref_manager_->GetSyncedWallpaperInfo(kAccountId1, &info));
-  EXPECT_EQ(expected_info, info);
+  EXPECT_TRUE(info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperControllerTest, MigrateWallpaperInfoDaily) {
@@ -3498,7 +3504,7 @@
   SimulateUserLogin(kAccountId1);
   WallpaperInfo info;
   ASSERT_TRUE(pref_manager_->GetSyncedWallpaperInfo(kAccountId1, &info));
-  EXPECT_EQ(expected_info, info);
+  EXPECT_TRUE(info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperControllerTest,
@@ -3523,7 +3529,7 @@
   WallpaperInfo info;
   ASSERT_TRUE(pref_manager_->GetSyncedWallpaperInfo(kAccountId1, &info));
   // Synced info should be the same if local is the same age.
-  EXPECT_EQ(synced_info, info);
+  EXPECT_TRUE(synced_info.MatchesSelection(info));
 }
 
 TEST_F(WallpaperControllerTest,
@@ -3911,7 +3917,7 @@
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual);
   // Type should be `WallpaperType::kDaily` now, and collection_id should be
   // updated.
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesSelection(expected));
   EXPECT_EQ(collection_id,
             controller_->GetDailyRefreshCollectionId(kAccountId1));
 
@@ -3941,7 +3947,7 @@
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual);
   // Type should be `WallpaperType::kOnline` now, and collection_id should be
   // `WallpaperType::EMPTY`.
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesSelection(expected));
   EXPECT_EQ(std::string(),
             controller_->GetDailyRefreshCollectionId(kAccountId1));
 }
@@ -3962,7 +3968,7 @@
 
   WallpaperInfo actual;
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual);
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesSelection(expected));
   EXPECT_EQ(std::string(),
             controller_->GetDailyRefreshCollectionId(kAccountId1));
 }
@@ -3983,6 +3989,7 @@
                             WALLPAPER_LAYOUT_CENTER_CROPPED,
                             /*preview_mode=*/false, /*from_user=*/true,
                             /*daily_refresh_enabled=*/false, kUnitId, variants);
+  // Use dark mode wallpaper initially.
   controller_->SetOnlineWallpaper(
       params, base::BindLambdaForTesting([&run_loop](bool success) {
         EXPECT_TRUE(success);
@@ -3992,22 +3999,54 @@
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kOnline);
 
-  pref_manager_->SetUserWallpaperInfo(kAccountId1, WallpaperInfo(params));
+  // Change to light mode.
   Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
-      prefs::kDarkModeEnabled, true);
-  controller_->OnColorModeChanged(true);
+      prefs::kDarkModeEnabled, false);
+  controller_->OnColorModeChanged(false);
   RunAllTasksUntilIdle();
   EXPECT_EQ(2, GetWallpaperCount());
-
   WallpaperInfo expected = WallpaperInfo(OnlineWallpaperParams(
-      kAccountId1, kAssetId, GURL(kDummyUrl),
+      kAccountId1, kAssetId2, GURL(kDummyUrl2),
       TestWallpaperControllerClient::kDummyCollectionId,
       WALLPAPER_LAYOUT_CENTER_CROPPED, /*preview_mode=*/false,
       /*from_user=*/true,
       /*daily_refresh_enabled=*/false, kUnitId, variants));
   WallpaperInfo actual;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual));
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesAsset(expected));
+}
+
+TEST_F(WallpaperControllerTest,
+       DoesNotUpdateWallpaperOnColorModeChangedWithNoVariants) {
+  SimulateUserLogin(kAccountId1);
+
+  auto run_loop = std::make_unique<base::RunLoop>();
+  ClearWallpaperCount();
+  std::vector<OnlineWallpaperVariant> variants;
+  variants.emplace_back(kAssetId, GURL(kDummyUrl),
+                        backdrop::Image::IMAGE_TYPE_UNKNOWN);
+  const OnlineWallpaperParams& params =
+      OnlineWallpaperParams(kAccountId1, kAssetId, GURL(kDummyUrl),
+                            TestWallpaperControllerClient::kDummyCollectionId,
+                            WALLPAPER_LAYOUT_CENTER_CROPPED,
+                            /*preview_mode=*/false, /*from_user=*/true,
+                            /*daily_refresh_enabled=*/false, kUnitId, variants);
+  controller_->SetOnlineWallpaper(
+      params, base::BindLambdaForTesting([&run_loop](bool success) {
+        EXPECT_TRUE(success);
+        run_loop->Quit();
+      }));
+  run_loop->Run();
+  EXPECT_EQ(1, GetWallpaperCount());
+
+  // Toggles color mode a couple times. Wallpaper count shouldn't change.
+  Shell::Get()->dark_light_mode_controller()->ToggleColorMode();
+  RunAllTasksUntilIdle();
+  EXPECT_EQ(1, GetWallpaperCount());
+
+  Shell::Get()->dark_light_mode_controller()->ToggleColorMode();
+  RunAllTasksUntilIdle();
+  EXPECT_EQ(1, GetWallpaperCount());
 }
 
 TEST_F(WallpaperControllerTest,
@@ -4049,7 +4088,7 @@
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDaily);
 
   // Attempt a system's color mode change.
-  controller_->OnColorModeChanged(true);
+  Shell::Get()->dark_light_mode_controller()->ToggleColorMode();
   RunAllTasksUntilIdle();
   EXPECT_EQ(2, GetWallpaperCount());
   // Expect the refresh timer doesn't reset.
@@ -4059,7 +4098,7 @@
 
   WallpaperInfo actual;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual));
-  EXPECT_EQ(info, actual);
+  EXPECT_TRUE(actual.MatchesSelection(info));
 }
 
 TEST_F(WallpaperControllerTest,
@@ -4083,7 +4122,7 @@
   WallpaperInfo expected = WallpaperInfo(params);
   WallpaperInfo actual;
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual);
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesSelection(expected));
 }
 
 TEST_F(WallpaperControllerTest, SetOnlineWallpaperIfExists) {
@@ -4124,7 +4163,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info = WallpaperInfo(params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 
   // Change the on-screen wallpaper to a different one. (Otherwise the
   // subsequent calls will be no-op since we intentionally prevent reloading the
@@ -4193,7 +4232,7 @@
 
   WallpaperInfo actual_info;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual_info));
-  EXPECT_EQ(synced_info, actual_info);
+  EXPECT_TRUE(actual_info.MatchesSelection(synced_info));
   // Verify the wallpaper is set.
   EXPECT_EQ(1, GetWallpaperCount());
 }
@@ -4232,8 +4271,8 @@
 
   WallpaperInfo actual_info;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual_info));
-  EXPECT_EQ(local_info, synced_info);
-  EXPECT_EQ(local_info, actual_info);
+  EXPECT_TRUE(local_info.MatchesSelection(synced_info));
+  EXPECT_TRUE(local_info.MatchesSelection(actual_info));
   // Verify the wallpaper is not set again.
   EXPECT_EQ(0, GetWallpaperCount());
 }
@@ -4360,7 +4399,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info(params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest,
@@ -4403,7 +4442,7 @@
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info(online_params);
-  EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
+  EXPECT_TRUE(wallpaper_info.MatchesSelection(expected_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest,
@@ -4555,7 +4594,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -4581,7 +4620,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   histogram_tester().ExpectTotalCount("Ash.Wallpaper.Preview.Show", 1);
 
   // Now confirm the preview wallpaper, verify that there's no wallpaper
@@ -4599,7 +4638,8 @@
                                              base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, google_photos_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(google_photos_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest, CancelPreviewWallpaper) {
@@ -4616,7 +4656,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -4641,7 +4681,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   histogram_tester().ExpectTotalCount("Ash.Wallpaper.Preview.Show", 1);
 
   // Now cancel the preview. Verify the wallpaper changes back to the default
@@ -4651,7 +4691,7 @@
   RunAllTasksUntilIdle();
   EXPECT_EQ(GetWallpaperCount(), 1);
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest,
@@ -4669,7 +4709,7 @@
       base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
 
   // Simulate opening the wallpaper picker window.
   std::unique_ptr<aura::Window> wallpaper_picker_window(
@@ -4695,7 +4735,7 @@
   // Verify that the user wallpaper info remains unchanged during the preview.
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
+  EXPECT_TRUE(user_wallpaper_info.MatchesSelection(default_wallpaper_info));
   histogram_tester().ExpectTotalCount("Ash.Wallpaper.Preview.Show", 1);
 
   // Now set a custom wallpaper for the user and disable preview (this happens
@@ -4719,7 +4759,8 @@
       WallpaperType::kCustomized, base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_custom_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_custom_wallpaper_info));
 
   // Now cancel the preview. Verify the synced custom wallpaper is shown
   // instead of the initial default wallpaper, and the user wallpaper info is
@@ -4731,7 +4772,8 @@
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kCustomized);
   EXPECT_TRUE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &user_wallpaper_info));
-  EXPECT_EQ(user_wallpaper_info, synced_custom_wallpaper_info);
+  EXPECT_TRUE(
+      user_wallpaper_info.MatchesSelection(synced_custom_wallpaper_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest,
@@ -4836,7 +4878,7 @@
   WallpaperInfo current_info;
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &current_info);
 
-  EXPECT_EQ(online_info, current_info);
+  EXPECT_TRUE(online_info.MatchesSelection(current_info));
 }
 
 TEST_F(WallpaperControllerGooglePhotosWallpaperTest,
@@ -4928,7 +4970,7 @@
   pref_manager_->GetUserWallpaperInfo(kAccountId1, &actual);
   // Type should be `WallpaperType::kDailyGooglePhotos` now, and collection_id
   // should be updated.
-  EXPECT_EQ(expected, actual);
+  EXPECT_TRUE(actual.MatchesSelection(expected));
   EXPECT_EQ(album_id,
             controller_->GetGooglePhotosDailyRefreshAlbumId(kAccountId1));
   Time run_time =
diff --git a/ash/wallpaper/wallpaper_pref_manager_unittest.cc b/ash/wallpaper/wallpaper_pref_manager_unittest.cc
index c27a726..17d8f07 100644
--- a/ash/wallpaper/wallpaper_pref_manager_unittest.cc
+++ b/ash/wallpaper/wallpaper_pref_manager_unittest.cc
@@ -198,7 +198,7 @@
 
   WallpaperInfo actual_info;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(account_id_1, &actual_info));
-  EXPECT_EQ(expected_info, actual_info);
+  EXPECT_TRUE(actual_info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperPrefManagerTest, GetWallpaperInfo_Ephemeral) {
@@ -208,7 +208,7 @@
 
   WallpaperInfo actual_info;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(account_id_1, &actual_info));
-  EXPECT_EQ(expected_info, actual_info);
+  EXPECT_TRUE(actual_info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperPrefManagerTest, GetWallpaperInfoNothingToGet_Normal) {
@@ -231,7 +231,7 @@
   WallpaperInfo actual_info;
   EXPECT_TRUE(pref_manager_->GetUserWallpaperInfo(
       account_id_1, /*is_ephemeral=*/true, &actual_info));
-  EXPECT_EQ(expected_info, actual_info);
+  EXPECT_TRUE(actual_info.MatchesSelection(expected_info));
 }
 
 TEST_F(WallpaperPrefManagerTest, SetWallpaperInfo_EphemeralDoesNotChangeLocal) {
diff --git a/ash/webui/personalization_app/resources/css/common.css b/ash/webui/personalization_app/resources/css/common.css
index 5a90693..04084a7 100644
--- a/ash/webui/personalization_app/resources/css/common.css
+++ b/ash/webui/personalization_app/resources/css/common.css
@@ -345,25 +345,6 @@
   cursor: pointer;
 }
 
-paper-tooltip::part(tooltip) {
-  align-items: flex-end;
-  display: flex;
-  flex-direction: row;
-  height: 18px;
-  padding: 3px 8px;
-}
-
-paper-tooltip {
-  --paper-tooltip-background: var(--cros-tooltip-background-color);
-  --paper-tooltip-text-color: var(--cros-tooltip-label-color);
-}
-
-paper-tooltip span {
-  align-items: center;
-  display: flex;
-  font: var(--cros-body-2-font);
-}
-
 :host-context([dir=rtl]) iron-icon[icon='cr:chevron-right'] {
   transform: scaleX(-1);
 }
diff --git a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.html b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.html
index 023a595e..5b4b01ab 100644
--- a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.html
+++ b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.html
@@ -15,13 +15,31 @@
     height: 100%;
   }
 
+  :host([main-page]) #container.zero-state-disabled {
+    grid-template-areas:
+      '. slot          .'
+      '. image         .'
+      '. .             .'
+      '. collage       .'
+      '. mainpage-desc .'
+      '. .             .';
+    grid-template-columns: 20px minmax(0,1fr) 20px;
+    grid-template-rows: auto minmax(158px, 220px) 20px 130px auto 18px;
+  }
+
   :host([main-page]) #container.pre-ui-change {
+    grid-template-areas:
+      '. slot           slot    slot    .'
+      '. image          image   image   .'
+      '. .              .       .       .'
+      '. message        message message .'
+      '. mainpage-desc  .       collage .'
+      '. .              .       .       .';
     grid-template-columns: 20px minmax(0,1fr) 16px 106px 20px;
     grid-template-rows: auto minmax(158px, 220px) 20px 106px auto 24px;
   }
 
-  :host([main-page]) #container.zero-state-disabled {
-    grid-template-columns: 20px minmax(0,1fr) 16px 106px 20px;
+  :host([main-page]) #container.pre-ui-change.zero-state-disabled {
     grid-template-rows: auto minmax(158px, 220px) 20px auto 106px 24px;
   }
 
@@ -152,6 +170,10 @@
     justify-content: center;
   }
 
+  :host([main-page]) .album-info-mainpage:not(.pre-ui-change) span {
+    margin-inline: auto;
+  }
+
   #imageContainer,
   #imagePlaceholder {
     grid-area: image;
@@ -177,11 +199,21 @@
   :host([main-page]) #albumTitle {
     color: var(--cros-text-color-primary);
     font: var(--cros-display-7-font);
+    margin-top: 16px;
+  }
+
+  :host([main-page]) #albumTitle.pre-ui-change {
+    margin-top: 4px;
   }
 
   :host([main-page]) #albumDescription {
     color: var(--cros-text-color-secondary);
     font: var(--cros-body-2-font);
+    margin-top: 2px;
+  }
+
+  :host([main-page]) #albumDescription.pre-ui-change {
+    margin-top: 4px;
   }
 
   :host([main-page]) #collageContainer,
@@ -241,11 +273,71 @@
     width: 20px;
   }
 
+  :host(:not([main-page])) #thumbnailContainer,
+  :host(:not([main-page])) #thumbnailPlaceholder {
+    display: none;
+  }
+
+  :host([main-page]) #thumbnailContainer,
+  :host([main-page]) #thumbnailPlaceholder {
+    display: grid;
+    grid-area: collage;
+    justify-self: center;
+    max-width: 360px;
+    min-width: 252px;
+    overflow: hidden;
+    width: 100%;
+  }
+
+  .thumbnail-item {
+    height: 100%;
+    overflow: hidden;
+    width: 100%;
+  }
+
+  .thumbnail-item img {
+    height: 100%;
+    object-fit: fill;
+    width: 100%;
+  }
+
+  #thumbnailContainer.thumbnail-1 .thumbnail-item {
+    border-radius: 60px;
+  }
+
+  #thumbnailContainer.thumbnail-2 {
+    column-gap: 12px;
+    grid-template-columns: 130px minmax(0,1fr);
+  }
+
+  #thumbnailContainer.thumbnail-2 .thumbnail-item:first-of-type {
+    clip-path: url(#squiggleClip);
+  }
+
+  #thumbnailContainer.thumbnail-2 .thumbnail-item:last-of-type {
+    border-radius: 60px;
+  }
+
+  #thumbnailContainer.thumbnail-3 {
+    column-gap: 8px;
+    grid-template-columns: minmax(0,1fr) 32px 32px;
+  }
+
+  #thumbnailContainer.thumbnail-3 .thumbnail-item:first-of-type {
+    border-radius: 60px;
+  }
+
+  #thumbnailContainer.thumbnail-3 .thumbnail-item:last-of-type img,
+  #thumbnailContainer.thumbnail-3 .thumbnail-item:nth-last-of-type(2) img {
+    border-radius: 16px;
+    object-fit: none;
+  }
 </style>
 <div class$="[[getPreviewContainerClass_(ambientModeEnabled_, loading_)]]" id="container">
   <slot></slot>
   <template is="dom-if" if="[[loading_]]">
     <div id="imagePlaceholder" class="placeholder"></div>
+    <div id="thumbnailPlaceholder" class="placeholder"></div>
     <div id="textPlaceholder" class="preview-text-placeholder album-info-mainpage album-info-subpage">
       <div class="placeholder currently-set-text"></div>
       <div class="placeholder"></div>
@@ -322,38 +414,76 @@
                 is-google-photos>
           </template>
         </div>
-        <h3 id="textContainer" class="preview-text-container album-info-mainpage album-info-subpage"
-            aria-label$="[[getPreviewTextAriaLabel_(firstPreviewAlbum_, topicSource_, previewAlbums_)]]">
-          <span id="currentlySet" class="currently-set-text" aria-hidden="true">
-            $i18n{currentlySet}
-          </span>
-          <span id="albumTitle" aria-hidden="true">
-            [[getAlbumTitle_(firstPreviewAlbum_)]]
-          </span>
-          <paper-tooltip id="albumTitleTooltip" for="albumTitle" aria-hidden="true">
-            <span>[[getAlbumTitle_(firstPreviewAlbum_)]]</span>
-          </paper-tooltip>
-          <span id="albumDescription" aria-hidden="true">
-            [[getAlbumDescription_(topicSource_, previewAlbums_)]]
-          </span>
-        </h3>
-        <div id="buttonContainer" hidden$="[[!isScreenSaverPreviewEnabled_()]]">
-          <cr-button class$="[[getScreenSaverPreviewClass_(ambientUiVisibility_)]]" on-click="startScreenSaverPreview_">
-            <iron-icon icon="personalization:fullscreen" hidden$="[[screenSaverPreviewActive_]]"></iron-icon>
-            <paper-spinner-lite active class="spinner" hidden$="[[!screenSaverPreviewActive_]]"></paper-spinner-lite>
-            <div class="text">[[getScreenSaverPreviewText_(ambientUiVisibility_)]]</div>
-          </cr-button>
-        </div>
-        <div aria-hidden="true"
-            class$="[[getCollageContainerClass_(collageImages_)]]"
-            on-click="onClickPhotoCollage_"
-            on-keypress="onClickPhotoCollage_"
-            id="collageContainer">
-          <template is="dom-repeat" items="[[collageImages_]]">
-            <img class="collage-item" is="cr-auto-img"
-                auto-src="[[item.url]]" is-google-photos>
-          </template>
-        </div>
+        <template is="dom-if" if="[[isAmbientSubpageUiChangeEnabled_()]]">
+          <div id="thumbnailContainer" aria-hidden="true"
+              class$="[[getThumbnailContainerClass_(collageImages_)]]"
+              on-click="onClickPhotoCollage_"
+              on-keypress="onClickPhotoCollage_">
+            <template is="dom-repeat" items="[[collageImages_]]">
+              <div class="thumbnail-item">
+                <img is="cr-auto-img" auto-src="[[item.url]]" is-google-photos>
+              </div>
+            </template>
+          </div>
+          <!-- Use inline svg in order to reference the clip path by url() -->
+          <svg width="0" height="0" viewBox="0 0 130 130" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <defs>
+              <clipPath id="squiggleClip">
+                <path d="M 1.4375 72.371094 C -0.332031 69.402344 -0.476562 65.738281 1.054688 62.640625 L 3.210938 58.28125 C 4.125 56.429688 4.457031 54.339844 4.15625 52.296875 L 3.457031 47.484375 C 2.957031 44.066406 4.226562 40.625 6.824219 38.347656 L 10.484375 35.148438 C 12.039062 33.785156 13.144531 31.984375 13.652344 29.980469 L 14.84375 25.265625 C 15.691406 21.914062 18.179688 19.222656 21.453125 18.113281 L 26.058594 16.554688 C 28.015625 15.894531 29.726562 14.652344 30.960938 12.996094 L 33.867188 9.097656 C 35.929688 6.324219 39.261719 4.789062 42.710938 5.019531 L 47.5625 5.339844 C 49.625 5.480469 51.679688 4.984375 53.453125 3.925781 L 57.628906 1.4375 C 60.597656 -0.332031 64.261719 -0.476562 67.359375 1.054688 L 71.71875 3.210938 C 73.570312 4.125 75.660156 4.457031 77.703125 4.15625 L 82.515625 3.457031 C 85.933594 2.957031 89.375 4.226562 91.652344 6.824219 L 94.851562 10.484375 C 96.214844 12.039062 98.015625 13.144531 100.019531 13.652344 L 104.734375 14.84375 C 108.085938 15.691406 110.777344 18.179688 111.886719 21.453125 L 113.445312 26.058594 C 114.105469 28.015625 115.347656 29.726562 117.003906 30.960938 L 120.902344 33.867188 C 123.675781 35.929688 125.210938 39.261719 124.980469 42.710938 L 124.660156 47.5625 C 124.519531 49.625 125.015625 51.679688 126.074219 53.453125 L 128.5625 57.628906 C 130.332031 60.597656 130.476562 64.261719 128.945312 67.359375 L 126.792969 71.71875 C 125.875 73.570312 125.542969 75.660156 125.84375 77.703125 L 126.542969 82.515625 C 127.042969 85.933594 125.773438 89.375 123.175781 91.652344 L 119.515625 94.851562 C 117.960938 96.214844 116.855469 98.015625 116.347656 100.019531 L 115.15625 104.734375 C 114.308594 108.085938 111.820312 110.777344 108.546875 111.886719 L 103.941406 113.445312 C 101.984375 114.105469 100.273438 115.347656 99.039062 117.003906 L 96.132812 120.902344 C 94.070312 123.675781 90.738281 125.210938 87.289062 124.980469 L 82.4375 124.660156 C 80.375 124.519531 78.320312 125.015625 76.546875 126.074219 L 72.371094 128.5625 C 69.402344 130.332031 65.738281 130.476562 62.640625 128.945312 L 58.28125 126.792969 C 56.429688 125.875 54.339844 125.542969 52.296875 125.84375 L 47.484375 126.542969 C 44.066406 127.042969 40.625 125.773438 38.347656 123.175781 L 35.148438 119.515625 C 33.785156 117.960938 31.984375 116.855469 29.980469 116.347656 L 25.265625 115.15625 C 21.914062 114.308594 19.222656 111.820312 18.113281 108.546875 L 16.554688 103.941406 C 15.894531 101.984375 14.652344 100.273438 12.996094 99.039062 L 9.097656 96.132812 C 6.324219 94.070312 4.789062 90.738281 5.019531 87.289062 L 5.339844 82.4375 C 5.480469 80.375 4.984375 78.320312 3.925781 76.546875 Z M 1.4375 72.371094 "></path>
+              </clipPath>
+            </defs>
+          </svg>
+          <div id="buttonContainer" hidden$="[[!isScreenSaverPreviewEnabled_()]]">
+            <cr-button class$="[[getScreenSaverPreviewClass_(ambientUiVisibility_)]]" on-click="startScreenSaverPreview_">
+              <iron-icon icon="personalization:fullscreen" hidden$="[[screenSaverPreviewActive_]]"></iron-icon>
+              <paper-spinner-lite active class="spinner" hidden$="[[!screenSaverPreviewActive_]]"></paper-spinner-lite>
+              <div class="text">[[getScreenSaverPreviewText_(ambientUiVisibility_)]]</div>
+            </cr-button>
+          </div>
+          <h3 id="textContainer" class="preview-text-container album-info-mainpage album-info-subpage"
+              aria-label$="[[getPreviewTextAriaLabel_(firstPreviewAlbum_, topicSource_, previewAlbums_)]]">
+            <span id="currentlySet" class="currently-set-text" aria-hidden="true">
+              $i18n{currentlySet}
+            </span>
+            <span id="albumTitle" aria-hidden="true" title="[[getAlbumTitle_(firstPreviewAlbum_)]]">
+              [[getAlbumTitle_(firstPreviewAlbum_)]]
+            </span>
+            <span id="albumDescription" aria-hidden="true">
+              [[getAlbumDescription_(topicSource_, previewAlbums_)]]
+            </span>
+          </h3>
+        </template>
+        <template is="dom-if" if="[[!isAmbientSubpageUiChangeEnabled_()]]">
+          <h3 id="textContainer" class="preview-text-container album-info-mainpage album-info-subpage pre-ui-change"
+              aria-label$="[[getPreviewTextAriaLabel_(firstPreviewAlbum_, topicSource_, previewAlbums_)]]">
+            <span id="currentlySet" class="currently-set-text" aria-hidden="true">
+              $i18n{currentlySet}
+            </span>
+            <span id="albumTitle" aria-hidden="true" title="[[getAlbumTitle_(firstPreviewAlbum_)]]">
+              [[getAlbumTitle_(firstPreviewAlbum_)]]
+            </span>
+            <span id="albumDescription" aria-hidden="true">
+              [[getAlbumDescription_(topicSource_, previewAlbums_)]]
+            </span>
+          </h3>
+          <div id="buttonContainer" hidden$="[[!isScreenSaverPreviewEnabled_()]]">
+            <cr-button class$="[[getScreenSaverPreviewClass_(ambientUiVisibility_)]]" on-click="startScreenSaverPreview_">
+              <iron-icon icon="personalization:fullscreen" hidden$="[[screenSaverPreviewActive_]]"></iron-icon>
+              <paper-spinner-lite active class="spinner" hidden$="[[!screenSaverPreviewActive_]]"></paper-spinner-lite>
+              <div class="text">[[getScreenSaverPreviewText_(ambientUiVisibility_)]]</div>
+            </cr-button>
+          </div>
+          <div aria-hidden="true"
+              class$="[[getCollageContainerClass_(collageImages_)]]"
+              on-click="onClickPhotoCollage_"
+              on-keypress="onClickPhotoCollage_"
+              id="collageContainer">
+            <template is="dom-repeat" items="[[collageImages_]]">
+              <img class="collage-item" is="cr-auto-img"
+                  auto-src="[[item.url]]" is-google-photos>
+            </template>
+          </div>
+        </template>
       </template>
     </template>
   </template>
diff --git a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.ts b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.ts
index 9fe8030..e8a7a54 100644
--- a/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.ts
+++ b/ash/webui/personalization_app/resources/js/ambient/ambient_preview_element.ts
@@ -10,7 +10,6 @@
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
-import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
 import './ambient_zero_state_svg_element.js';
 import '../../css/common.css.js';
 import '../../css/cros_button_style.css.js';
@@ -207,21 +206,26 @@
    *   - if ||googlePhotosAlbumsPreviews_| is empty:
    *        - if |previewAlbums_| contains fewer than 4 albums, return one of
    *        their previews; otherwise return the first 4.
+   *
+   * If isAmbientSubpageUIChangeEnabled flag is on, max number of collage image
+   * will be 3 instead of 4.
    */
   private computeCollageImages_(): Url[] {
+    const maxLength =
+        loadTimeData.getBoolean('isAmbientSubpageUIChangeEnabled') ? 3 : 4;
     switch (this.topicSource_) {
       case TopicSource.kArtGallery:
         return (this.previewAlbums_ || []).map(album => album.url);
       case TopicSource.kGooglePhotos:
         if (isNonEmptyArray(this.googlePhotosAlbumsPreviews_)) {
-          return this.googlePhotosAlbumsPreviews_.length < 4 ?
+          return this.googlePhotosAlbumsPreviews_.length < maxLength ?
               [this.googlePhotosAlbumsPreviews_[0]] :
-              this.googlePhotosAlbumsPreviews_.slice(0, 4);
+              this.googlePhotosAlbumsPreviews_.slice(0, maxLength);
         }
         if (isNonEmptyArray(this.previewAlbums_)) {
-          return this.previewAlbums_.length < 4 ?
+          return this.previewAlbums_.length < maxLength ?
               [this.previewAlbums_[0].url] :
-              this.previewAlbums_.map(album => album.url).slice(0, 4);
+              this.previewAlbums_.map(album => album.url).slice(0, maxLength);
         }
     }
     return [];
@@ -259,6 +263,10 @@
     return classes.join(' ');
   }
 
+  private getThumbnailContainerClass_(): string {
+    return `thumbnail-${this.collageImages_.length} clickable`;
+  }
+
   private getCollageContainerClass_(): string {
     return `collage-${this.collageImages_.length} clickable`;
   }
diff --git a/base/base_paths_fuchsia.cc b/base/base_paths_fuchsia.cc
index 18577efd..937ce4da 100644
--- a/base/base_paths_fuchsia.cc
+++ b/base/base_paths_fuchsia.cc
@@ -30,8 +30,8 @@
       *result = base::FilePath(base::kPackageRootDirectoryPath);
       return true;
     case DIR_USER_DESKTOP:
-      // TODO(crbug.com/1231928): Implement this case.
-      NOTIMPLEMENTED_LOG_ONCE() << " for DIR_USER_DESKTOP.";
+      // TODO(crbug.com/1231928): Implement this case for DIR_USER_DESKTOP.
+      NOTIMPLEMENTED_LOG_ONCE();
       return false;
     case DIR_HOME:
       // TODO(crbug.com/1231928) Provide a proper base::GetHomeDir()
@@ -39,7 +39,8 @@
       // crbug.com/1261284. For now, log, return false, and let the base
       // implementation handle it. This will end up returning a temporary
       // directory.
-      NOTIMPLEMENTED_LOG_ONCE() << "for DIR_HOME. Will use temporary dir.";
+      // This is for DIR_HOME. Will use temporary dir.
+      NOTIMPLEMENTED_LOG_ONCE();
       return false;
   }
 
diff --git a/base/check_unittest.cc b/base/check_unittest.cc
index d0a103c..bd9a6505 100644
--- a/base/check_unittest.cc
+++ b/base/check_unittest.cc
@@ -470,8 +470,7 @@
 }
 
 void NiLogOnce() {
-  // Note: The stream param is not logged.
-  NOTIMPLEMENTED_LOG_ONCE() << "foo";
+  NOTIMPLEMENTED_LOG_ONCE();
 }
 
 TEST(CheckTest, NotImplementedLogOnce) {
diff --git a/base/functional/unretained_traits.h b/base/functional/unretained_traits.h
index 464c644..6933b0e 100644
--- a/base/functional/unretained_traits.h
+++ b/base/functional/unretained_traits.h
@@ -11,11 +11,13 @@
 
 // Various opaque system types that should still be usable with the base
 // callback system. Please keep sorted.
+struct ANativeWindow;
 struct DBusMessage;
 struct HWND__;
-struct VkImage_T;
-struct VkDeviceMemory_T;
 struct VkBuffer_T;
+struct VkDeviceMemory_T;
+struct VkImage_T;
+struct VkSemaphore_T;
 struct VmaAllocation_T;
 struct WGPUAdapterImpl;
 struct fpdf_action_t__;
@@ -63,17 +65,21 @@
 // Various opaque system types that should still be usable with the base
 // callback system. Please keep sorted.
 template <>
+inline constexpr bool IsIncompleteTypeSafeForUnretained<ANativeWindow> = true;
+template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<DBusMessage> = true;
 template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<HWND__> = true;
 template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<VkBuffer_T> = true;
 template <>
-inline constexpr bool IsIncompleteTypeSafeForUnretained<VkImage_T> = true;
-template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<VkDeviceMemory_T> =
     true;
 template <>
+inline constexpr bool IsIncompleteTypeSafeForUnretained<VkImage_T> = true;
+template <>
+inline constexpr bool IsIncompleteTypeSafeForUnretained<VkSemaphore_T> = true;
+template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<VmaAllocation_T> = true;
 template <>
 inline constexpr bool IsIncompleteTypeSafeForUnretained<WGPUAdapterImpl> = true;
@@ -116,14 +122,14 @@
 // - non-test code
 // - non-official code (because these builds don't run as part of the default CQ
 //   and are slower due to PGO and LTO)
-// - Linux or Windows
+// - Android, Linux or Windows
 //
 // to make this easier to land without potentially breaking the tree.
 //
 // TODO(https://crbug.com/1392872): Enable this on all platforms, then in
 // official builds, and then in non-test code as well.
 #if !defined(UNIT_TEST) && !defined(OFFICIAL_BUILD)
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || \
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || \
     defined(FORCE_UNRETAINED_COMPLETENESS_CHECKS_FOR_TESTS)
   static_assert(IsCompleteTypeV<T> ||
                     IsIncompleteTypeSafeForUnretained<std::remove_cv_t<T>>,
diff --git a/base/notreached.h b/base/notreached.h
index 255db00e..3deaca8 100644
--- a/base/notreached.h
+++ b/base/notreached.h
@@ -24,6 +24,10 @@
 // The NOTIMPLEMENTED() macro annotates codepaths which have not been
 // implemented yet. If output spam is a serious concern,
 // NOTIMPLEMENTED_LOG_ONCE can be used.
+// Note that the NOTIMPLEMENTED_LOG_ONCE() macro does not allow custom error
+// messages to be appended to the macro to log, unlike NOTIMPLEMENTED() which
+// does support the pattern of appending a custom error message.  As in, the
+// NOTIMPLEMENTED_LOG_ONCE() << "foo message"; pattern is not supported.
 #if DCHECK_IS_ON()
 #define NOTIMPLEMENTED() \
   ::logging::CheckError::NotImplemented(__FILE__, __LINE__, __PRETTY_FUNCTION__)
@@ -38,8 +42,7 @@
       NOTIMPLEMENTED();              \
       logged_once = true;            \
     }                                \
-  }                                  \
-  EAT_CHECK_STREAM_PARAMS()
+  }
 
 }  // namespace logging
 
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index e14bf38b..e7ad457 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -30,6 +30,12 @@
 BASE_EXPORT BASE_DECLARE_FEATURE(kNumberOfCoresWithCpuSecurityMitigation);
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Strings for environment variables.
+BASE_EXPORT extern const char kLsbReleaseKey[];
+BASE_EXPORT extern const char kLsbReleaseTimeKey[];
+#endif
+
 namespace debug {
 FORWARD_DECLARE_TEST(SystemMetricsTest, ParseMeminfo);
 }
diff --git a/base/system/sys_info_chromeos.cc b/base/system/sys_info_chromeos.cc
index afef7f4d..da4f2ce 100644
--- a/base/system/sys_info_chromeos.cc
+++ b/base/system/sys_info_chromeos.cc
@@ -24,6 +24,9 @@
 
 namespace base {
 
+const char kLsbReleaseKey[] = "LSB_RELEASE";
+const char kLsbReleaseTimeKey[] = "LSB_RELEASE_TIME";  // Seconds since epoch
+
 namespace {
 
 const char* const kLinuxStandardBaseVersionKeys[] = {
@@ -38,9 +41,6 @@
 
 const char kLinuxStandardBaseReleaseFile[] = "/etc/lsb-release";
 
-const char kLsbReleaseKey[] = "LSB_RELEASE";
-const char kLsbReleaseTimeKey[] = "LSB_RELEASE_TIME";  // Seconds since epoch
-
 const char kLsbReleaseSourceKey[] = "lsb-release";
 const char kLsbReleaseSourceEnv[] = "env";
 const char kLsbReleaseSourceFile[] = "file";
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc
index 0292130..321c609 100644
--- a/base/task/sequence_manager/sequence_manager_impl.cc
+++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -179,7 +179,7 @@
 // deciding when the next wake up should happen.
 // Note: An atomic is used here because some tests can initialize two different
 //       sequence managers on different threads (e.g. by using base::Thread).
-std::atomic_bool g_no_wake_ups_for_canceled_tasks{false};
+std::atomic_bool g_no_wake_ups_for_canceled_tasks{true};
 
 #if BUILDFLAG(IS_WIN)
 bool g_explicit_high_resolution_timer_win = false;
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index dbe525f..fe62bb9 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -80,7 +80,7 @@
 // kSweepCancelledTasks and kExplicitHighResolutionTimerWin features. This
 // avoids the need to constantly query their enabled state through
 // FeatureList::IsEnabled().
-bool g_is_remove_canceled_tasks_in_task_queue_enabled = false;
+bool g_is_remove_canceled_tasks_in_task_queue_enabled = true;
 bool g_is_sweep_cancelled_tasks_enabled =
     kSweepCancelledTasks.default_state == FEATURE_ENABLED_BY_DEFAULT;
 #if BUILDFLAG(IS_WIN)
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index a07c711..5e03b8a7 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -33,15 +33,15 @@
 // static
 BASE_FEATURE(kNoWakeUpsForCanceledTasks,
              "NoWakeUpsForCanceledTasks",
-             FEATURE_DISABLED_BY_DEFAULT);
+             FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kRemoveCanceledTasksInTaskQueue,
              "RemoveCanceledTasksInTaskQueue2",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kAlwaysAbandonScheduledTask,
              "AlwaysAbandonScheduledTask",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kDelayFirstWorkerWake,
              "DelayFirstWorkerWake",
diff --git a/base/task/thread_pool/delayed_task_manager.cc b/base/task/thread_pool/delayed_task_manager.cc
index a4108a92..4be81c1 100644
--- a/base/task/thread_pool/delayed_task_manager.cc
+++ b/base/task/thread_pool/delayed_task_manager.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/feature_list.h"
+#include "base/task/common/checked_lock.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_features.h"
 #include "base/task/task_runner.h"
@@ -101,7 +102,7 @@
     delayed_task_queue_.insert(DelayedTask(std::move(task),
                                            std::move(post_task_now_callback),
                                            std::move(task_runner)));
-    // Not started yet.
+    // Not started or already shutdown.
     if (service_thread_task_runner_ == nullptr)
       return;
 
@@ -125,6 +126,11 @@
 
   {
     CheckedAutoLock auto_lock(queue_lock_);
+
+    // Already shutdown.
+    if (!service_thread_task_runner_)
+      return;
+
     const TimeTicks now = tick_clock_->NowTicks();
     // A delayed task is ripe if it reached its delayed run time or if it is
     // canceled. If it is canceled, schedule its deletion on the correct
@@ -170,6 +176,31 @@
   return delayed_task_queue_.top().task.delay_policy;
 }
 
+void DelayedTaskManager::Shutdown() {
+  scoped_refptr<SequencedTaskRunner> service_thread_task_runner;
+
+  {
+    CheckedAutoLock auto_lock(queue_lock_);
+    // Prevent delayed tasks from being posted or processed after this.
+    service_thread_task_runner = service_thread_task_runner_;
+  }
+
+  if (service_thread_task_runner) {
+    // Cancel our delayed task on the service thread. This cannot be done from
+    // ~DelayedTaskManager because the delayed task handle is sequence-affine.
+    service_thread_task_runner->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            [](DelayedTaskManager* manager) {
+              DCHECK_CALLED_ON_VALID_SEQUENCE(manager->sequence_checker_);
+              manager->delayed_task_handle_.CancelTask();
+            },
+            // Unretained() is safe because the caller must flush tasks posted
+            // to the service thread before deleting `this`.
+            Unretained(this)));
+  }
+}
+
 std::pair<TimeTicks, subtle::DelayPolicy> DelayedTaskManager::
     GetTimeAndDelayPolicyToScheduleProcessRipeTasksLockRequired() {
   queue_lock_.AssertAcquired();
diff --git a/base/task/thread_pool/delayed_task_manager.h b/base/task/thread_pool/delayed_task_manager.h
index 3a86edf..a04b4e3 100644
--- a/base/task/thread_pool/delayed_task_manager.h
+++ b/base/task/thread_pool/delayed_task_manager.h
@@ -64,6 +64,11 @@
   // Returns the DelayPolicy for the next delayed task.
   subtle::DelayPolicy TopTaskDelayPolicyForTesting() const;
 
+  // Must be invoked before deleting the delayed task manager. The caller must
+  // flush tasks posted to the service thread by this before deleting the
+  // delayed task manager.
+  void Shutdown();
+
  private:
   struct DelayedTask {
     DelayedTask();
diff --git a/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc b/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
index 20da3ce..136ea27 100644
--- a/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
+++ b/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
@@ -62,6 +62,7 @@
   void TearDown() override {
     if (single_thread_task_runner_manager_)
       TearDownSingleThreadTaskRunnerManager();
+    delayed_task_manager_.Shutdown();
     service_thread_.Stop();
   }
 
diff --git a/base/task/thread_pool/thread_group_impl_unittest.cc b/base/task/thread_pool/thread_group_impl_unittest.cc
index 8a74410..01a82feb 100644
--- a/base/task/thread_pool/thread_group_impl_unittest.cc
+++ b/base/task/thread_pool/thread_group_impl_unittest.cc
@@ -79,6 +79,7 @@
         tracked_ref_factory_(this) {}
 
   void CommonTearDown() {
+    delayed_task_manager_.Shutdown();
     service_thread_.Stop();
     task_tracker_.FlushForTesting();
     if (thread_group_)
diff --git a/base/task/thread_pool/thread_group_unittest.cc b/base/task/thread_pool/thread_group_unittest.cc
index 87d32049..233c84b 100644
--- a/base/task/thread_pool/thread_group_unittest.cc
+++ b/base/task/thread_pool/thread_group_unittest.cc
@@ -101,6 +101,7 @@
   }
 
   void TearDown() override {
+    delayed_task_manager_.Shutdown();
     service_thread_.Stop();
     if (thread_group_)
       thread_group_->JoinForTesting();
diff --git a/base/task/thread_pool/thread_pool_impl.cc b/base/task/thread_pool/thread_pool_impl.cc
index eac909f1b..ad911cdd 100644
--- a/base/task/thread_pool/thread_pool_impl.cc
+++ b/base/task/thread_pool/thread_pool_impl.cc
@@ -299,6 +299,10 @@
 void ThreadPoolImpl::Shutdown() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Cancels an internal service thread task. This must be done before stopping
+  // the service thread.
+  delayed_task_manager_.Shutdown();
+
   // Stop() the ServiceThread before triggering shutdown. This ensures that no
   // more delayed tasks or file descriptor watches will trigger during shutdown
   // (preventing http://crbug.com/698140). None of these asynchronous tasks
@@ -337,6 +341,9 @@
 #if DCHECK_IS_ON()
   DCHECK(!join_for_testing_returned_.IsSet());
 #endif
+  // Cancels an internal service thread task. This must be done before stopping
+  // the service thread.
+  delayed_task_manager_.Shutdown();
   // The service thread must be stopped before the workers are joined, otherwise
   // tasks scheduled by the DelayedTaskManager might be posted between joining
   // those workers and stopping the service thread which will cause a CHECK. See
diff --git a/base/timer/timer.cc b/base/timer/timer.cc
index f755b249..8f00af2 100644
--- a/base/timer/timer.cc
+++ b/base/timer/timer.cc
@@ -26,7 +26,7 @@
 // Cache of the state of the kAlwaysAbandonScheduledTask feature. This avoids
 // the need to constantly query its enabled state through
 // FeatureList::IsEnabled().
-bool g_is_always_abandon_scheduled_task_enabled = false;
+bool g_is_always_abandon_scheduled_task_enabled = true;
 
 }  // namespace
 
diff --git a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
index 75615f1..48ba50f 100644
--- a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
+++ b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
@@ -56,11 +56,13 @@
     flags.setColor(color_);
     gfx::Rect inset_rect = rect;
     while (!inset_rect.IsEmpty()) {
-      int big = paint_scale_ + 2;
-      int small = paint_scale_;
-      inset_rect.Inset(gfx::Insets::TLBR(big, big, small, small));
+      int big_rect = paint_scale_ + 2;
+      int small_rect = paint_scale_;
+      inset_rect.Inset(
+          gfx::Insets::TLBR(big_rect, big_rect, small_rect, small_rect));
       canvas->drawRect(RectToSkRect(inset_rect), flags);
-      inset_rect.Inset(gfx::Insets::TLBR(big, big, small, small));
+      inset_rect.Inset(
+          gfx::Insets::TLBR(big_rect, big_rect, small_rect, small_rect));
     }
   }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 94ff806..34dbce24 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=110
 MINOR=0
-BUILD=5475
+BUILD=5476
 PATCH=0
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 23e5d0a..950ce56b 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -1235,13 +1235,6 @@
       <meta-data android:name="com.samsung.android.sdk.multiwindow.enable"
           android:value="true" />
 
-      {% if min_sdk_version|int < 24 %}
-      <meta-data android:name="com.samsung.android.sdk.multiwindow.multiinstance.enable"
-          android:value="true" />
-      <meta-data android:name="com.samsung.android.sdk.multiwindow.multiinstance.launchmode"
-          android:value="singleTask" />
-      {% endif %}
-
       <meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable"
           android:value="true"/>
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index c3f3118..aebcb6b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -476,9 +476,6 @@
             isContentScheme = true;
             newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         }
-        if (MultiWindowUtils.getInstance().shouldRunInLegacyMultiInstanceMode(mActivity, mIntent)) {
-            MultiWindowUtils.getInstance().makeLegacyMultiInstanceIntent(mActivity, newIntent);
-        }
 
         if (newIntent.getComponent().getClassName().equals(mActivity.getClass().getName())) {
             // We're trying to start activity that is already running - just continue.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
index f692f0c..b2d02f313 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
@@ -197,8 +197,7 @@
             mPreviousWindowTop = windowTop;
 
             Activity activity = mWindowAndroid != null ? mWindowAndroid.getActivity().get() : null;
-            boolean isMultiWindow = MultiWindowUtils.getInstance().isLegacyMultiWindow(activity)
-                    || MultiWindowUtils.getInstance().isInMultiWindowMode(activity);
+            boolean isMultiWindow = MultiWindowUtils.getInstance().isInMultiWindowMode(activity);
 
             // If the measured width is the same as the allowed width (i.e. the orientation has
             // not changed) and multi-window mode is off, use the largest measured height seen thus
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
index f37ab524..963371e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
@@ -426,8 +426,7 @@
 
     @Override
     public void onActivityStateChange(Activity activity, int newState) {
-        boolean isMultiWindowMode = MultiWindowUtils.getInstance().isLegacyMultiWindow(mActivity)
-                || MultiWindowUtils.getInstance().isInMultiWindowMode(mActivity);
+        boolean isMultiWindowMode = MultiWindowUtils.getInstance().isInMultiWindowMode(mActivity);
 
         // In multi-window mode the activity that was interacted with last is resumed and
         // all others are paused. We should not close Contextual Search in this case,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java
index 49996a1..2cba79aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java
@@ -594,8 +594,7 @@
             systemUiVisibility = applyEnterFullscreenUIFlags(systemUiVisibility);
         } else {
             Activity activity = TabUtils.getActivity(tab);
-            boolean isMultiWindow = MultiWindowUtils.getInstance().isLegacyMultiWindow(activity)
-                    || MultiWindowUtils.getInstance().isInMultiWindowMode(activity);
+            boolean isMultiWindow = MultiWindowUtils.getInstance().isInMultiWindowMode(activity);
 
             // To avoid a double layout that is caused by the system when just hiding
             // the status bar set the status bar as translucent immediately. This causes
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java
index 3aa3871..78cb2a2a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/SingleWindowKeyboardVisibilityDelegate.java
@@ -9,7 +9,6 @@
 import android.view.View;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
 
 import java.lang.ref.WeakReference;
@@ -30,9 +29,6 @@
             activity = (Activity) view.getContext();
         }
 
-        if (activity != null && MultiWindowUtils.getInstance().isLegacyMultiWindow(activity)) {
-            return false; // For multi-window mode we do not track keyboard visibility.
-        }
         return super.isKeyboardShowing(context, view);
     }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
index 9b59d06..a7a2900 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
@@ -239,6 +239,8 @@
     public void attemptPictureInPicture() {
         // If there are already callbacks registered, then do nothing.
         final @MetricsAttemptResult int result = getAttemptResult();
+        Log.i(TAG, "Attempted picture-in-picture with result: " + result);
+
         recordAttemptResult(result);
         if (result != MetricsAttemptResult.SUCCESS) return;
 
@@ -266,6 +268,8 @@
      * {@link #attemptPictureInPicture()} or because we auto-entered Picture in Picture.
      */
     public void onEnteredPictureInPictureMode() {
+        Log.i(TAG, "Entered Picture-in-picture.");
+
         // Inform the WebContents when we enter and when we leave PiP.
         final WebContents webContents = getWebContents();
         // If we're closing the tab, just stop here.
@@ -346,6 +350,8 @@
      * It's okay if we're not currently in Picture in Picture mode.
      */
     private void onExitedPictureInPicture(@MetricsEndReason int reason) {
+        Log.i(TAG, "Exited picture in picture with reason: " + reason);
+
         // If we don't believe that a Picture in Picture session is active, it means that the
         // cleanup call happened while Chrome was not PIP'ing. The early return also avoid recording
         // the reason why the (non-)PIP session ended.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
index c65b7bf..2cb0a650 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
@@ -21,7 +21,6 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ActivityState;
@@ -47,9 +46,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Locale;
 
@@ -219,7 +215,6 @@
      * @param activity The activity firing the intent.
      * @param targetActivity The class of the activity receiving the intent.
      */
-    @RequiresApi(Build.VERSION_CODES.N)
     public static void setOpenInOtherWindowIntentExtras(
             Intent intent, Activity activity, Class<? extends Activity> targetActivity) {
         intent.setClass(activity, targetActivity);
@@ -602,49 +597,6 @@
     }
 
     /**
-     * @param activity The {@link Activity} to check.
-     * @return Whether or not {@code activity} is currently in pre-N Samsung multi-window mode.
-     */
-    public boolean isLegacyMultiWindow(Activity activity) {
-        if (activity == null) return false;
-
-        try {
-            // Check if Samsung's multi-window mode is supported on this device.
-            // PackageManager#hasSystemFeature(PackageManager.FEATURE_MULTIWINDOW);
-            PackageManager pm = activity.getPackageManager();
-            Field multiwindowFeatureField = pm.getClass().getField("FEATURE_MULTIWINDOW");
-            if (!pm.hasSystemFeature((String) multiwindowFeatureField.get(null))) return false;
-
-            // Grab the current window mode.
-            // int windowMode = Activity#getWindowMode();
-            Method getWindowMode = activity.getClass().getMethod("getWindowMode", (Class[]) null);
-            int windowMode = (Integer) getWindowMode.invoke(activity, (Object[]) null);
-
-            // Grab the multi-window mode constant.
-            // android.view.WindowManagerPolicy#WINDOW_MODE_FREESTYLE
-            Class<?> windowManagerPolicyClass = Class.forName("android.view.WindowManagerPolicy");
-            Field windowModeFreestyleField =
-                    windowManagerPolicyClass.getField("WINDOW_MODE_FREESTYLE");
-            int featureMultiWindowFreestyle = (Integer) windowModeFreestyleField.get(null);
-
-            // Compare windowMode with WINDOW_MODE_FREESTYLE to see if that flag is set.
-            return (windowMode & featureMultiWindowFreestyle) != 0;
-        } catch (NoSuchFieldException e) {
-            return false;
-        } catch (IllegalAccessException e) {
-            return false;
-        } catch (IllegalArgumentException e) {
-            return false;
-        } catch (NoSuchMethodException e) {
-            return false;
-        } catch (InvocationTargetException e) {
-            return false;
-        } catch (ClassNotFoundException e) {
-            return false;
-        }
-    }
-
-    /**
      * @return Whether ChromeTabbedActivity (exact activity, not a subclass of) is currently
      *         running.
      */
@@ -656,29 +608,6 @@
     }
 
     /**
-     * @return Whether or not activity should run in pre-N Samsung multi-instance mode.
-     */
-    public boolean shouldRunInLegacyMultiInstanceMode(Activity activity, Intent intent) {
-        return Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
-                && TextUtils.equals(intent.getAction(), Intent.ACTION_MAIN)
-                && isLegacyMultiWindow(activity) && isPrimaryTabbedActivityRunning();
-    }
-
-    /**
-     * Makes |intent| able to support multi-instance in pre-N Samsung multi-window mode.
-     */
-    public void makeLegacyMultiInstanceIntent(Activity activity, Intent intent) {
-        if (isLegacyMultiWindow(activity)) {
-            if (TextUtils.equals(ChromeTabbedActivity.class.getName(),
-                    intent.getComponent().getClassName())) {
-                intent.setClassName(activity, MultiInstanceChromeTabbedActivity.class.getName());
-            }
-            intent.setFlags(intent.getFlags()
-                    & ~(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT));
-        }
-    }
-
-    /**
      * Records user actions and ukms associated with entering and exiting Android N multi-window
      * mode.
      * For second activity, records separate user actions for entering/exiting multi-window mode to
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index c95e0a59..10977b3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -924,10 +924,6 @@
             // single PromoDialogCoordinator.
             boolean isShowingPromo =
                     LocaleManager.getInstance().hasShownSearchEnginePromoThisSession();
-            // Promo dialogs in multiwindow mode are broken on some devices:
-            // http://crbug.com/354696
-            boolean isLegacyMultiWindow =
-                    MultiWindowUtils.getInstance().isLegacyMultiWindow(mActivity);
             if (!isShowingPromo && !intentWithEffect && FirstRunStatus.getFirstRunFlowComplete()
                     && preferenceManager.readBoolean(
                             ChromePreferenceKeys.PROMOS_SKIPPED_ON_FIRST_START, false)
@@ -936,8 +932,7 @@
                     // even though Chrome is about to enter VR, so we need to also check whether
                     // we're launching into VR.
                     && !VrModuleProvider.getIntentDelegate().isLaunchingIntoVr(
-                            mActivity, mActivity.getIntent())
-                    && !isLegacyMultiWindow) {
+                            mActivity, mActivity.getIntent())) {
                 isShowingPromo = maybeShowPromo();
             } else {
                 preferenceManager.writeBoolean(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/FullscreenVideoPictureInPictureControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/FullscreenVideoPictureInPictureControllerTest.java
index 1dd1192e..c12de71 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/FullscreenVideoPictureInPictureControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/FullscreenVideoPictureInPictureControllerTest.java
@@ -19,6 +19,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
@@ -43,8 +44,10 @@
  * Tests for FullscreenVideoPictureInPictureController and related methods.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
+@Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
         MediaSwitches.AUTOPLAY_NO_GESTURE_REQUIRED_POLICY})
+@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
 @RequiresApi(Build.VERSION_CODES.O)
 public class FullscreenVideoPictureInPictureControllerTest {
     // TODO(peconn): Add a test for exit on Tab Reparenting.
@@ -74,7 +77,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "https://crbug.com/1332360")
     public void testFullscreenVideoDetected() throws Throwable {
         enterFullscreen();
     }
@@ -83,7 +85,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     public void testFullscreenVideoDetectedOnlyWhenPlaying() throws Throwable {
         enterFullscreen();
 
@@ -95,7 +96,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "https://crbug.com/1211930/#c10")
     public void testEnterPip() throws Throwable {
         enterFullscreen();
         triggerAutoPiPAndWait();
@@ -105,7 +105,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "https://crbug.com/1211930/#c10")
     public void testExitPipOnNavigation() throws Throwable {
         testExitOn(()
                            -> JavaScriptUtils.executeJavaScript(getWebContents(),
@@ -116,8 +115,6 @@
     @MediumTest
     @CommandLineFlags.Add({"enable-features=Portals"})
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
-    @DisabledTest(message = "https://crbug.com/1374237/")
     public void testExitPipOnPortalActivation() throws Throwable {
         testExitOn(()
                            -> JavaScriptUtils.executeJavaScript(getWebContents(),
@@ -128,7 +125,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "crbug.com/1330630")
     public void testExitOnLeaveFullscreen() throws Throwable {
         testExitOn(() -> DOMUtils.exitFullscreen(getWebContents()));
     }
@@ -137,7 +133,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     public void testExitOnCloseTab() throws Throwable {
         // We want 2 Tabs so we can close the first without any special behaviour.
         mActivityTestRule.loadUrlInNewTab(mTestServer.getURL(TEST_PATH));
@@ -149,7 +144,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "crbug.com/1333963")
     public void testExitOnCrash() throws Throwable {
         testExitOn(() -> WebContentsUtils.simulateRendererKilled(getWebContents()));
     }
@@ -158,7 +152,6 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "crbug.com/1249669")
     public void testExitOnNewForegroundTab() throws Throwable {
         testExitOn(new Runnable() {
             @Override
@@ -172,7 +165,12 @@
         });
     }
 
-    /** Tests that a navigation in an iframe other than the fullscreen one does not exit PiP. */
+    /**
+     * Tests that a navigation in an iframe other than the fullscreen one does not exit PiP.
+     * TODO(jazzhsu): This test is failing because the navigation observer is no longer observing
+     * child frame navigation. Should fix this after the navigation observer can observe child
+     * frame navigation.
+     */
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
@@ -181,7 +179,8 @@
         // Add a TabObserver so we know when the iFrame navigation has occurred before we check that
         // we are still in PiP.
         final NavigationObserver navigationObserver = new NavigationObserver();
-        mActivity.getActivityTab().addObserver(navigationObserver);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivity.getActivityTab().addObserver(navigationObserver));
 
         enterFullscreen();
         triggerAutoPiPAndWait();
@@ -202,13 +201,11 @@
     @Test
     @MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.O)
-    @DisabledTest(message = "crbug.com/1038151")
     public void testReenterPip() throws Throwable {
         enterFullscreen();
         triggerAutoPiPAndWait();
 
-        // This waits for Stage.CREATED, but we never get one.  We go right to Stage.RESUMED .
-        mActivityTestRule.startMainActivityFromLauncher();
+        mActivityTestRule.resumeMainActivityFromLauncher();
         CriteriaHelper.pollUiThread(() -> !mActivity.getLastPictureInPictureModeForTesting());
 
         enterFullscreen(false);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
index fc593002..7731b28 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java
@@ -8,10 +8,8 @@
 import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.waitForSecondChromeTabbedActivity;
 import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.waitForTabs;
 
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -27,7 +25,6 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -50,7 +47,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
 public class MultiWindowIntegrationTest {
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@@ -71,7 +67,6 @@
     @Test
     @MediumTest
     @Feature("MultiWindow")
-    @RequiresApi(Build.VERSION_CODES.N)
     @DisabledTest(message = "Flaky on test-n-phone https://crbug/1197125")
     @CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
     public void testIncognitoNtpHandledCorrectly() {
@@ -109,7 +104,6 @@
     @Test
     @MediumTest
     @Feature("MultiWindow")
-    @RequiresApi(Build.VERSION_CODES.N)
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING,
             ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
     public void
@@ -145,7 +139,6 @@
     @Test
     @MediumTest
     @Feature("MultiWindow")
-    @RequiresApi(Build.VERSION_CODES.N)
     @DisabledTest(message = "Flaky on test-n-phone https://crbug/1197125")
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING,
             ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
index 0cee87df..fbfef51 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java
@@ -9,10 +9,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Build;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
@@ -122,7 +120,6 @@
     /**
      * Moves the given activity to the foreground so it can receive user input.
      */
-    @RequiresApi(Build.VERSION_CODES.N)
     public static void moveActivityToFront(final Activity activity) {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             Context context = ContextUtils.getApplicationContext();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java
index 66da7218..ad62eb8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java
@@ -28,7 +28,6 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -45,7 +44,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
 public class MultiWindowUtilsTest {
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index efcdff8..767453b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -2756,39 +2756,67 @@
         Anyone using this device can see downloaded files
       </message>
       <message name="IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION"
-            desc="Accessibility/tooltip label for action to resume a download.">
+            desc="Tooltip label for action to resume a download.">
         Resume
       </message>
+      <message name="IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION_ACCESSIBILITY"
+            desc="Accessibility label for action to resume a download.">
+        Resume <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph>
+      </message>
       <message name="IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION"
-              desc="Accessibility/tooltip label for action to pause a download.">
+              desc="Tooltip label for action to pause a download.">
         Pause
       </message>
+      <message name="IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION_ACCESSIBILITY"
+              desc="Accessibility label for action to pause a download.">
+        Pause <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph>
+      </message>
       <message name="IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION"
-              desc="Accessibility/tooltip label for action to cancel a download.">
+              desc="Tooltip label for action to cancel a download.">
         Cancel
       </message>
+      <message name="IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION_ACCESSIBILITY"
+              desc="Accessibility label for action to cancel a download.">
+        Cancel <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph>
+      </message>
       <message name="IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION"
-              desc="Accessibility/tooltip label for action to open a download.">
+              desc="Tooltip label for action to open a download.">
         Open
       </message>
+      <message name="IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION_ACCESSIBILITY"
+              desc="Accessibility label for action to open a download.">
+        Open <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph>
+      </message>
       <if expr="is_macosx">
             <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION"
-                  desc="Mac: Accessibility/tooltip label for showing the file in the Finder">
+                  desc="Mac: Tooltip label for showing the file in the Finder">
               Show in Finder
             </message>
+            <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY"
+                  desc="Mac: Accessibility label for showing the file in the Finder">
+              Show <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> in Finder
+            </message>
       </if>
       <if expr="not is_macosx">
         <if expr="not use_titlecase">
             <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION"
-                     desc="Accessibility/tooltip label for showing the file in file explorer">
+                     desc="Tooltip label for showing the file in file explorer">
               Show in folder
             </message>
+            <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY"
+                     desc="Accessibility label for showing the file in file explorer">
+              Show <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> in folder
+            </message>
           </if>
           <if expr="use_titlecase">
             <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION"
-                     desc="In Title Case: Accessibility/tooltip label for showing the file in file explorer">
+                     desc="In Title Case: Tooltip label for showing the file in file explorer">
               Show in Folder
             </message>
+            <message name="IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY"
+                     desc="In Title Case: Accessibility label for showing the file in file explorer">
+              Show <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> in Folder
+            </message>
           </if>
       </if>
       <if expr="not use_titlecase">
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION_ACCESSIBILITY.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION_ACCESSIBILITY.png.sha1
new file mode 100644
index 0000000..e58c8fdc
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION_ACCESSIBILITY.png.sha1
@@ -0,0 +1 @@
+c31c682d09d146340f5e1287c35110f0258b46a2
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION_ACCESSIBILITY.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION_ACCESSIBILITY.png.sha1
new file mode 100644
index 0000000..6fa66d8
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION_ACCESSIBILITY.png.sha1
@@ -0,0 +1 @@
+e4eb8ee8c7daa77a91b9a87b70c39028edc5db4c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION_ACCESSIBILITY.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION_ACCESSIBILITY.png.sha1
new file mode 100644
index 0000000..e08d19b1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION_ACCESSIBILITY.png.sha1
@@ -0,0 +1 @@
+93c35d08cdc456d9d18a73840651e9518298c5a8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION_ACCESSIBILITY.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION_ACCESSIBILITY.png.sha1
new file mode 100644
index 0000000..0f914b53
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION_ACCESSIBILITY.png.sha1
@@ -0,0 +1 @@
+5f76a57c3870f213e0f9dbb7f280cfbdc54d9afc
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY.png.sha1
new file mode 100644
index 0000000..3b34e85
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY.png.sha1
@@ -0,0 +1 @@
+f9898ecf76d57b2e13bc2cc26de79a52b478a743
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index a340cb3..22d057c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -943,6 +943,8 @@
     "ntp_tiles/chrome_popular_sites_factory.h",
     "offline_items_collection/offline_content_aggregator_factory.cc",
     "offline_items_collection/offline_content_aggregator_factory.h",
+    "optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc",
+    "optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h",
     "optimization_guide/chrome_hints_manager.cc",
     "optimization_guide/chrome_hints_manager.h",
     "optimization_guide/model_validator_keyed_service.cc",
@@ -2235,7 +2237,7 @@
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
     "//components/reporting/proto:status_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/storage:storage_configuration",
     "//components/reporting/storage:storage_module",
     "//components/reporting/storage:storage_module_interface",
@@ -4425,6 +4427,7 @@
       "//chrome/common/importer:interfaces",
       "//chrome/common/themes:autogenerated_theme_util",
       "//chrome/services/media_gallery_util/public/cpp",
+      "//components/access_code_cast/common:common",
       "//components/app_constants",
       "//components/commerce/core:cart_db_content_proto",
       "//components/commerce/core:coupon_db_content_proto",
@@ -5881,6 +5884,8 @@
       "enterprise/platform_auth/cloud_ap_utils_win.h",
       "enterprise/platform_auth/platform_auth_navigation_throttle.cc",
       "enterprise/platform_auth/platform_auth_navigation_throttle.h",
+      "enterprise/platform_auth/platform_auth_policy_observer.cc",
+      "enterprise/platform_auth/platform_auth_policy_observer.h",
       "enterprise/platform_auth/platform_auth_provider.h",
       "enterprise/platform_auth/platform_auth_provider_manager.cc",
       "enterprise/platform_auth/platform_auth_provider_manager.h",
@@ -8021,8 +8026,8 @@
       "//chrome/browser/resources/chromeos/account_manager/components:html_wrapper_files",
       "//chrome/browser/resources/chromeos/add_supervision:web_components",
       "//chrome/browser/resources/chromeos/arc_account_picker:web_components",
-      "//chrome/browser/resources/chromeos/crostini_installer:web_components",
-      "//chrome/browser/resources/chromeos/crostini_upgrader:web_components",
+      "//chrome/browser/resources/chromeos/crostini_installer:html_wrapper_files",
+      "//chrome/browser/resources/chromeos/crostini_upgrader:html_wrapper_files",
       "//chrome/browser/resources/chromeos/edu_coexistence:edu_coexistence_controller",
       "//chrome/browser/resources/chromeos/edu_coexistence:web_components",
       "//chrome/browser/resources/chromeos/emulator:web_components",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index dac02f0..95110ce 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5530,10 +5530,10 @@
                                     kOmniboxDynamicMaxAutocompleteVariations,
                                     "OmniboxBundledExperimentV1")},
 
-    {"omnibox-retain-suggestions-with-headers",
-     flag_descriptions::kOmniboxRetainSuggestionsWithHeadersName,
-     flag_descriptions::kOmniboxRetainSuggestionsWithHeadersDescription, kOsAll,
-     FEATURE_VALUE_TYPE(omnibox::kRetainSuggestionsWithHeaders)},
+    {"omnibox-keep-secondary-zero-suggest",
+     flag_descriptions::kOmniboxKeepSecondaryZeroSuggestName,
+     flag_descriptions::kOmniboxKeepSecondaryZeroSuggestDescription, kOsAll,
+     FEATURE_VALUE_TYPE(omnibox::kKeepSecondaryZeroSuggest)},
 
     {"omnibox-bookmark-paths", flag_descriptions::kOmniboxBookmarkPathsName,
      flag_descriptions::kOmniboxBookmarkPathsDescription, kOsAll,
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index c438241..67e8a45 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -454,7 +454,8 @@
     content::WebContents& web_contents) {
   Profile* profile =
       Profile::FromBrowserContext(web_contents.GetBrowserContext());
-  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 std::unique_ptr<content::WebContents>
diff --git a/chrome/browser/android/vr/vr_gl_thread.cc b/chrome/browser/android/vr/vr_gl_thread.cc
index 1ee82f1..13f1456 100644
--- a/chrome/browser/android/vr/vr_gl_thread.cc
+++ b/chrome/browser/android/vr/vr_gl_thread.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/vr/ui_test_input.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gl/android/surface_texture.h"
 
 namespace vr {
 
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_loader.cc b/chrome/browser/apps/app_service/app_icon/app_icon_loader.cc
index c57bee9..ab6104e 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_loader.cc
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_loader.cc
@@ -480,7 +480,8 @@
         base::StringPiece data =
             ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
                 icon_resource);
-        CompleteWithCompressed(std::vector<uint8_t>(data.begin(), data.end()));
+        CompleteWithCompressed(/*is_maskable_icon=*/false,
+                               std::vector<uint8_t>(data.begin(), data.end()));
         return;
       }
       [[fallthrough]];
@@ -601,7 +602,8 @@
   icon_manager.ReadIcons(
       web_app_id, *icon_purpose_to_read, icon_pixel_sizes,
       base::BindOnce(&AppIconLoader::OnReadWebAppForCompressedIconData,
-                     base::WrapRefCounted(this)));
+                     base::WrapRefCounted(this),
+                     *icon_purpose_to_read == IconPurpose::MASKABLE));
 }
 
 void AppIconLoader::GetChromeAppCompressedIconData(
@@ -709,7 +711,8 @@
   CompleteWithIconValue(std::move(iv));
 }
 
-void AppIconLoader::CompleteWithCompressed(std::vector<uint8_t> data) {
+void AppIconLoader::CompleteWithCompressed(bool is_maskable_icon,
+                                           std::vector<uint8_t> data) {
   if (data.empty()) {
     MaybeLoadFallbackOrCompleteEmpty();
     return;
@@ -717,6 +720,7 @@
   auto iv = std::make_unique<IconValue>();
   iv->icon_type = IconType::kCompressed;
   iv->compressed = std::move(data);
+  iv->is_maskable_icon = is_maskable_icon;
   iv->is_placeholder_icon = is_placeholder_icon_;
   std::move(callback_).Run(std::move(iv));
 }
@@ -744,7 +748,7 @@
     // the icon might be null. Return early here if the image is null, to
     // prevent calling MakeThreadSafe, which might cause the system crash due to
     // DCHECK error on image.
-    CompleteWithCompressed(std::vector<uint8_t>());
+    CompleteWithCompressed(/*is_maskable_icon=*/false, std::vector<uint8_t>());
     return;
   }
 
@@ -754,7 +758,7 @@
       base::BindOnce(&apps::EncodeImageToPngBytes, iv->uncompressed,
                      icon_scale_for_compressed_response_),
       base::BindOnce(&AppIconLoader::CompleteWithCompressed,
-                     base::WrapRefCounted(this)));
+                     base::WrapRefCounted(this), /*is_maskable_icon=*/false));
 }
 
 // Callback for reading uncompressed web app icons.
@@ -797,6 +801,7 @@
 }
 
 void AppIconLoader::OnReadWebAppForCompressedIconData(
+    bool is_maskable_icon,
     std::map<int, SkBitmap> icon_bitmaps) {
   if (icon_bitmaps.empty()) {
     MaybeLoadFallbackOrCompleteEmpty();
@@ -817,7 +822,7 @@
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
       base::BindOnce(&apps::EncodeImageToPngBytes, image_skia, icon_scale_),
       base::BindOnce(&AppIconLoader::CompleteWithCompressed,
-                     base::WrapRefCounted(this)));
+                     base::WrapRefCounted(this), is_maskable_icon));
 }
 
 void AppIconLoader::OnReadChromeAppForCompressedIconData(gfx::ImageSkia image) {
@@ -831,7 +836,7 @@
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
       base::BindOnce(&apps::EncodeImageToPngBytes, image, icon_scale_),
       base::BindOnce(&AppIconLoader::CompleteWithCompressed,
-                     base::WrapRefCounted(this)));
+                     base::WrapRefCounted(this), /*is_maskable_icon=*/false));
 }
 
 void AppIconLoader::MaybeLoadFallbackOrCompleteEmpty() {
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_loader.h b/chrome/browser/apps/app_service/app_icon/app_icon_loader.h
index faf87b8..272c398 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_loader.h
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_loader.h
@@ -147,7 +147,7 @@
 
   void MaybeApplyEffectsAndComplete(const gfx::ImageSkia image);
 
-  void CompleteWithCompressed(std::vector<uint8_t> data);
+  void CompleteWithCompressed(bool is_maskable_icon, std::vector<uint8_t> data);
 
   void CompleteWithUncompressed(IconValuePtr iv);
 
@@ -155,7 +155,8 @@
 
   void OnReadWebAppIcon(std::map<int, SkBitmap> icon_bitmaps);
 
-  void OnReadWebAppForCompressedIconData(std::map<int, SkBitmap> icon_bitmaps);
+  void OnReadWebAppForCompressedIconData(bool is_maskable_icon,
+                                         std::map<int, SkBitmap> icon_bitmaps);
 
   void OnReadChromeAppForCompressedIconData(gfx::ImageSkia image);
 
diff --git a/chrome/browser/apps/app_service/app_icon/web_app_icon_unittest.cc b/chrome/browser/apps/app_service/app_icon/web_app_icon_unittest.cc
index ae361942..10448f2 100644
--- a/chrome/browser/apps/app_service/app_icon/web_app_icon_unittest.cc
+++ b/chrome/browser/apps/app_service/app_icon/web_app_icon_unittest.cc
@@ -87,14 +87,14 @@
     ASSERT_TRUE(web_app_provider_);
 
     base::RunLoop run_loop;
-    web_app_provider_->sync_bridge().Init(run_loop.QuitClosure());
+    web_app_provider_->sync_bridge_unsafe().Init(run_loop.QuitClosure());
     run_loop.Run();
 
     icon_manager_ = static_cast<web_app::WebAppIconManager*>(
         &(web_app_provider_->icon_manager()));
     ASSERT_TRUE(icon_manager_);
 
-    sync_bridge_ = &web_app_provider_->sync_bridge();
+    sync_bridge_ = &web_app_provider_->sync_bridge_unsafe();
   }
 
   void RegisterApp(std::unique_ptr<web_app::WebApp> web_app) {
@@ -794,7 +794,9 @@
       GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
+  ASSERT_FALSE(icon1->is_maskable_icon);
   VerifyCompressedIcon(src_data2, *icon2);
+  ASSERT_FALSE(icon2->is_maskable_icon);
 }
 
 TEST_F(WebAppIconFactoryTest,
@@ -834,7 +836,9 @@
       GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
+  ASSERT_FALSE(icon1->is_maskable_icon);
   VerifyCompressedIcon(src_data2, *icon2);
+  ASSERT_FALSE(icon2->is_maskable_icon);
 }
 
 TEST_F(WebAppIconFactoryTest, GetNonMaskableNonEffectCompressedIcon) {
@@ -871,7 +875,9 @@
       GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
+  ASSERT_FALSE(icon1->is_maskable_icon);
   VerifyCompressedIcon(src_data2, *icon2);
+  ASSERT_FALSE(icon2->is_maskable_icon);
 }
 
 TEST_F(WebAppIconFactoryTest,
@@ -910,7 +916,9 @@
       GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
+  ASSERT_FALSE(icon1->is_maskable_icon);
   VerifyCompressedIcon(src_data2, *icon2);
+  ASSERT_FALSE(icon2->is_maskable_icon);
 }
 
 TEST_F(WebAppIconFactoryTest, GetMaskableCompressedIcon) {
@@ -951,7 +959,9 @@
       GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
+  ASSERT_TRUE(icon1->is_maskable_icon);
   VerifyCompressedIcon(src_data2, *icon2);
+  ASSERT_TRUE(icon1->is_maskable_icon);
 }
 
 class AppServiceWebAppIconTest : public WebAppIconFactoryTest {
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.cc b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
index d0f60c0b..63b6e5d 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_ash.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
@@ -215,11 +215,6 @@
 
 void AppServiceProxyAsh::PauseApps(
     const std::map<std::string, PauseData>& pause_data) {
-  if (!base::FeatureList::IsEnabled(kAppServiceWithoutMojom) &&
-      !app_service_.is_connected()) {
-    return;
-  }
-
   for (auto& data : pause_data) {
     auto app_type = app_registry_cache_.GetAppType(data.first);
     if (app_type == AppType::kUnknown) {
@@ -235,14 +230,9 @@
 
     // The app pause dialog can't be loaded for unit tests.
     if (!data.second.should_show_pause_dialog || is_using_testing_profile_) {
-      if (base::FeatureList::IsEnabled(kAppServiceWithoutMojom)) {
-        auto* publisher = GetPublisher(app_type);
-        if (publisher) {
-          publisher->PauseApp(data.first);
-        }
-      } else {
-        app_service_->PauseApp(ConvertAppTypeToMojomAppType(app_type),
-                               data.first);
+      auto* publisher = GetPublisher(app_type);
+      if (publisher) {
+        publisher->PauseApp(data.first);
       }
       continue;
     }
@@ -259,11 +249,6 @@
 }
 
 void AppServiceProxyAsh::UnpauseApps(const std::set<std::string>& app_ids) {
-  if (!base::FeatureList::IsEnabled(kAppServiceWithoutMojom) &&
-      !app_service_.is_connected()) {
-    return;
-  }
-
   for (auto& app_id : app_ids) {
     auto app_type = app_registry_cache_.GetAppType(app_id);
     if (app_type == AppType::kUnknown) {
@@ -271,13 +256,9 @@
     }
 
     pending_pause_requests_.MaybeRemoveApp(app_id);
-    if (base::FeatureList::IsEnabled(kAppServiceWithoutMojom)) {
-      auto* publisher = GetPublisher(app_type);
-      if (publisher) {
-        publisher->UnpauseApp(app_id);
-      }
-    } else {
-      app_service_->UnpauseApp(ConvertAppTypeToMojomAppType(app_type), app_id);
+    auto* publisher = GetPublisher(app_type);
+    if (publisher) {
+      publisher->UnpauseApp(app_id);
     }
   }
 }
@@ -290,15 +271,6 @@
   }
 }
 
-void AppServiceProxyAsh::SetResizeLocked(const std::string& app_id,
-                                         apps::mojom::OptionalBool locked) {
-  if (app_service_.is_connected()) {
-    auto app_type = app_registry_cache_.GetAppType(app_id);
-    app_service_->SetResizeLocked(ConvertAppTypeToMojomAppType(app_type),
-                                  app_id, locked);
-  }
-}
-
 void AppServiceProxyAsh::SetArcIsRegistered() {
   if (arc_is_registered_) {
     return;
@@ -609,13 +581,9 @@
         });
   }
   if (should_pause_app) {
-    if (base::FeatureList::IsEnabled(kAppServiceWithoutMojom)) {
-      auto* publisher = GetPublisher(app_type);
-      if (publisher) {
-        publisher->PauseApp(app_id);
-      }
-    } else if (app_service_.is_connected()) {
-      app_service_->PauseApp(ConvertAppTypeToMojomAppType(app_type), app_id);
+    auto* publisher = GetPublisher(app_type);
+    if (publisher) {
+      publisher->PauseApp(app_id);
     }
   }
 }
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.h b/chrome/browser/apps/app_service/app_service_proxy_ash.h
index b5e6f62..6456f149 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_ash.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_ash.h
@@ -109,11 +109,6 @@
   // Set whether resize lock is enabled for the app identified by |app_id|.
   void SetResizeLocked(const std::string& app_id, bool locked);
 
-  // TODO(crbug.com/1253250): Will be removed soon. Please use the non mojom
-  // interface.
-  void SetResizeLocked(const std::string& app_id,
-                       apps::mojom::OptionalBool locked);
-
   // Sets |extension_apps_| and |web_apps_| to observe the ARC apps to set the
   // badge on the equivalent Chrome app's icon, when ARC is available.
   void SetArcIsRegistered();
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.cc b/chrome/browser/apps/app_service/app_service_proxy_base.cc
index 44dc577..0b0a376 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.cc
@@ -451,19 +451,10 @@
 }
 
 void AppServiceProxyBase::StopApp(const std::string& app_id) {
-  if (base::FeatureList::IsEnabled(kAppServiceWithoutMojom)) {
-    auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
-    if (publisher) {
-      publisher->StopApp(app_id);
-    }
-    return;
+  auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
+  if (publisher) {
+    publisher->StopApp(app_id);
   }
-
-  if (!app_service_.is_connected()) {
-    return;
-  }
-  auto app_type = app_registry_cache_.GetAppType(app_id);
-  app_service_->StopApp(ConvertAppTypeToMojomAppType(app_type), app_id);
 }
 
 void AppServiceProxyBase::GetMenuModel(
@@ -493,20 +484,9 @@
 }
 
 void AppServiceProxyBase::OpenNativeSettings(const std::string& app_id) {
-  if (base::FeatureList::IsEnabled(kAppServiceWithoutMojom)) {
-    auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
-    if (publisher) {
-      publisher->OpenNativeSettings(app_id);
-    }
-    return;
-  }
-
-  if (app_service_.is_connected()) {
-    app_registry_cache_.ForOneApp(
-        app_id, [this](const apps::AppUpdate& update) {
-          app_service_->OpenNativeSettings(
-              ConvertAppTypeToMojomAppType(update.AppType()), update.AppId());
-        });
+  auto* publisher = GetPublisher(app_registry_cache_.GetAppType(app_id));
+  if (publisher) {
+    publisher->OpenNativeSettings(app_id);
   }
 }
 
@@ -686,15 +666,6 @@
   }
 }
 
-void AppServiceProxyBase::SetWindowMode(const std::string& app_id,
-                                        apps::mojom::WindowMode window_mode) {
-  if (app_service_.is_connected()) {
-    app_service_->SetWindowMode(
-        ConvertAppTypeToMojomAppType(app_registry_cache_.GetAppType(app_id)),
-        app_id, window_mode);
-  }
-}
-
 void AppServiceProxyBase::OnApps(std::vector<AppPtr> deltas,
                                  AppType app_type,
                                  bool should_notify_initialized) {
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.h b/chrome/browser/apps/app_service/app_service_proxy_base.h
index dd81835..910b1b24 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.h
@@ -277,10 +277,6 @@
   // `window_mode` represents how the app will be open in (e.g. in a
   // standalone window or in a browser tab).
   void SetWindowMode(const std::string& app_id, WindowMode window_mode);
-  // TODO(crbug.com/1253250): Will be removed soon. Please use the non mojom
-  // interface.
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode);
 
   // Called by an app publisher to inform the proxy of a change in app state.
   virtual void OnApps(std::vector<AppPtr> deltas,
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
index 1c08800..2303e25 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
@@ -426,11 +426,6 @@
   NOTIMPLEMENTED();
 }
 
-void AppServiceProxyLacros::SetWindowMode(const std::string& app_id,
-                                          apps::mojom::WindowMode window_mode) {
-  NOTIMPLEMENTED();
-}
-
 web_app::LacrosWebAppsController*
 AppServiceProxyLacros::LacrosWebAppsControllerForTesting() {
   return lacros_web_apps_controller_.get();
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.h b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
index 09b022a8..04582f37 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
@@ -231,10 +231,6 @@
   void RemoveSupportedLinksPreference(const std::string& app_id);
 
   void SetWindowMode(const std::string& app_id, WindowMode window_mode);
-  // TODO(crbug.com/1253250): Will be removed soon. Please use the non mojom
-  // interface.
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode);
 
   web_app::LacrosWebAppsController* LacrosWebAppsControllerForTesting();
 
diff --git a/chrome/browser/apps/app_service/browser_app_instance_registry.cc b/chrome/browser/apps/app_service/browser_app_instance_registry.cc
index c1ac8d6..dbb6054 100644
--- a/chrome/browser/apps/app_service/browser_app_instance_registry.cc
+++ b/chrome/browser/apps/app_service/browser_app_instance_registry.cc
@@ -136,6 +136,22 @@
 
   if (const BrowserWindowInstance* instance =
           GetBrowserWindowInstanceById(id)) {
+    if (aura::Window* window = GetWindowByInstanceId(id)) {
+      views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
+      if (widget->IsActive() != instance->is_active) {
+        // TODO: Replace log with DCHECK once we know better about
+        // crbug.com/1284930 and b/256952679.
+        static bool reported = false;
+        if (!reported) {
+          reported = true;
+          LOG(ERROR) << "Browser window activation is inconsistent. Registry "
+                        "is "
+                     << instance->is_active << " while widget is "
+                     << widget->IsActive() << ".";
+          base::debug::DumpWithoutCrashing();
+        }
+      }
+    }
     return instance->is_active;
   }
   return false;
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc
index 3fc9f544..2665db8 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -1090,20 +1090,6 @@
   subscribers_.Add(std::move(subscriber));
 }
 
-void ArcApps::SetResizeLocked(const std::string& app_id,
-                              apps::mojom::OptionalBool locked) {
-  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
-  if (!prefs) {
-    return;
-  }
-  if (locked == apps::mojom::OptionalBool::kUnknown) {
-    return;
-  }
-  prefs->SetResizeLockState(app_id, locked == apps::mojom::OptionalBool::kTrue
-                                        ? arc::mojom::ArcResizeLockState::ON
-                                        : arc::mojom::ArcResizeLockState::OFF);
-}
-
 void ArcApps::PauseApp(const std::string& app_id) {
   if (paused_apps_.MaybeAddApp(app_id)) {
     SetIconEffect(app_id);
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.h b/chrome/browser/apps/app_service/publishers/arc_apps.h
index 8d191b9..3b264b1 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.h
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.h
@@ -145,8 +145,6 @@
   // apps::mojom::Publisher overrides.
   void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
                apps::mojom::ConnectOptionsPtr opts) override;
-  void SetResizeLocked(const std::string& app_id,
-                       apps::mojom::OptionalBool locked) override;
   void PauseApp(const std::string& app_id) override;
   void UnpauseApp(const std::string& app_id) override;
   void StopApp(const std::string& app_id) override;
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
index 3101da00..6de2bf0 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
@@ -338,12 +338,6 @@
   controller_->StopApp(app_id);
 }
 
-void StandaloneBrowserExtensionApps::SetWindowMode(
-    const std::string& app_id,
-    apps::mojom::WindowMode window_mode) {
-  SetWindowMode(app_id, ConvertMojomWindowModeToWindowMode(window_mode));
-}
-
 void StandaloneBrowserExtensionApps::OpenNativeSettings(
     const std::string& app_id) {
   // It is possible that Lacros is briefly unavailable, for example if it shuts
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
index 6b27251..d6f015c 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
@@ -118,8 +118,6 @@
   void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
                apps::mojom::ConnectOptionsPtr opts) override;
   void StopApp(const std::string& app_id) override;
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode) override;
   void OpenNativeSettings(const std::string& app_id) override;
 
   // crosapi::mojom::AppPublisher overrides.
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
index baacb1e..a4a6f4c 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
@@ -318,11 +318,6 @@
   controller_->OpenNativeSettings(app_id);
 }
 
-void WebAppsCrosapi::SetWindowMode(const std::string& app_id,
-                                   apps::mojom::WindowMode window_mode) {
-  SetWindowMode(app_id, ConvertMojomWindowModeToWindowMode(window_mode));
-}
-
 void WebAppsCrosapi::ExecuteContextMenuCommand(const std::string& app_id,
                                                int command_id,
                                                const std::string& shortcut_id,
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
index d91fae7..4046a2a 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
@@ -128,8 +128,6 @@
   void UnpauseApp(const std::string& app_id) override;
   void StopApp(const std::string& app_id) override;
   void OpenNativeSettings(const std::string& app_id) override;
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode) override;
   void ExecuteContextMenuCommand(const std::string& app_id,
                                  int command_id,
                                  const std::string& shortcut_id,
diff --git a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
index 96257df..146b6c7ea 100644
--- a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
+++ b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "base/barrier_closure.h"
 #include "base/functional/callback_helpers.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
@@ -54,11 +55,13 @@
     Profile* profile,
     apps::AppLaunchParams params,
     const WebAppFileHandlerManager::LaunchInfos& file_launches) {
-  auto callback = GetMacShimStartupDoneCallbackForTesting()
-                      ? base::IgnoreArgs<content::WebContents*>(std::move(
-                            GetMacShimStartupDoneCallbackForTesting()))
-                      : base::DoNothing();
+  auto callback =
+      GetMacShimStartupDoneCallbackForTesting()
+          ? std::move(GetMacShimStartupDoneCallbackForTesting())  // IN-TEST
+          : base::DoNothing();
   if (!file_launches.empty()) {
+    auto barrier_callback =
+        base::BarrierClosure(file_launches.size(), std::move(callback));
     for (const auto& [url, files] : file_launches) {
       apps::AppLaunchParams params_copy(params.app_id, params.container,
                                         params.disposition,
@@ -68,11 +71,13 @@
 
       if (GetBrowserAppLauncherForTesting()) {
         GetBrowserAppLauncherForTesting().Run(params_copy);
-        OnShimLaunchResolved();
+        barrier_callback.Run();
       } else {
         apps::AppServiceProxyFactory::GetForProfile(profile)
             ->BrowserAppLauncher()
-            ->LaunchAppWithParams(std::move(params_copy), std::move(callback));
+            ->LaunchAppWithParams(
+                std::move(params_copy),
+                base::IgnoreArgs<content::WebContents*>(barrier_callback));
       }
     }
     return;
@@ -80,11 +85,13 @@
 
   if (GetBrowserAppLauncherForTesting()) {
     GetBrowserAppLauncherForTesting().Run(params);
-    OnShimLaunchResolved();
+    std::move(callback).Run();
   } else {
     apps::AppServiceProxyFactory::GetForProfile(profile)
         ->BrowserAppLauncher()
-        ->LaunchAppWithParams(std::move(params), std::move(callback));
+        ->LaunchAppWithParams(
+            std::move(params),
+            base::IgnoreArgs<content::WebContents*>(std::move(callback)));
   }
 }
 
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 319fa268..197d7a0 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -694,8 +694,12 @@
     "browser_accelerator_configuration.h",
     "browser_context_keyed_service_factories.cc",
     "browser_context_keyed_service_factories.h",
+    "bruschetta/bruschetta_download_client.cc",
+    "bruschetta/bruschetta_download_client.h",
     "bruschetta/bruschetta_features.cc",
     "bruschetta/bruschetta_features.h",
+    "bruschetta/bruschetta_installer.cc",
+    "bruschetta/bruschetta_installer.h",
     "bruschetta/bruschetta_launcher.cc",
     "bruschetta/bruschetta_launcher.h",
     "bruschetta/bruschetta_mount_provider.cc",
@@ -1077,6 +1081,9 @@
     "file_manager/zip_io_task.cc",
     "file_manager/zip_io_task.h",
     "file_system_provider/abort_callback.h",
+    "file_system_provider/event_dispatcher.h",
+    "file_system_provider/event_dispatcher_impl.cc",
+    "file_system_provider/event_dispatcher_impl.h",
     "file_system_provider/extension_provider.cc",
     "file_system_provider/extension_provider.h",
     "file_system_provider/fileapi/backend_delegate.cc",
@@ -3370,7 +3377,7 @@
     "//components/reporting/proto:metric_data_proto",
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/storage_selector",
     "//components/services/app_service/public/cpp:app_types",
     "//components/services/app_service/public/cpp:app_update",
@@ -4205,6 +4212,7 @@
     # builds as well.
     "../ui/views/frame/immersive_mode_controller_chromeos_unittest.cc",
     "../ui/views/select_file_dialog_extension_unittest.cc",
+    "../ui/webui/ash/cloud_upload/cloud_upload_notification_manager_unittest.cc",
     "../ui/webui/ash/login/l10n_util_test_util.cc",
     "../ui/webui/ash/login/l10n_util_test_util.h",
     "../ui/webui/ash/login/l10n_util_unittest.cc",
@@ -4411,6 +4419,7 @@
     "borealis/infra/state_manager_unittest.cc",
     "borealis/infra/transition_unittest.cc",
     "browser_accelerator_configuration_unittest.cc",
+    "bruschetta/bruschetta_installer_unittest.cc",
     "bruschetta/bruschetta_launcher_unittest.cc",
     "bruschetta/bruschetta_mount_provider_unittest.cc",
     "bruschetta/bruschetta_service_unittest.cc",
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index f6b1558..1e4c0a1 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -494,6 +494,8 @@
       &AccessibilityManager::PlayVolumeAdjustSound, base::Unretained(this)));
 
   CrasAudioHandler::Get()->AddAudioObserver(this);
+
+  pumpkin_installer_ = std::make_unique<PumpkinInstaller>();
 }
 
 AccessibilityManager::~AccessibilityManager() {
@@ -2318,7 +2320,7 @@
     return;
 
   if (ShouldShowSodaSucceededNotificationForDictation())
-    ShowSodaDownloadNotificationForDictation(true);
+    UpdateDictationNotification();
   OnSodaInstallUpdated(100);
 }
 
@@ -2333,7 +2335,7 @@
   // Show the failed message if either the Dictation locale failed or the SODA
   // binary failed (encoded by LanguageCode::kNone).
   if (ShouldShowSodaFailedNotificationForDictation(language_code))
-    ShowSodaDownloadNotificationForDictation(false);
+    UpdateDictationNotification();
   OnSodaInstallUpdated(0);
 }
 
@@ -2380,11 +2382,7 @@
          language_code == GetDictationLanguageCode();
 }
 
-void AccessibilityManager::ShowSodaDownloadNotificationForDictation(
-    bool succeeded) {
-  if (!::features::IsDictationOfflineAvailable())
-    return;
-
+void AccessibilityManager::UpdateDictationNotification() {
   const std::string locale =
       profile_->GetPrefs()->GetString(prefs::kAccessibilityDictationLocale);
   // Get the display name of |locale| in the application locale.
@@ -2392,11 +2390,35 @@
       /*locale=*/locale,
       /*display_locale=*/g_browser_process->GetApplicationLocale(),
       /*is_ui=*/true);
-  AccessibilityController::Get()
-      ->ShowSpeechRecognitionDownloadNotificationForDictation(succeeded,
-                                                              display_name);
 
-  if (!succeeded)
+  bool soda_installed = false;
+  if (::features::IsDictationOfflineAvailable()) {
+    // Only access SodaInstaller if offline Dictation is available.
+    soda_installed = speech::SodaInstaller::GetInstance()->IsSodaInstalled(
+        GetDictationLanguageCode());
+  }
+  bool pumpkin_installed = pumpkin_installer_->IsPumpkinInstalled();
+
+  // There are four possible states for the Dictation notification:
+  // 1. Pumpkin installed, SODA installed
+  // 2. Pumpkin installed, SODA not installed
+  // 3. Pumpkin not installed, SODA installed
+  // 4. Pumpkin not installed, SODA not installed
+  DictationNotificationType type;
+  if (pumpkin_installed && soda_installed) {
+    type = DictationNotificationType::kAllDlcsDownloaded;
+  } else if (pumpkin_installed && !soda_installed) {
+    type = DictationNotificationType::kOnlyPumpkinDownloaded;
+  } else if (!pumpkin_installed && soda_installed) {
+    type = DictationNotificationType::kOnlySodaDownloaded;
+  } else {
+    type = DictationNotificationType::kNoDlcsDownloaded;
+  }
+
+  AccessibilityController::Get()->ShowNotificationForDictation(type,
+                                                               display_name);
+
+  if (type == DictationNotificationType::kNoDlcsDownloaded)
     soda_failed_notification_shown_ = true;
 }
 
@@ -2415,9 +2437,6 @@
     return;
   }
 
-  if (!pumpkin_installer_)
-    pumpkin_installer_ = std::make_unique<PumpkinInstaller>();
-
   // Save `callback` and run it after the installation succeeds or fails.
   install_pumpkin_callback_ = std::move(callback);
   pumpkin_installer_->MaybeInstall(
@@ -2445,6 +2464,8 @@
       base::BindOnce(&CreatePumpkinData, base_pumpkin_path),
       base::BindOnce(&AccessibilityManager::OnPumpkinDataCreated,
                      weak_ptr_factory_.GetWeakPtr()));
+
+  UpdateDictationNotification();
 }
 
 void AccessibilityManager::OnPumpkinDataCreated(
@@ -2457,7 +2478,8 @@
   DCHECK(!install_pumpkin_callback_.is_null());
   std::move(install_pumpkin_callback_).Run(nullptr);
   is_pumpkin_installed_for_testing_ = false;
-  // TODO(akihiroota): Consider showing the error message to the user.
+
+  UpdateDictationNotification();
 }
 
 void AccessibilityManager::GetDlcContents(DlcType dlc,
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.h b/chrome/browser/ash/accessibility/accessibility_manager.h
index fc1f3830..bdd39c24 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.h
+++ b/chrome/browser/ash/accessibility/accessibility_manager.h
@@ -521,13 +521,18 @@
   bool ShouldShowSodaSucceededNotificationForDictation();
   bool ShouldShowSodaFailedNotificationForDictation(
       speech::LanguageCode language_code);
-  void ShowSodaDownloadNotificationForDictation(bool succeeded);
+
+  // Updates the Dictation notification according to DLC states. Assumes that
+  // it's only called when a Dictation-related DLC has downloaded (or failed to
+  // download).
+  void UpdateDictationNotification();
 
   void ShowDictationLanguageUpgradedNudge(const std::string& locale);
   speech::LanguageCode GetDictationLanguageCode();
 
   void CreateChromeVoxPanel();
 
+  // Pumpkin-related methods.
   void OnPumpkinInstalled(bool success);
   void OnPumpkinError(const std::string& error);
   void OnPumpkinDataCreated(
@@ -644,7 +649,7 @@
   friend class DictationTest;
   friend class SwitchAccessTest;
   friend class AccessibilityManagerTest;
-  friend class AccessibilityManagerSodaTest;
+  friend class AccessibilityManagerDlcTest;
   friend class AccessibilityManagerDictationDialogTest;
   friend class AccessibilityManagerNoOnDeviceSpeechRecognitionTest;
 };
diff --git a/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc b/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
index 7bc78ddd..9fadbab 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/run_loop.h"
+#include "base/strings/string_util.h"
 #include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
 #include "chrome/browser/ash/accessibility/magnification_manager.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
@@ -67,6 +68,27 @@
 constexpr char kTestUserName[] = "owner@gmail.com";
 constexpr char kTestUserGaiaId[] = "9876543210";
 
+// Dictation notification titles and descriptions. '*'s are used as placeholders
+// for languages, which are substituted in at a later time.
+std::u16string kDictationAllDlcsDownloadedTitle = u"* speech files downloaded";
+std::u16string kDictationAllDlcsDownloadedDesc =
+    u"Speech is now processed locally and Dictation works offline";
+std::u16string kDictationNoDlcsDownloadedTitle =
+    u"Couldn't download * speech files";
+std::u16string kDictationNoDlcsDownloadedDesc =
+    u"Download will be attempted later. Speech will be sent to Google for "
+    u"processing for now.";
+std::u16string kDicationOnlyPumpkinDownloadedTitle =
+    u"* speech files partially downloaded";
+std::u16string kDicationOnlyPumpkinDownloadedDesc =
+    u"Download will be attempted later. Speech will be sent to Google for "
+    u"processing for now.";
+std::u16string kDictationOnlySodaDownloadedTitle =
+    u"* speech files partially downloaded";
+std::u16string kDictationOnlySodaDownloadedDesc =
+    u"Speech is processed locally and dictation works offline, but some voice "
+    u"commands won’t work.";
+
 constexpr int kTestAutoclickDelayMs = 2000;
 
 class MockAccessibilityObserver {
@@ -315,31 +337,60 @@
   return offline_nudges.FindBool(locale);
 }
 
-void AssertSodaNotificationShownForDictation(
-    const std::u16string& display_language,
-    bool success) {
-  const std::u16string kTitle =
-      success ? display_language + u" speech files downloaded"
-              : u"Couldn't download " + display_language + u" speech files";
-  const std::u16string kDescription =
-      success ? u"Speech is now processed locally and Dictation works offline"
-              : u"Download will be attempted later. Speech will be sent to "
-                u"Google for processing until download is completed.";
-  message_center::SystemNotificationWarningLevel warning =
-      success
-          ? message_center::SystemNotificationWarningLevel::NORMAL
-          : message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
+void AssertDictationNotificationShown(const std::u16string& display_language,
+                                      const std::u16string& title,
+                                      const std::u16string& description,
+                                      bool is_critical) {
+  // Replace the '*' placeholder in `title` with `display_name`.
+  std::u16string new_title = title;
+  ASSERT_TRUE(
+      base::ReplaceChars(new_title, u"*", display_language, &new_title));
 
   message_center::NotificationList::Notifications notifications =
       message_center::MessageCenter::Get()->GetVisibleNotifications();
   ASSERT_EQ(1u, notifications.size());
-  ASSERT_EQ(kTitle, (*notifications.begin())->title());
-  ASSERT_EQ(kDescription, (*notifications.begin())->message());
+  ASSERT_EQ(new_title, (*notifications.begin())->title());
+  ASSERT_EQ(description, (*notifications.begin())->message());
   ASSERT_EQ(u"Dictation", (*notifications.begin())->display_source());
+
+  message_center::SystemNotificationWarningLevel warning =
+      is_critical
+          ? message_center::SystemNotificationWarningLevel::CRITICAL_WARNING
+          : message_center::SystemNotificationWarningLevel::NORMAL;
   ASSERT_EQ(warning,
             (*notifications.begin())->system_notification_warning_level());
 }
 
+void AssertDictationAllDlcsNotifcation(const std::u16string& display_language) {
+  AssertDictationNotificationShown(display_language,
+                                   kDictationAllDlcsDownloadedTitle,
+                                   kDictationAllDlcsDownloadedDesc,
+                                   /*is_critical=*/false);
+}
+
+void AssertDictationNoDlcsNotifcation(const std::u16string& display_language) {
+  AssertDictationNotificationShown(display_language,
+                                   kDictationNoDlcsDownloadedTitle,
+                                   kDictationNoDlcsDownloadedDesc,
+                                   /*is_critical=*/true);
+}
+
+void AssertDictationOnlySodaNotifcation(
+    const std::u16string& display_language) {
+  AssertDictationNotificationShown(display_language,
+                                   kDictationOnlySodaDownloadedTitle,
+                                   kDictationOnlySodaDownloadedDesc,
+                                   /*is_critical=*/true);
+}
+
+void AssertDictationOnlyPumpkinNotifcation(
+    const std::u16string& display_language) {
+  AssertDictationNotificationShown(display_language,
+                                   kDicationOnlyPumpkinDownloadedTitle,
+                                   kDicationOnlyPumpkinDownloadedDesc,
+                                   /*is_critical=*/true);
+}
+
 void AssertMessageCenterEmpty() {
   message_center::NotificationList::Notifications notifications =
       message_center::MessageCenter::Get()->GetVisibleNotifications();
@@ -860,19 +911,22 @@
       TtsDlcTypeToPath(DlcType::DLC_TYPE_TTSSVSE));
 }
 
-class AccessibilityManagerSodaTest : public AccessibilityManagerTest {
- protected:
-  AccessibilityManagerSodaTest()
+class AccessibilityManagerDlcTest : public AccessibilityManagerTest {
+ public:
+  AccessibilityManagerDlcTest()
       : disable_animations_(
             ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {}
-  ~AccessibilityManagerSodaTest() override = default;
-  AccessibilityManagerSodaTest(const AccessibilityManagerSodaTest&) = delete;
-  AccessibilityManagerSodaTest& operator=(const AccessibilityManagerSodaTest&) =
+  ~AccessibilityManagerDlcTest() override = default;
+  AccessibilityManagerDlcTest(const AccessibilityManagerDlcTest&) = delete;
+  AccessibilityManagerDlcTest& operator=(const AccessibilityManagerDlcTest&) =
       delete;
 
+ protected:
   void SetUpOnMainThread() override {
     UninstallSodaForTesting();
     EnsureSodaObservation();
+    ClearMessageCenter();
+    AssertMessageCenterEmpty();
     AccessibilityManagerTest::SetUpOnMainThread();
   }
 
@@ -888,6 +942,20 @@
       AccessibilityManager::Get()->soda_observation_.Observe(soda_installer());
   }
 
+  void InstallPumpkinAndWait() {
+    base::RunLoop loop;
+    AccessibilityManager::Get()->InstallPumpkinForDictation(base::DoNothing());
+    loop.RunUntilIdle();
+  }
+
+  void OnPumpkinError() {
+    AccessibilityManager* manager = AccessibilityManager::Get();
+    // We require `install_pumpkin_callback_` to be set before `OnPumpkinError`
+    // can be called.
+    manager->install_pumpkin_callback_ = base::DoNothing();
+    AccessibilityManager::Get()->OnPumpkinError("Error");
+  }
+
   speech::SodaInstaller* soda_installer() {
     return speech::SodaInstaller::GetInstance();
   }
@@ -901,11 +969,12 @@
 
  private:
   ui::ScopedAnimationDurationScaleMode disable_animations_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests that SODA download is initiated when Dictation is enabled.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       DownloadWhenDictationEnabled) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaDownloadWhenDictationEnabled) {
   ClearDictationOfflineNudgePref("en-US");
   EXPECT_FALSE(IsSodaDownloading());
   EXPECT_FALSE(ShouldShowNetworkDictationDialog("en-US"));
@@ -921,7 +990,7 @@
   EXPECT_FALSE(GetDictationOfflineNudgePref("en-US"));
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaDownloadNotTriggeredByUserShowsNudge) {
   SetDictationEnabled(true);
   // Reset to initial state: no SODA, no locale. Then trigger the dictation
@@ -942,7 +1011,7 @@
   AssertMessageCenterEmpty();
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaErrorNotTriggeredByUserTriesToShowNudge) {
   SetDictationEnabled(true);
   // Reset to initial state: no SODA, no locale. Then trigger the dictation
@@ -963,7 +1032,7 @@
   AssertMessageCenterEmpty();
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        OneNudgeForSodaMultipleDownload) {
   ClearDictationOfflineNudgePref("en-US");
   EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/true);
@@ -985,7 +1054,7 @@
   EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaInstalledBeforeDictationEnabled) {
   soda_installer()->NotifySodaInstalledForTesting();
   soda_installer()->NotifySodaInstalledForTesting(en_us());
@@ -999,7 +1068,7 @@
   EXPECT_TRUE(GetDictationOfflineNudgePref("en-US").value());
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaDownloadTriggeredByLocaleChange) {
   EXPECT_FALSE(IsSodaDownloading());
 
@@ -1021,15 +1090,14 @@
   // The nudge was never shown because this was a user-initiated change.
   EXPECT_FALSE(GetDictationOfflineNudgePref("en-US"));
   // The notification was shown.
-  AssertSodaNotificationShownForDictation(u"English (United States)",
-                                          /*success=*/true);
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
 }
 
 // Ensures that we show the SODA succeeded notification for Dictation if the
 // SODA binary downloads, followed by the language pack matching the dictation
 // language.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       SucceededNotificationCase1) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaSucceededNotificationCase1) {
   // For this test, pretend that the Dictation locale is fr-FR.
   g_browser_process->SetApplicationLocale("fr-FR");
   SetDictationEnabled(true);
@@ -1037,58 +1105,53 @@
   soda_installer()->NotifySodaInstalledForTesting(en_us());
   AssertMessageCenterEmpty();
   soda_installer()->NotifySodaInstalledForTesting(fr_fr());
-  AssertSodaNotificationShownForDictation(u"français (France)",
-                                          /*success=*/true);
+  AssertDictationOnlySodaNotifcation(u"français (France)");
 }
 
 // Similar to above. Ensures that we show the SODA succeeded notification for
 // Dictation if the language pack downloads, followed by the SODA binary.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       SucceededNotificationCase2) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaSucceededNotificationCase2) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaInstalledForTesting(en_us());
   AssertMessageCenterEmpty();
   soda_installer()->NotifySodaInstalledForTesting();
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/true);
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
 }
 
 // Ensures that we show the SODA failed notification for Dictation if the SODA
 // binary fails.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaFailedNotificationBinaryError) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaErrorForTesting();
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
 }
 
 // Similar to above. Ensures that we show the SODA failed notification for
 // Dictation if the language pack fails.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaFailedNotificationLanguageError) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaErrorForTesting(en_us());
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
 }
 
 // Ensures that the SODA failed notification for Dictation is given if
 // the language pack downloads, but the SODA binary fails.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       LanguageInstalledBinaryFails) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaLanguageInstalledBinaryFails) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaInstalledForTesting(en_us());
   AssertMessageCenterEmpty();
   soda_installer()->NotifySodaErrorForTesting();
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
 }
 
 // Similar to above. Ensures that we show the SODA failed notification if the
 // SODA binary downloads, but the language pack fails.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       BinaryInstalledLanguageFails) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaBinaryInstalledLanguageFails) {
   // For this test, pretend that the Dictation locale is fr-FR.
   g_browser_process->SetApplicationLocale("fr-FR");
   SetDictationEnabled(true);
@@ -1096,18 +1159,16 @@
   soda_installer()->NotifySodaInstalledForTesting(en_us());
   AssertMessageCenterEmpty();
   soda_installer()->NotifySodaErrorForTesting(fr_fr());
-  AssertSodaNotificationShownForDictation(u"français (France)",
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(u"français (France)");
 }
 
 // Ensures that SODA failed notification is shown just once if both the SODA
 // binary fails and the language pack fails.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaFailedNotificationNotShownTwice) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaErrorForTesting(en_us());
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
   ClearMessageCenter();
 
   // No second message is shown on additional failures.
@@ -1119,12 +1180,11 @@
 
 // Ensures that SODA failed notification could be shown each time Dictation
 // is toggled on.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
                        SodaFailedNotificationShownOncePerDownload) {
   SetDictationEnabled(true);
   soda_installer()->NotifySodaErrorForTesting(en_us());
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
   SetDictationEnabled(false);
 
   // Reset SODA state so that it tries the download again.
@@ -1134,13 +1194,12 @@
   // A fresh attempt at Dictation means another chance to show an error message.
   SetDictationEnabled(true);
   soda_installer()->NotifySodaErrorForTesting(en_us());
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/false);
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
 }
 
 // Tests that the SODA download notification for Dictation is NOT given if
 // Dictation wasn't triggered by the user.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest, NotTriggeredByUser) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaNotTriggeredByUser) {
   EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);
   soda_installer()->NotifySodaInstalledForTesting();
   soda_installer()->NotifySodaInstalledForTesting(en_us());
@@ -1149,7 +1208,7 @@
 
 // Tests that the SODA download notification for Dictation is NOT given if
 // the installed language doesn't match the Dictation locale.
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest, WrongLanguage) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaWrongLanguage) {
   // For this test, pretend that the Dictation locale is fr-FR.
   g_browser_process->SetApplicationLocale("fr-FR");
   SetDictationEnabled(true);
@@ -1159,8 +1218,8 @@
   AssertMessageCenterEmpty();
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityManagerSodaTest,
-                       NotificationShownOnDictationLocaleChange) {
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest,
+                       SodaNotificationShownOnDictationLocaleChange) {
   // it-IT is not supported by SODA.
   SetDictationLocale("it-IT");
   EnableDictationTriggeredByUser(/*soda_uninstalled_first=*/false);
@@ -1173,8 +1232,7 @@
   soda_installer()->NotifySodaInstalledForTesting(en_us());
 
   // The notification should have been shown.
-  AssertSodaNotificationShownForDictation(en_us_display_name(),
-                                          /*success=*/true);
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
 }
 
 // Tests the behavior of AccessibilityManager and when it calls the
@@ -1182,7 +1240,7 @@
 // The method is used to update the Dictation button tray when SODA download
 // state changes.
 IN_PROC_BROWSER_TEST_F(
-    AccessibilityManagerSodaTest,
+    AccessibilityManagerDlcTest,
     UpdateDictationButtonOnSpeechRecognitionDownloadChanged) {
   SetDictationLocale("en-US");
   SetDictationEnabled(true);
@@ -1208,6 +1266,51 @@
   EXPECT_EQ(100, test_api->GetDictationSodaDownloadProgress());
 }
 
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaSuccessPumpkinSuccess) {
+  SetDictationLocale("en-US");
+  SetDictationEnabled(true);
+  soda_installer()->NotifySodaInstalledForTesting(en_us());
+  AssertMessageCenterEmpty();
+  soda_installer()->NotifySodaInstalledForTesting();
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
+
+  InstallPumpkinAndWait();
+  AssertDictationAllDlcsNotifcation(en_us_display_name());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaSuccessPumpkinFail) {
+  SetDictationLocale("en-US");
+  SetDictationEnabled(true);
+  soda_installer()->NotifySodaInstalledForTesting(en_us());
+  AssertMessageCenterEmpty();
+  soda_installer()->NotifySodaInstalledForTesting();
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
+
+  OnPumpkinError();
+  AssertDictationOnlySodaNotifcation(en_us_display_name());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaFailPumpkinSuccess) {
+  // Calling `InstallPumpkinAndWait()` will run the message loop and cause SODA
+  // to be installed. Avoid this scenario for the purposes of this test.
+  soda_installer()->NeverDownloadSodaForTesting();
+  SetDictationLocale("en-US");
+  SetDictationEnabled(true);
+  soda_installer()->NotifySodaErrorForTesting(en_us());
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
+  InstallPumpkinAndWait();
+  AssertDictationOnlyPumpkinNotifcation(en_us_display_name());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityManagerDlcTest, SodaFailPumpkinFail) {
+  SetDictationLocale("en-US");
+  SetDictationEnabled(true);
+  soda_installer()->NotifySodaErrorForTesting(en_us());
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
+  OnPumpkinError();
+  AssertDictationNoDlcsNotifcation(en_us_display_name());
+}
+
 enum DictationDialogTestVariant {
   kOfflineEnabledAndAvailable,
   kOfflineEnabledAndUnavailable,
diff --git a/chrome/browser/ash/accessibility/pumpkin_installer.cc b/chrome/browser/ash/accessibility/pumpkin_installer.cc
index 02bb3fdb..7534f5f5 100644
--- a/chrome/browser/ash/accessibility/pumpkin_installer.cc
+++ b/chrome/browser/ash/accessibility/pumpkin_installer.cc
@@ -57,6 +57,7 @@
       OnError(kPumpkinInstallingError);
       return;
     case dlcservice::DlcState_State_INSTALLED:
+      is_pumpkin_installed_ = true;
       CHECK(!on_installed_.is_null());
       std::move(on_installed_).Run(true);
       return;
@@ -77,6 +78,7 @@
 void PumpkinInstaller::OnInstalled(
     const DlcserviceClient::InstallResult& install_result) {
   pending_dlc_request_ = false;
+  is_pumpkin_installed_ = true;
   base::UmaHistogramBoolean(kInstallationMetricName,
                             install_result.error == dlcservice::kErrorNone);
   if (install_result.error != dlcservice::kErrorNone) {
@@ -93,6 +95,7 @@
 }
 
 void PumpkinInstaller::OnError(const std::string& error) {
+  is_pumpkin_installed_ = false;
   CHECK(!on_error_.is_null());
   std::move(on_error_).Run(error);
 }
diff --git a/chrome/browser/ash/accessibility/pumpkin_installer.h b/chrome/browser/ash/accessibility/pumpkin_installer.h
index 5d705f4..33f761e 100644
--- a/chrome/browser/ash/accessibility/pumpkin_installer.h
+++ b/chrome/browser/ash/accessibility/pumpkin_installer.h
@@ -33,6 +33,8 @@
                     ProgressCallback on_progress,
                     ErrorCallback on_error);
 
+  bool IsPumpkinInstalled() const { return is_pumpkin_installed_; }
+
  private:
   // A helper function that is run once we've grabbed the state of the Pumpkin
   // DLC from the DLC service.
@@ -52,6 +54,7 @@
   // Requests to DlcserviceClient are async. This is true if we've made a
   // request and are still waiting for a response.
   bool pending_dlc_request_ = false;
+  bool is_pumpkin_installed_ = false;
 
   base::WeakPtrFactory<PumpkinInstaller> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/app_list/app_service/app_service_context_menu.cc b/chrome/browser/ash/app_list/app_service/app_service_context_menu.cc
index 338f05bb..dbf93738 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_context_menu.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_context_menu.cc
@@ -37,7 +37,6 @@
 #include "chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/app_constants/constants.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "components/services/app_service/public/cpp/types_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -277,12 +276,7 @@
 
         if (app_type_ == apps::AppType::kWeb &&
             command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW) {
-          if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
-            proxy_->SetWindowMode(app_id(), apps::WindowMode::kTabbedWindow);
-          } else {
-            proxy_->SetWindowMode(app_id(),
-                                  apps::mojom::WindowMode::kTabbedWindow);
-          }
+          proxy_->SetWindowMode(app_id(), apps::WindowMode::kTabbedWindow);
           return;
         }
 
@@ -500,13 +494,7 @@
       apps::WindowMode user_window_mode =
           ConvertUseLaunchTypeCommandToWindowMode(command_id);
       if (user_window_mode != apps::WindowMode::kUnknown) {
-        if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
-          proxy_->SetWindowMode(app_id(), user_window_mode);
-        } else {
-          proxy_->SetWindowMode(
-              app_id(),
-              apps::ConvertWindowModeToMojomWindowMode(user_window_mode));
-        }
+        proxy_->SetWindowMode(app_id(), user_window_mode);
       }
       return;
     }
diff --git a/chrome/browser/ash/app_list/search/burnin_controller.cc b/chrome/browser/ash/app_list/search/burn_in_controller.cc
similarity index 97%
rename from chrome/browser/ash/app_list/search/burnin_controller.cc
rename to chrome/browser/ash/app_list/search/burn_in_controller.cc
index f397b0c3..73cc0c1 100644
--- a/chrome/browser/ash/app_list/search/burnin_controller.cc
+++ b/chrome/browser/ash/app_list/search/burn_in_controller.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 "chrome/browser/ash/app_list/search/burnin_controller.h"
+#include "chrome/browser/ash/app_list/search/burn_in_controller.h"
 
 #include "base/containers/flat_set.h"
 #include "chrome/browser/ash/app_list/search/chrome_search_result.h"
diff --git a/chrome/browser/ash/app_list/search/burnin_controller.h b/chrome/browser/ash/app_list/search/burn_in_controller.h
similarity index 94%
rename from chrome/browser/ash/app_list/search/burnin_controller.h
rename to chrome/browser/ash/app_list/search/burn_in_controller.h
index b31d7ad..82f5418 100644
--- a/chrome/browser/ash/app_list/search/burnin_controller.h
+++ b/chrome/browser/ash/app_list/search/burn_in_controller.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 CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
-#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURN_IN_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURN_IN_CONTROLLER_H_
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/callback.h"
@@ -99,4 +99,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURN_IN_CONTROLLER_H_
diff --git a/chrome/browser/ash/app_list/search/search_controller_impl.h b/chrome/browser/ash/app_list/search/search_controller_impl.h
index 6766a003..258143e 100644
--- a/chrome/browser/ash/app_list/search/search_controller_impl.h
+++ b/chrome/browser/ash/app_list/search/search_controller_impl.h
@@ -16,7 +16,7 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/ash/app_list/search/burnin_controller.h"
+#include "chrome/browser/ash/app_list/search/burn_in_controller.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h"
 #include "chrome/browser/ash/app_list/search/search_controller.h"
diff --git a/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc b/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc
index 3a28f61..6a6e3e3 100644
--- a/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc
@@ -11,20 +11,14 @@
 #include "base/strings/strcat.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/ash/app_list/search/search_metrics_util.h"
+#include "chrome/browser/ash/app_list/search/test/search_metrics_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace app_list::test {
 namespace {
 
-using Result = ash::AppListNotifier::Result;
-using Location = ash::AppListNotifier::Location;
-using Type = ash::SearchResultType;
 using Action = SearchMetricsManager::Action;
 
-Result CreateFakeResult(Type type, const std::string& id = "fake_id") {
-  return Result(id, type);
-}
-
 }  // namespace
 
 class SearchMetricsManagerTest : public testing::Test {
@@ -56,8 +50,8 @@
   histogram_tester_.ExpectTotalCount(action_histogram, 0);
 
   // Metrics Updated for non_empty results.
-  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD));
-  results.emplace_back(CreateFakeResult(Type::KEYBOARD_SHORTCUT));
+  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::KEYBOARD_SHORTCUT, "result_id"));
   metrics_manager_->OnImpression(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 2);
@@ -68,7 +62,7 @@
                                        1);
 
   // Duplicate type is counted once only.
-  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD));
+  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD, "result_id"));
   metrics_manager_->OnImpression(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 4);
@@ -100,9 +94,9 @@
 
   // Without drive results.
   std::vector<Result> results;
-  results.emplace_back(CreateFakeResult(Type::ZERO_STATE_FILE));
-  results.emplace_back(CreateFakeResult(Type::HELP_APP_UPDATES));
-  results.emplace_back(CreateFakeResult(Type::HELP_APP_UPDATES));
+  results.emplace_back(CreateFakeResult(Type::ZERO_STATE_FILE, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::HELP_APP_UPDATES, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::HELP_APP_UPDATES, "result_id"));
   metrics_manager_->OnImpression(location, results, query);
 
   histogram_tester_.ExpectUniqueSample(total_histogram, 3, 1);
@@ -112,7 +106,7 @@
   histogram_tester_.ExpectUniqueSample(bool_histogram, false, 1);
 
   // With drive results.
-  results.emplace_back(CreateFakeResult(Type::ZERO_STATE_DRIVE));
+  results.emplace_back(CreateFakeResult(Type::ZERO_STATE_DRIVE, "result_id"));
   metrics_manager_->OnImpression(location, results, query);
 
   histogram_tester_.ExpectTotalCount(total_histogram, 2);
@@ -150,8 +144,8 @@
   histogram_tester_.ExpectTotalCount(action_histogram, 0);
 
   // Metrics Updated for non_empty results.
-  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH));
-  results.emplace_back(CreateFakeResult(Type::ASSISTANT));
+  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::ASSISTANT, "result_id"));
   metrics_manager_->OnAbandon(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 2);
@@ -160,7 +154,7 @@
   histogram_tester_.ExpectUniqueSample(action_histogram, Action::kAbandon, 1);
 
   // Duplicate type is counted once only.
-  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH));
+  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH, "result_id"));
   metrics_manager_->OnAbandon(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 4);
@@ -231,8 +225,8 @@
   histogram_tester_.ExpectTotalCount(action_histogram, 0);
 
   // Metrics Updated for non_empty results.
-  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH));
-  results.emplace_back(CreateFakeResult(Type::ASSISTANT));
+  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::ASSISTANT, "result_id"));
   metrics_manager_->OnIgnore(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 2);
@@ -241,7 +235,7 @@
   histogram_tester_.ExpectUniqueSample(action_histogram, Action::kIgnore, 1);
 
   // Duplicate type is counted once only.
-  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH));
+  results.emplace_back(CreateFakeResult(Type::FILE_SEARCH, "result_id"));
   metrics_manager_->OnIgnore(location, results, query);
 
   histogram_tester_.ExpectTotalCount(view_histogram, 4);
diff --git a/chrome/browser/ash/app_list/search/search_metrics_util.cc b/chrome/browser/ash/app_list/search/search_metrics_util.cc
index 63a4738d8..5b3fca1 100644
--- a/chrome/browser/ash/app_list/search/search_metrics_util.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_util.cc
@@ -14,4 +14,30 @@
                                 error);
 }
 
+void LogSessionError(Error error) {
+  base::UmaHistogramEnumeration(
+      base::StrCat({kSessionHistogramPrefix, "Error"}), error);
+}
+
+std::string GetAppListOpenMethod(ash::AppListShowSource source) {
+  // This switch determines which metric we submit for the Apps.AppListOpenTime
+  // metric. Adding a string requires you update the apps histogram.xml as well.
+  switch (source) {
+    case ash::AppListShowSource::kSearchKey:
+    case ash::AppListShowSource::kSearchKeyFullscreen_DEPRECATED:
+      return "SearchKey";
+    case ash::AppListShowSource::kShelfButton:
+    case ash::AppListShowSource::kShelfButtonFullscreen_DEPRECATED:
+      return "HomeButton";
+    case ash::AppListShowSource::kSwipeFromShelf:
+      return "Swipe";
+    case ash::AppListShowSource::kScrollFromShelf:
+      return "Scroll";
+    case ash::AppListShowSource::kTabletMode:
+    case ash::AppListShowSource::kAssistantEntryPoint:
+    case ash::AppListShowSource::kBrowser:
+      return "Others";
+  }
+}
+
 }  // namespace app_list
diff --git a/chrome/browser/ash/app_list/search/search_metrics_util.h b/chrome/browser/ash/app_list/search/search_metrics_util.h
index 7161f74c..0271fb7 100644
--- a/chrome/browser/ash/app_list/search/search_metrics_util.h
+++ b/chrome/browser/ash/app_list/search/search_metrics_util.h
@@ -5,13 +5,18 @@
 #ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
 
+#include <string>
+
+#include "ash/public/cpp/app_list/app_list_metrics.h"
+
 namespace app_list {
 
 constexpr char kHistogramPrefix[] = "Apps.AppList.Search.";
+constexpr char kSessionHistogramPrefix[] = "Apps.AppList.Search.Session.";
 
 // Represents possible error states of the metrics observer itself. These
-// values persist to logs. Entries should not be renumbered and numeric values
-// should never be reused.
+// values persist to logs. Entries should not be renumbered and numeric
+// values should never be reused.
 enum class Error {
   kMissingNotifier = 0,
   kResultNotFound = 1,
@@ -21,6 +26,9 @@
 };
 
 void LogError(Error error);
+void LogSessionError(Error error);
+
+std::string GetAppListOpenMethod(ash::AppListShowSource source);
 
 }  // namespace app_list
 
diff --git a/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc b/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
index 9435f055..44e2e0e3 100644
--- a/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/ash/app_list/search/search_session_metrics_manager.h"
 
+#include "ash/public/cpp/app_list/app_list_controller.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 #include "chrome/browser/ash/app_list/search/search_metrics_util.h"
 
 namespace app_list {
@@ -20,16 +23,21 @@
   if (notifier) {
     observation_.Observe(notifier);
   } else {
-    LogError(Error::kMissingNotifier);
+    LogSessionError(Error::kMissingNotifier);
   }
 }
 
 SearchSessionMetricsManager::~SearchSessionMetricsManager() = default;
 
 void SearchSessionMetricsManager::EndSearchSession() {
-  // TODO (crbug/1380563) Log search metrics
+  std::string show_source = GetAppListOpenMethod(
+      ash::AppListController::Get()->LastAppListShowSource());
+
+  base::UmaHistogramEnumeration(
+      base::StrCat({kSessionHistogramPrefix, show_source}), session_result_);
+
+  session_result_ = ash::SearchSessionResult::kQuit;
   session_active_ = false;
-  session_answer_card_impression_ = false;
 }
 
 void SearchSessionMetricsManager::OnSearchSessionStarted() {
@@ -46,7 +54,7 @@
     const std::u16string& query) {
   if (location == Location::kAnswerCard) {
     DCHECK(session_active_);
-    session_answer_card_impression_ = true;
+    session_result_ = ash::SearchSessionResult::kAnswerCardImpression;
   }
 }
 
@@ -56,8 +64,7 @@
                                            const std::u16string& query) {
   if (location == Location::kList) {
     DCHECK(session_active_);
-    result_launched_ = true;
-    // TODO (crbug/1380563) Log search metrics
+    session_result_ = ash::SearchSessionResult::kLaunch;
   }
   EndSearchSession();
 }
diff --git a/chrome/browser/ash/app_list/search/search_session_metrics_manager.h b/chrome/browser/ash/app_list/search/search_session_metrics_manager.h
index 3565c857..e706836 100644
--- a/chrome/browser/ash/app_list/search/search_session_metrics_manager.h
+++ b/chrome/browser/ash/app_list/search/search_session_metrics_manager.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "ash/public/cpp/app_list/app_list_notifier.h"
 #include "base/scoped_observation.h"
 
@@ -45,10 +46,9 @@
  private:
   // Whether the metrics manager is tracking an active search session.
   bool session_active_ = false;
-  // Whether the current search session has had an answer card impression.
-  bool session_answer_card_impression_ = false;
-  // Whether a result was launched during the search session.
-  bool result_launched_ = false;
+  // Tracks the metric recorded when EndSearchSession() is called.
+  ash::SearchSessionResult session_result_ = ash::SearchSessionResult::kQuit;
+
   base::ScopedObservation<ash::AppListNotifier, ash::AppListNotifier::Observer>
       observation_{this};
 };
diff --git a/chrome/browser/ash/app_list/search/search_session_metrics_manager_unittest.cc b/chrome/browser/ash/app_list/search/search_session_metrics_manager_unittest.cc
new file mode 100644
index 0000000..9527f0e0
--- /dev/null
+++ b/chrome/browser/ash/app_list/search/search_session_metrics_manager_unittest.cc
@@ -0,0 +1,135 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/search/search_session_metrics_manager.h"
+
+#include <memory>
+
+#include "ash/public/cpp/app_list/app_list_metrics.h"
+#include "base/strings/strcat.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_util.h"
+#include "chrome/browser/ash/app_list/search/test/search_metrics_test_util.h"
+#include "chrome/browser/ash/app_list/test/test_app_list_controller.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace app_list::test {
+
+namespace {
+
+using Type = ash::SearchResultType;
+
+constexpr char HomeButtonHistogram[] = "Apps.AppList.Search.Session.HomeButton";
+constexpr char SearchKeyHistogram[] = "Apps.AppList.Search.Session.SearchKey";
+
+}  // namespace
+
+class SearchSessionMetricsManagerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    histogram_tester_ = std::make_unique<base::HistogramTester>();
+    metrics_manager_ = std::make_unique<app_list::SearchSessionMetricsManager>(
+        nullptr, nullptr);
+    app_list_controller_ = std::make_unique<::test::TestAppListController>();
+  }
+
+  ::test::TestAppListController* app_list_controller() {
+    return app_list_controller_.get();
+  }
+
+  base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }
+
+  app_list::SearchSessionMetricsManager* metrics_manager() {
+    return metrics_manager_.get();
+  }
+
+ protected:
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
+  std::unique_ptr<app_list::SearchSessionMetricsManager> metrics_manager_;
+  std::unique_ptr<::test::TestAppListController> app_list_controller_;
+};
+
+TEST_F(SearchSessionMetricsManagerTest, AnswerCardImpression) {
+  // The kMissingNotifier log comes from the constructor with null notifier.
+  histogram_tester()->ExpectUniqueSample(
+      base::StrCat({app_list::kSessionHistogramPrefix, "Error"}),
+      app_list::Error::kMissingNotifier, 1);
+
+  Location location = Location::kAnswerCard;
+  const std::u16string query = u"query";
+
+  app_list_controller()->ShowAppList(ash::AppListShowSource::kSearchKey);
+  std::vector<Result> results;
+  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::KEYBOARD_SHORTCUT, "result_id"));
+
+  metrics_manager()->OnSearchSessionStarted();
+  metrics_manager_->OnImpression(location, results, query);
+
+  // No metrics should be recorded until the search session ends.
+  histogram_tester()->ExpectTotalCount(HomeButtonHistogram, 0);
+  histogram_tester()->ExpectTotalCount(SearchKeyHistogram, 0);
+
+  // One answer card impression should be recorded for the kSearchKey show
+  // source.
+  metrics_manager()->OnSearchSessionEnded();
+  histogram_tester()->ExpectTotalCount(HomeButtonHistogram, 0);
+  histogram_tester()->ExpectTotalCount(SearchKeyHistogram, 1);
+  histogram_tester()->ExpectUniqueSample(
+      SearchKeyHistogram, ash::SearchSessionResult::kAnswerCardImpression, 1);
+}
+
+TEST_F(SearchSessionMetricsManagerTest, LaunchResult) {
+  // The kMissingNotifier log comes from the constructor with null notifier.
+  histogram_tester()->ExpectUniqueSample(
+      base::StrCat({app_list::kSessionHistogramPrefix, "Error"}),
+      app_list::Error::kMissingNotifier, 1);
+
+  Location location = Location::kList;
+  const std::u16string query = u"query";
+
+  app_list_controller()->ShowAppList(ash::AppListShowSource::kSearchKey);
+  std::vector<Result> results;
+  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::KEYBOARD_SHORTCUT, "result_id"));
+  Result launched_result =
+      CreateFakeResult(Type::FILE_SEARCH, "fake_id_launched");
+  results.emplace_back(launched_result);
+
+  metrics_manager()->OnSearchSessionStarted();
+  metrics_manager_->OnImpression(location, results, query);
+
+  // No metrics should be recorded until the search session ends.
+  histogram_tester()->ExpectTotalCount(HomeButtonHistogram, 0);
+  histogram_tester()->ExpectTotalCount(SearchKeyHistogram, 0);
+
+  // No metrics should be recorded until the search session ends.
+  metrics_manager()->OnLaunch(location, launched_result, results, query);
+  histogram_tester()->ExpectTotalCount(HomeButtonHistogram, 0);
+  histogram_tester()->ExpectTotalCount(SearchKeyHistogram, 1);
+  histogram_tester()->ExpectUniqueSample(SearchKeyHistogram,
+                                         ash::SearchSessionResult::kLaunch, 1);
+}
+
+TEST_F(SearchSessionMetricsManagerTest, AbandonResult) {
+  // The kMissingNotifier log comes from the constructor with null notifier.
+  histogram_tester()->ExpectUniqueSample(
+      base::StrCat({app_list::kSessionHistogramPrefix, "Error"}),
+      app_list::Error::kMissingNotifier, 1);
+
+  app_list_controller()->ShowAppList(ash::AppListShowSource::kSearchKey);
+  std::vector<Result> results;
+  results.emplace_back(CreateFakeResult(Type::ANSWER_CARD, "result_id"));
+  results.emplace_back(CreateFakeResult(Type::KEYBOARD_SHORTCUT, "result_id"));
+
+  metrics_manager()->OnSearchSessionStarted();
+  metrics_manager()->OnSearchSessionEnded();
+
+  histogram_tester()->ExpectTotalCount(HomeButtonHistogram, 0);
+  histogram_tester()->ExpectTotalCount(SearchKeyHistogram, 1);
+  histogram_tester()->ExpectUniqueSample(SearchKeyHistogram,
+                                         ash::SearchSessionResult::kQuit, 1);
+}
+
+}  // namespace app_list::test
diff --git a/chrome/browser/ash/app_list/search/test/search_metrics_test_util.cc b/chrome/browser/ash/app_list/search/test/search_metrics_test_util.cc
new file mode 100644
index 0000000..ed02850
--- /dev/null
+++ b/chrome/browser/ash/app_list/search/test/search_metrics_test_util.cc
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/search/test/search_metrics_test_util.h"
+
+namespace app_list::test {
+
+Result CreateFakeResult(Type type, const std::string& id) {
+  return Result(id, type);
+}
+
+}  // namespace app_list::test
diff --git a/chrome/browser/ash/app_list/search/test/search_metrics_test_util.h b/chrome/browser/ash/app_list/search/test/search_metrics_test_util.h
new file mode 100644
index 0000000..afa863b
--- /dev/null
+++ b/chrome/browser/ash/app_list/search/test/search_metrics_test_util.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_SEARCH_METRICS_TEST_UTIL_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_SEARCH_METRICS_TEST_UTIL_H_
+
+#include <string>
+
+#include "ash/public/cpp/app_list/app_list_metrics.h"
+#include "ash/public/cpp/app_list/app_list_notifier.h"
+
+namespace app_list::test {
+
+using Result = ash::AppListNotifier::Result;
+using Location = ash::AppListNotifier::Location;
+using Type = ash::SearchResultType;
+
+Result CreateFakeResult(Type type, const std::string& id);
+
+}  // namespace app_list::test
+
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_SEARCH_METRICS_TEST_UTIL_H_
diff --git a/chrome/browser/ash/app_list/test/test_app_list_controller.cc b/chrome/browser/ash/app_list/test/test_app_list_controller.cc
index ab6cb696..3dc1f45 100644
--- a/chrome/browser/ash/app_list/test/test_app_list_controller.cc
+++ b/chrome/browser/ash/app_list/test/test_app_list_controller.cc
@@ -33,10 +33,16 @@
 }
 
 void TestAppListController::ShowAppList(ash::AppListShowSource source) {
+  last_open_source_ = source;
   visible_ = true;
   NotifyAppListVisibilityChanged();
 }
 
+ash::AppListShowSource TestAppListController::LastAppListShowSource() {
+  DCHECK(last_open_source_.has_value());
+  return last_open_source_.value();
+}
+
 void TestAppListController::DismissAppList() {
   visible_ = false;
   NotifyAppListVisibilityChanged();
diff --git a/chrome/browser/ash/app_list/test/test_app_list_controller.h b/chrome/browser/ash/app_list/test/test_app_list_controller.h
index 7dc414d..500cddf 100644
--- a/chrome/browser/ash/app_list/test/test_app_list_controller.h
+++ b/chrome/browser/ash/app_list/test/test_app_list_controller.h
@@ -36,6 +36,7 @@
                       ash::SearchModel* search_model) override {}
   void ClearActiveModel() override {}
   void ShowAppList(ash::AppListShowSource source) override;
+  ash::AppListShowSource LastAppListShowSource() override;
   void DismissAppList() override;
   void GetAppInfoDialogBounds(
       GetAppInfoDialogBoundsCallback callback) override {}
@@ -53,6 +54,9 @@
   // The visibility state set using (Show|Dismiss)AppList.
   bool visible_ = false;
 
+  // Tracks the most recent show source for the app list.
+  absl::optional<ash::AppListShowSource> last_open_source_;
+
   base::ObserverList<ash::AppListControllerObserver> observers_;
 };
 
diff --git a/chrome/browser/ash/app_restore/arc_ghost_window_delegate.cc b/chrome/browser/ash/app_restore/arc_ghost_window_delegate.cc
index ffa1049..e61cf9c 100644
--- a/chrome/browser/ash/app_restore/arc_ghost_window_delegate.cc
+++ b/chrome/browser/ash/app_restore/arc_ghost_window_delegate.cc
@@ -121,9 +121,9 @@
            requested_state == chromeos::WindowStateType::kSecondarySnapped);
 
     if (requested_state == chromeos::WindowStateType::kPrimarySnapped)
-      shell_surface_->SetSnappedToPrimary();
+      shell_surface_->SetSnapPrimary(chromeos::kDefaultSnapRatio);
     else
-      shell_surface_->SetSnappedToSecondary();
+      shell_surface_->SetSnapSecondary(chromeos::kDefaultSnapRatio);
     // TODO(sstan): Currently the snap state will be ignored. Sync it to ARC.
   }
   shell_surface_->OnSurfaceCommit();
diff --git a/chrome/browser/ash/bruschetta/bruschetta_download_client.cc b/chrome/browser/ash/bruschetta/bruschetta_download_client.cc
new file mode 100644
index 0000000..d92797f
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/bruschetta_download_client.cc
@@ -0,0 +1,98 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/bruschetta/bruschetta_download_client.h"
+
+#include "chrome/browser/download/background_download_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_key.h"
+#include "components/download/public/background_service/background_download_service.h"
+#include "components/download/public/background_service/download_metadata.h"
+#include "services/network/public/cpp/resource_request_body.h"
+
+namespace bruschetta {
+
+BruschettaInstaller* BruschettaDownloadClient::installer_ = nullptr;
+
+BruschettaDownloadClient::BruschettaDownloadClient(Profile* profile)
+    : profile_(profile) {}
+
+bool BruschettaDownloadClient::MaybeCancelDownload(const std::string& guid) {
+  if (!installer_ ||
+      guid != installer_->GetDownloadGuid().AsLowercaseString()) {
+    BackgroundDownloadServiceFactory::GetForKey(profile_->GetProfileKey())
+        ->CancelDownload(guid);
+    return true;
+  }
+  return false;
+}
+
+void BruschettaDownloadClient::OnServiceInitialized(
+    bool state_lost,
+    const std::vector<download::DownloadMetaData>& downloads) {
+  for (const auto& download : downloads) {
+    MaybeCancelDownload(download.guid);
+  }
+}
+
+void BruschettaDownloadClient::OnServiceUnavailable() {}
+
+void BruschettaDownloadClient::OnDownloadStarted(
+    const std::string& guid,
+    const std::vector<GURL>& url_chain,
+    const scoped_refptr<const net::HttpResponseHeaders>& headers) {
+  if (MaybeCancelDownload(guid)) {
+    return;
+  }
+}
+
+void BruschettaDownloadClient::OnDownloadUpdated(const std::string& guid,
+                                                 uint64_t bytes_uploaded,
+                                                 uint64_t bytes_downloaded) {
+  if (MaybeCancelDownload(guid)) {
+    return;
+  }
+}
+
+void BruschettaDownloadClient::OnDownloadFailed(
+    const std::string& guid,
+    const download::CompletionInfo& info,
+    FailureReason reason) {
+  if (MaybeCancelDownload(guid)) {
+    return;
+  }
+  LOG(ERROR) << "Failed to download object, error code "
+             << static_cast<int>(reason);
+  installer_->DownloadFailed();
+}
+
+void BruschettaDownloadClient::OnDownloadSucceeded(
+    const std::string& guid,
+    const download::CompletionInfo& completion_info) {
+  if (MaybeCancelDownload(guid)) {
+    return;
+  }
+  installer_->DownloadSucceeded(completion_info);
+}
+
+bool BruschettaDownloadClient::CanServiceRemoveDownloadedFile(
+    const std::string& guid,
+    bool force_delete) {
+  return !installer_;
+}
+
+void BruschettaDownloadClient::GetUploadData(
+    const std::string& guid,
+    download::GetUploadDataCallback callback) {
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), nullptr));
+}
+
+void BruschettaDownloadClient::SetInstallerInstance(
+    BruschettaInstaller* instance) {
+  DCHECK(!installer_ || !instance);
+  installer_ = instance;
+}
+
+}  // namespace bruschetta
diff --git a/chrome/browser/ash/bruschetta/bruschetta_download_client.h b/chrome/browser/ash/bruschetta/bruschetta_download_client.h
new file mode 100644
index 0000000..cdf05cd
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/bruschetta_download_client.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_DOWNLOAD_CLIENT_H_
+#define CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_DOWNLOAD_CLIENT_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_installer.h"
+#include "components/download/public/background_service/client.h"
+
+class Profile;
+
+namespace bruschetta {
+
+class BruschettaDownloadClient : public download::Client {
+ public:
+  explicit BruschettaDownloadClient(Profile* profile);
+
+  void OnServiceInitialized(
+      bool state_lost,
+      const std::vector<download::DownloadMetaData>& downloads) override;
+  void OnServiceUnavailable() override;
+  void OnDownloadStarted(
+      const std::string& guid,
+      const std::vector<GURL>& url_chain,
+      const scoped_refptr<const net::HttpResponseHeaders>& headers) override;
+  void OnDownloadUpdated(const std::string& guid,
+                         uint64_t bytes_uploaded,
+                         uint64_t bytes_downloaded) override;
+  void OnDownloadFailed(const std::string& guid,
+                        const download::CompletionInfo& info,
+                        FailureReason reason) override;
+  void OnDownloadSucceeded(
+      const std::string& guid,
+      const download::CompletionInfo& completion_info) override;
+  bool CanServiceRemoveDownloadedFile(const std::string& guid,
+                                      bool force_delete) override;
+  void GetUploadData(const std::string& guid,
+                     download::GetUploadDataCallback callback) override;
+
+  static void SetInstallerInstance(BruschettaInstaller* instance);
+
+ private:
+  // base::raw_ptr can't be used as a static member variable
+  static BruschettaInstaller* installer_;
+
+  bool MaybeCancelDownload(const std::string& guid);
+
+  const base::raw_ptr<Profile> profile_;
+};
+
+}  // namespace bruschetta
+
+#endif
diff --git a/chrome/browser/ash/bruschetta/bruschetta_installer.cc b/chrome/browser/ash/bruschetta/bruschetta_installer.cc
new file mode 100644
index 0000000..d5123b6
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/bruschetta_installer.cc
@@ -0,0 +1,554 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/bruschetta/bruschetta_installer.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/callback_forward.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_download_client.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_pref_names.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_service.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
+#include "chrome/browser/ash/crostini/crostini_util.h"
+#include "chrome/browser/ash/guest_os/guest_id.h"
+#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/download/background_download_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_key.h"
+#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
+#include "chromeos/ash/components/disks/disk_mount_manager.h"
+#include "components/download/public/background_service/background_download_service.h"
+#include "components/download/public/background_service/clients.h"
+#include "components/download/public/background_service/download_params.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace bruschetta {
+
+namespace {
+
+const net::NetworkTrafficAnnotationTag kBruschettaTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("bruschetta_installer_download",
+                                        R"(
+      semantics {
+        sender: "Bruschetta VM Installer",
+        description: "Request sent to download firmware and VM image for "
+          "a Bruschetta VM, which allows the user to run the VM."
+        trigger: "User installing a Bruschetta VM"
+        user_data: {
+          type: ACCESS_TOKEN
+        }
+        data: "Request to download Bruschetta firmware and VM image. "
+          "Sends cookies associated with the source to authenticate the user."
+        destination: WEBSITE
+      }
+      policy {
+        cookies_allowed: YES
+        cookies_store: "user"
+        chrome_policy {
+          BruschettaVMConfiguration {
+            BruschettaVMConfiguration: "{}"
+          }
+        }
+      }
+    )");
+
+absl::optional<std::pair<base::ScopedFD, base::ScopedFD>> OpenFdsBlocking(
+    base::FilePath firmware_path,
+    base::FilePath boot_disk_path,
+    base::FilePath profile_path);
+
+}  // namespace
+
+BruschettaInstaller::BruschettaInstaller(Profile* profile,
+                                         base::OnceClosure close_closure)
+    : profile_(profile), close_closure_(std::move(close_closure)) {
+  BruschettaDownloadClient::SetInstallerInstance(this);
+}
+
+BruschettaInstaller::~BruschettaInstaller() {
+  BruschettaDownloadClient::SetInstallerInstance(nullptr);
+}
+
+bool BruschettaInstaller::MaybeClose() {
+  if (!install_running_) {
+    std::move(close_closure_).Run();
+    return true;
+  }
+  return false;
+}
+
+void BruschettaInstaller::Cancel() {
+  if (download_guid_.is_valid()) {
+    BackgroundDownloadServiceFactory::GetForKey(profile_->GetProfileKey())
+        ->CancelDownload(download_guid_.AsLowercaseString());
+  }
+
+  if (MaybeClose())
+    return;
+
+  install_running_ = false;
+}
+
+void BruschettaInstaller::Install(std::string vm_name, std::string config_id) {
+  if (install_running_) {
+    LOG(ERROR) << "Install requested while an install is already running";
+    return;
+  }
+
+  NotifyObserver(State::kInstallStarted);
+
+  install_running_ = true;
+
+  auto config_ptr = GetInstallableConfig(profile_, config_id);
+  if (config_ptr.has_value()) {
+    config_ = config_ptr.value()->Clone();
+    config_id_ = std::move(config_id);
+    vm_name_ = std::move(vm_name);
+    InstallToolsDlc();
+  } else {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Installation prohibited by policy";
+    return;
+  }
+}
+
+void BruschettaInstaller::InstallToolsDlc() {
+  NotifyObserver(State::kDlcInstall);
+
+  dlcservice::InstallRequest request;
+  request.set_id(crostini::kCrostiniDlcName);
+  chromeos::DlcserviceClient::Get()->Install(
+      request,
+      base::BindOnce(&BruschettaInstaller::OnToolsDlcInstalled,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::DoNothing());
+}
+
+void BruschettaInstaller::OnToolsDlcInstalled(
+    const chromeos::DlcserviceClient::InstallResult& install_result) {
+  if (MaybeClose())
+    return;
+
+  if (install_result.error != dlcservice::kErrorNone) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Failed to install tools dlc: " << install_result.error;
+    return;
+  }
+
+  DownloadFirmware();
+}
+
+void BruschettaInstaller::StartDownload(GURL url, DownloadCallback callback) {
+  auto* download_service =
+      BackgroundDownloadServiceFactory::GetForKey(profile_->GetProfileKey());
+
+  download::DownloadParams params;
+
+  params.client = download::DownloadClient::BRUSCHETTA;
+
+  params.guid = download_guid_.AsLowercaseString();
+  params.callback = base::BindOnce(&BruschettaInstaller::DownloadStarted,
+                                   weak_ptr_factory_.GetWeakPtr());
+
+  download_callback_ = std::move(callback);
+
+  params.scheduling_params.priority = download::SchedulingParams::Priority::UI;
+  params.scheduling_params.network_requirements =
+      download::SchedulingParams::NetworkRequirements::NONE;
+  params.scheduling_params.battery_requirements =
+      download::SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+
+  params.traffic_annotation =
+      net::MutableNetworkTrafficAnnotationTag(kBruschettaTrafficAnnotation);
+
+  params.request_params.url = std::move(url);
+
+  download_service->StartDownload(std::move(params));
+}
+
+void BruschettaInstaller::DownloadStarted(
+    const std::string& guid,
+    download::DownloadParams::StartResult result) {
+  if (guid != download_guid_.AsLowercaseString()) {
+    LOG(ERROR) << "Got unexpected response from download service";
+    return;
+  }
+
+  if (result != download::DownloadParams::StartResult::ACCEPTED) {
+    LOG(ERROR) << "Download failed to start, error code " << result;
+    DownloadFailed();
+  }
+}
+
+void BruschettaInstaller::DownloadFailed() {
+  download_guid_ = base::GUID();
+  download_callback_.Reset();
+
+  if (MaybeClose()) {
+    return;
+  }
+
+  install_running_ = false;
+  NotifyObserverError();
+}
+
+void BruschettaInstaller::DownloadSucceeded(
+    const download::CompletionInfo& completion_info) {
+  download_guid_ = base::GUID();
+  std::move(download_callback_).Run(completion_info);
+}
+
+void BruschettaInstaller::DownloadFirmware() {
+  // We need to generate the download GUID before notifying because the tests
+  // need it to set the response.
+  download_guid_ = base::GUID::GenerateRandomV4();
+  NotifyObserver(State::kFirmwareDownload);
+
+  const std::string* url =
+      config_.FindDict(prefs::kPolicyUefiKey)->FindString(prefs::kPolicyURLKey);
+  StartDownload(GURL(*url),
+                base::BindOnce(&BruschettaInstaller::OnFirmwareDownloaded,
+                               weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnFirmwareDownloaded(
+    const download::CompletionInfo& completion_info) {
+  if (MaybeClose())
+    return;
+
+  const std::string* expected_hash = config_.FindDict(prefs::kPolicyUefiKey)
+                                         ->FindString(prefs::kPolicyHashKey);
+  if (!base::EqualsCaseInsensitiveASCII(completion_info.hash256,
+                                        *expected_hash)) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Downloaded firmware image has incorrect hash";
+    LOG(ERROR) << "Actual   " << completion_info.hash256;
+    LOG(ERROR) << "Expected " << *expected_hash;
+    return;
+  }
+
+  MountFirmware(completion_info.path);
+}
+
+void BruschettaInstaller::MountFirmware(const base::FilePath& path) {
+  NotifyObserver(State::kFirmwareMount);
+
+  ash::disks::DiskMountManager::GetInstance()->MountPath(
+      path.AsUTF8Unsafe(), "", "", {}, ash::MountType::kArchive,
+      ash::MountAccessMode::kReadOnly,
+      base::BindOnce(&BruschettaInstaller::OnFirmwareMounted,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnFirmwareMounted(ash::MountError error_code,
+                                            const ash::MountPoint& mount_info) {
+  if (MaybeClose())
+    return;
+
+  if (error_code != ash::MountError::kSuccess) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Failed to unpack firmware image: " << error_code;
+    return;
+  }
+
+  firmware_mount_path_ = mount_info.mount_path;
+
+  DownloadBootDisk();
+}
+
+void BruschettaInstaller::DownloadBootDisk() {
+  // We need to generate the download GUID before notifying because the tests
+  // need it to set the response.
+  download_guid_ = base::GUID::GenerateRandomV4();
+  NotifyObserver(State::kBootDiskDownload);
+
+  const std::string* url = config_.FindDict(prefs::kPolicyImageKey)
+                               ->FindString(prefs::kPolicyURLKey);
+  StartDownload(GURL(*url),
+                base::BindOnce(&BruschettaInstaller::OnBootDiskDownloaded,
+                               weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnBootDiskDownloaded(
+    const download::CompletionInfo& completion_info) {
+  if (MaybeClose())
+    return;
+
+  const std::string* expected_hash = config_.FindDict(prefs::kPolicyImageKey)
+                                         ->FindString(prefs::kPolicyHashKey);
+  if (!base::EqualsCaseInsensitiveASCII(completion_info.hash256,
+                                        *expected_hash)) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Downloaded boot disk has incorrect hash";
+    LOG(ERROR) << "Actual   " << completion_info.hash256;
+    LOG(ERROR) << "Expected " << *expected_hash;
+    return;
+  }
+
+  MountBootDisk(completion_info.path);
+}
+
+void BruschettaInstaller::MountBootDisk(const base::FilePath& path) {
+  NotifyObserver(State::kBootDiskMount);
+
+  ash::disks::DiskMountManager::GetInstance()->MountPath(
+      path.AsUTF8Unsafe(), "", "", {}, ash::MountType::kArchive,
+      ash::MountAccessMode::kReadOnly,
+      base::BindOnce(&BruschettaInstaller::OnBootDiskMounted,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnBootDiskMounted(ash::MountError error_code,
+                                            const ash::MountPoint& mount_info) {
+  if (MaybeClose())
+    return;
+
+  if (error_code != ash::MountError::kSuccess) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Failed to unpack firmware image: " << error_code;
+    return;
+  }
+
+  boot_disk_mount_path_ = mount_info.mount_path;
+
+  OpenFds();
+}
+
+void BruschettaInstaller::OpenFds() {
+  NotifyObserver(State::kOpenFiles);
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&OpenFdsBlocking, base::FilePath(firmware_mount_path_),
+                     base::FilePath(boot_disk_mount_path_),
+                     profile_->GetPath()),
+      base::BindOnce(&BruschettaInstaller::OnOpenFds,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+namespace {
+
+absl::optional<base::FilePath> FindPath(const base::FilePath& dir) {
+  auto enumerator =
+      base::FileEnumerator(dir, true, base::FileEnumerator::FILES);
+  auto filepath = enumerator.Next();
+  if (filepath.empty()) {
+    LOG(ERROR) << "No files under mount point";
+    return absl::nullopt;
+  }
+  if (!enumerator.Next().empty()) {
+    LOG(ERROR) << "Multiple files under mount point";
+    return absl::nullopt;
+  }
+  return filepath;
+}
+
+absl::optional<std::pair<base::ScopedFD, base::ScopedFD>> OpenFdsBlocking(
+    base::FilePath firmware_path,
+    base::FilePath boot_disk_path,
+    base::FilePath profile_path) {
+  auto firmware_src_path = FindPath(firmware_path);
+  if (!firmware_src_path) {
+    LOG(ERROR) << "Couldn't find firmware image";
+    return absl::nullopt;
+  }
+
+  auto boot_disk_file = FindPath(boot_disk_path);
+  if (!boot_disk_file) {
+    LOG(ERROR) << "Couldn't find boot disk";
+    return absl::nullopt;
+  }
+
+  auto firmware_dest_path = profile_path.Append(kBiosPath);
+
+  if (!base::CopyFile(*firmware_src_path, firmware_dest_path)) {
+    PLOG(ERROR) << "Failed to move firmware image to destination";
+    return absl::nullopt;
+  }
+
+  base::File firmware(firmware_dest_path,
+                      base::File::FLAG_OPEN | base::File::FLAG_READ);
+  base::File boot_disk(*boot_disk_file,
+                       base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!firmware.IsValid() || !boot_disk.IsValid()) {
+    PLOG(ERROR) << "Failed to open boot disk or firmware image";
+    return absl::nullopt;
+  }
+
+  return std::pair(base::ScopedFD(firmware.TakePlatformFile()),
+                   base::ScopedFD(boot_disk.TakePlatformFile()));
+}
+}  // namespace
+
+void BruschettaInstaller::OnOpenFds(
+    absl::optional<std::pair<base::ScopedFD, base::ScopedFD>> fds) {
+  if (MaybeClose())
+    return;
+
+  if (!fds) {
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Failed to open image files";
+    return;
+  }
+
+  firmware_fd_ = std::move(fds->first);
+  boot_disk_fd_ = std::move(fds->second);
+
+  CreateVmDisk();
+}
+
+void BruschettaInstaller::CreateVmDisk() {
+  NotifyObserver(State::kCreateVmDisk);
+
+  auto* client = chromeos::ConciergeClient::Get();
+  DCHECK(client) << "This code requires a ConciergeClient";
+
+  std::string user_hash =
+      ash::ProfileHelper::GetUserIdHashFromProfile(profile_);
+
+  vm_tools::concierge::CreateDiskImageRequest request;
+
+  request.set_cryptohome_id(std::move(user_hash));
+  request.set_vm_name(kBruschettaVmName);
+  request.set_image_type(vm_tools::concierge::DiskImageType::DISK_IMAGE_AUTO);
+
+  client->CreateDiskImage(request,
+                          base::BindOnce(&BruschettaInstaller::OnCreateVmDisk,
+                                         weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnCreateVmDisk(
+    absl::optional<vm_tools::concierge::CreateDiskImageResponse> result) {
+  if (MaybeClose())
+    return;
+
+  if (!result ||
+      result->status() !=
+          vm_tools::concierge::DiskImageStatus::DISK_STATUS_CREATED) {
+    install_running_ = false;
+    NotifyObserverError();
+    if (result) {
+      LOG(ERROR) << "Create VM failed: " << result->failure_reason();
+    } else {
+      LOG(ERROR) << "Create VM failed, no response";
+    }
+    return;
+  }
+
+  disk_path_ = result->disk_path();
+
+  StartVm();
+}
+
+void BruschettaInstaller::StartVm() {
+  NotifyObserver(State::kStartVm);
+
+  if (!GetInstallableConfig(profile_, config_id_)) {
+    // Policy has changed to prohibit installation, so bail out before actually
+    // starting the VM.
+    install_running_ = false;
+    NotifyObserverError();
+    LOG(ERROR) << "Installation prohibited by policy";
+    return;
+  }
+
+  auto* client = chromeos::ConciergeClient::Get();
+  DCHECK(client) << "This code requires a ConciergeClient";
+
+  std::string user_hash =
+      ash::ProfileHelper::GetUserIdHashFromProfile(profile_);
+  vm_tools::concierge::StartVmRequest request;
+
+  request.set_name(kBruschettaVmName);
+  request.set_owner_id(std::move(user_hash));
+  request.mutable_vm()->set_tools_dlc_id("termina-dlc");
+  request.set_start_termina(false);
+
+  auto* disk = request.add_disks();
+  disk->set_path(std::move(disk_path_));
+  disk->set_writable(true);
+
+  request.add_kernel_params("biosdevname=0");
+  request.add_kernel_params("net.ifnames=0");
+  request.add_kernel_params("console=hvc0");
+  request.add_kernel_params("earlycon=uart8250,io,0x3f8");
+  request.add_kernel_params("g-i/track=latest");
+  request.add_kernel_params("glinux/bruschetta-alpha");
+  request.set_timeout(240);
+
+  // fds and request.fds must have the same order.
+  std::vector<base::ScopedFD> fds;
+  request.add_fds(vm_tools::concierge::StartVmRequest::BIOS);
+  fds.push_back(std::move(firmware_fd_));
+  request.add_fds(vm_tools::concierge::StartVmRequest::ROOTFS);
+  fds.push_back(std::move(boot_disk_fd_));
+
+  client->StartVmWithFds(std::move(fds), request,
+                         base::BindOnce(&BruschettaInstaller::OnStartVm,
+                                        weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BruschettaInstaller::OnStartVm(
+    absl::optional<vm_tools::concierge::StartVmResponse> result) {
+  if (MaybeClose())
+    return;
+
+  if (!result || !result->success()) {
+    install_running_ = false;
+    NotifyObserverError();
+    if (result) {
+      LOG(ERROR) << "VM failed to start: " << result->failure_reason();
+    } else {
+      LOG(ERROR) << "VM failed to start, no response";
+    }
+    return;
+  }
+
+  LaunchTerminal();
+}
+
+void BruschettaInstaller::LaunchTerminal() {
+  NotifyObserver(State::kLaunchTerminal);
+
+  // TODO(b/231899688): Implement Bruschetta sending an RPC when installation
+  // finishes so that we only add to prefs on success.
+  auto guest_id = MakeBruschettaId(std::move(vm_name_));
+  BruschettaService::GetForProfile(profile_)->RegisterInPrefs(
+      guest_id, std::move(config_id_));
+
+  guest_id.container_name = "";
+
+  // kInvalidDisplayId will launch terminal on the current active display.
+  guest_os::LaunchTerminal(profile_, display::kInvalidDisplayId, guest_id);
+
+  // Close dialog.
+  std::move(close_closure_).Run();
+}
+
+void BruschettaInstaller::NotifyObserver(State state) {
+  if (observer_)
+    observer_->StateChanged(state);
+}
+
+void BruschettaInstaller::NotifyObserverError() {
+  if (observer_)
+    observer_->Error();
+}
+
+const base::GUID& BruschettaInstaller::GetDownloadGuid() const {
+  return download_guid_;
+}
+
+}  // namespace bruschetta
diff --git a/chrome/browser/ash/bruschetta/bruschetta_installer.h b/chrome/browser/ash/bruschetta/bruschetta_installer.h
new file mode 100644
index 0000000..f743ea3f
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/bruschetta_installer.h
@@ -0,0 +1,124 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_INSTALLER_H_
+#define CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_INSTALLER_H_
+
+#include "base/check_is_test.h"
+#include "base/guid.h"
+#include "base/memory/raw_ptr.h"
+#include "base/values.h"
+#include "chromeos/ash/components/dbus/concierge/concierge_service.pb.h"
+#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
+#include "chromeos/ash/components/disks/disk_mount_manager.h"
+#include "components/download/public/background_service/download_metadata.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+class Profile;
+
+namespace bruschetta {
+
+class BruschettaInstaller {
+ public:
+  BruschettaInstaller(Profile* profile, base::OnceClosure close_callback);
+
+  BruschettaInstaller(const BruschettaInstaller&) = delete;
+  BruschettaInstaller& operator=(const BruschettaInstaller&) = delete;
+  ~BruschettaInstaller();
+
+  void Cancel();
+  void Install(std::string vm_name, std::string config_id);
+
+  const base::GUID& GetDownloadGuid() const;
+
+  void DownloadStarted(const std::string& guid,
+                       download::DownloadParams::StartResult result);
+  void DownloadFailed();
+  void DownloadSucceeded(const download::CompletionInfo& completion_info);
+
+  enum class State {
+    kInstallStarted,
+    kDlcInstall,
+    kFirmwareDownload,
+    kFirmwareMount,
+    kBootDiskDownload,
+    kBootDiskMount,
+    kOpenFiles,
+    kCreateVmDisk,
+    kStartVm,
+    kLaunchTerminal,
+  };
+
+  class TestingObserver {
+   public:
+    virtual void StateChanged(State state) = 0;
+    virtual void Error() = 0;
+  };
+
+  void set_observer_for_testing(TestingObserver* observer) {
+    CHECK_IS_TEST();
+    observer_ = observer;
+  }
+
+ private:
+  using DownloadCallback =
+      base::OnceCallback<void(const download::CompletionInfo&)>;
+
+  bool MaybeClose();
+
+  void StartDownload(GURL url, DownloadCallback callback);
+
+  void InstallToolsDlc();
+  void OnToolsDlcInstalled(
+      const chromeos::DlcserviceClient::InstallResult& install_result);
+  void DownloadFirmware();
+  void OnFirmwareDownloaded(const download::CompletionInfo& completion_info);
+  void MountFirmware(const base::FilePath& path);
+  void OnFirmwareMounted(ash::MountError error_code,
+                         const ash::MountPoint& mount_info);
+  void DownloadBootDisk();
+  void OnBootDiskDownloaded(const download::CompletionInfo& completion_info);
+  void MountBootDisk(const base::FilePath& path);
+  void OnBootDiskMounted(ash::MountError error_code,
+                         const ash::MountPoint& mount_info);
+  void OpenFds();
+  void OnOpenFds(absl::optional<std::pair<base::ScopedFD, base::ScopedFD>> fds);
+  void CreateVmDisk();
+  void OnCreateVmDisk(
+      absl::optional<vm_tools::concierge::CreateDiskImageResponse> result);
+  void StartVm();
+  void OnStartVm(absl::optional<vm_tools::concierge::StartVmResponse> result);
+  void LaunchTerminal();
+
+  void NotifyObserver(State state);
+  void NotifyObserverError();
+
+  bool install_running_ = false;
+
+  std::string vm_name_;
+  std::string config_id_;
+  base::Value::Dict config_;
+
+  base::GUID download_guid_;
+  DownloadCallback download_callback_;
+
+  std::string firmware_mount_path_;
+  std::string boot_disk_mount_path_;
+  base::ScopedFD firmware_fd_;
+  base::ScopedFD boot_disk_fd_;
+  std::string disk_path_;
+
+  const base::raw_ptr<Profile> profile_;
+
+  base::OnceClosure close_closure_;
+
+  base::raw_ptr<TestingObserver> observer_ = nullptr;
+
+  base::WeakPtrFactory<BruschettaInstaller> weak_ptr_factory_{this};
+};
+
+}  // namespace bruschetta
+
+#endif  // CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_INSTALLER_H_
diff --git a/chrome/browser/ash/bruschetta/bruschetta_installer_unittest.cc b/chrome/browser/ash/bruschetta/bruschetta_installer_unittest.cc
new file mode 100644
index 0000000..fee24ed
--- /dev/null
+++ b/chrome/browser/ash/bruschetta/bruschetta_installer_unittest.cc
@@ -0,0 +1,664 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/bruschetta/bruschetta_installer.h"
+
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_download_client.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_pref_names.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_service_factory.h"
+#include "chrome/browser/ash/guest_os/dbus_test_helper.h"
+#include "chrome/browser/download/background_download_service_factory.h"
+#include "chrome/browser/profiles/profile_key.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
+#include "chromeos/ash/components/dbus/dlcservice/dlcservice.pb.h"
+#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
+#include "chromeos/ash/components/disks/disk_mount_manager.h"
+#include "chromeos/ash/components/disks/mock_disk_mount_manager.h"
+#include "components/download/public/background_service/test/test_download_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-spi.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace bruschetta {
+
+namespace {
+
+using testing::_;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::InvokeWithoutArgs;
+using testing::Sequence;
+
+// Total number of stopping points in ::ExpectStopOnStepN
+constexpr int kMaxSteps = 27;
+
+const char kVmName[] = "vm-name";
+const char kVmConfigId[] = "test-config-id";
+const char kVmConfigName[] = "test vm config";
+const char kVmConfigUrl[] = "https://example.com/";
+const char kVmConfigHash[] =
+    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+const char kBadHash[] =
+    "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
+const base::FilePath kDownloadPath{"/path/to/downloaded/object"};
+
+class MockObserver : public BruschettaInstaller::TestingObserver {
+ public:
+  MOCK_METHOD(void,
+              StateChanged,
+              (BruschettaInstaller::State state),
+              (override));
+  MOCK_METHOD(void, Error, (), (override));
+};
+
+class BruschettaInstallerTest : public testing::TestWithParam<int>,
+                                protected guest_os::FakeVmServicesHelper {
+ public:
+  BruschettaInstallerTest() = default;
+  BruschettaInstallerTest(const BruschettaInstallerTest&) = delete;
+  BruschettaInstallerTest& operator=(const BruschettaInstallerTest&) = delete;
+  ~BruschettaInstallerTest() override = default;
+
+ protected:
+  void BuildPrefValues() {
+    base::Value::Dict vtpm;
+    vtpm.Set(prefs::kPolicyVTPMEnabledKey, true);
+    vtpm.Set(prefs::kPolicyVTPMUpdateActionKey,
+             static_cast<int>(
+                 prefs::PolicyUpdateAction::FORCE_SHUTDOWN_IF_MORE_RESTRICTED));
+    base::Value::Dict image;
+    image.Set(prefs::kPolicyURLKey, kVmConfigUrl);
+    image.Set(prefs::kPolicyHashKey, kVmConfigHash);
+    base::Value::Dict config;
+    config.Set(prefs::kPolicyEnabledKey,
+               static_cast<int>(prefs::PolicyEnabledState::INSTALL_ALLOWED));
+    config.Set(prefs::kPolicyNameKey, kVmConfigName);
+    config.Set(prefs::kPolicyVTPMKey, vtpm.Clone());
+    config.Set(prefs::kPolicyImageKey, image.Clone());
+    config.Set(prefs::kPolicyUefiKey, image.Clone());
+    config.Set(prefs::kPolicyPflashKey, image.Clone());
+    prefs_installable_.Set(kVmConfigId, config.Clone());
+
+    config.Set(prefs::kPolicyEnabledKey,
+               static_cast<int>(prefs::PolicyEnabledState::RUN_ALLOWED));
+    config.Set(prefs::kPolicyNameKey, kVmConfigName);
+    config.Set(prefs::kPolicyVTPMKey, vtpm.Clone());
+    prefs_not_installable_.Set(kVmConfigId, std::move(config));
+  }
+
+  void SetUp() override {
+    BuildPrefValues();
+
+    BackgroundDownloadServiceFactory::GetInstance()->SetTestingFactory(
+        profile_.GetProfileKey(), base::BindRepeating([](SimpleFactoryKey*) {
+          return base::WrapUnique<KeyedService>(
+              new download::test::TestDownloadService());
+        }));
+
+    download_service_ = static_cast<download::test::TestDownloadService*>(
+        BackgroundDownloadServiceFactory::GetForKey(profile_.GetProfileKey()));
+    download_service_->SetIsReady(true);
+    download_service_->set_client(&download_client_);
+
+    firmware_mount_path_ = profile_.GetPath().Append("firmware_mount");
+    boot_disk_mount_path_ = profile_.GetPath().Append("boot_disk_mount");
+    ASSERT_TRUE(base::CreateDirectory(profile_.GetPath().Append("Downloads")));
+
+    ash::disks::DiskMountManager::InitializeForTesting(&disk_mount_manager_);
+
+    BruschettaServiceFactory::EnableForTesting(&profile_);
+
+    installer_ = std::make_unique<BruschettaInstaller>(
+        &profile_, base::BindOnce(&BruschettaInstallerTest::CloseCallback,
+                                  base::Unretained(this)));
+
+    installer_->set_observer_for_testing(&observer_);
+  }
+
+  void TearDown() override { ash::disks::DiskMountManager::Shutdown(); }
+
+  // All these methods return anonymous lambdas because gmock doesn't accept
+  // base::OnceCallbacks as callable objects in expectations.
+  auto CancelCallback() {
+    return [this]() { installer_->Cancel(); };
+  }
+
+  auto StopCallback() {
+    return [this]() { run_loop_.Quit(); };
+  }
+
+  auto PrefsCallback(const base::Value::Dict& value) {
+    return [this, &value]() {
+      profile_.GetPrefs()->SetDict(prefs::kBruschettaVMConfiguration,
+                                   value.Clone());
+    };
+  }
+
+  auto DlcCallback(std::string error) {
+    return
+        [this, error]() { FakeDlcserviceClient()->set_install_error(error); };
+  }
+
+  auto DownloadErrorCallback(bool fail_at_start) {
+    return [this, fail_at_start]() {
+      download_service_->SetFailedDownload(
+          installer_->GetDownloadGuid().AsLowercaseString(), fail_at_start);
+    };
+  }
+
+  auto DownloadBadHashCallback() {
+    return [this]() {
+      download_service_->SetHash256(kBadHash);
+      download_service_->SetFailedDownload("", false);
+    };
+  }
+
+  auto DownloadSuccessCallback() {
+    return [this]() {
+      download_service_->SetHash256(kVmConfigHash);
+      download_service_->SetFilePath(kDownloadPath);
+      download_service_->SetFailedDownload("", false);
+    };
+  }
+
+  auto OpenFilesCallback(base::FilePath dir, int count) {
+    return [dir, count]() {
+      ASSERT_TRUE(base::CreateDirectory(dir));
+      for (int i = 0; i < count; i++) {
+        base::File(dir.Append(base::NumberToString(i)),
+                   base::File::FLAG_CREATE | base::File::FLAG_READ);
+      }
+    };
+  }
+
+  auto DiskImageCallback(
+      absl::optional<vm_tools::concierge::DiskImageStatus> value) {
+    return [this, value]() {
+      if (value.has_value()) {
+        vm_tools::concierge::CreateDiskImageResponse response;
+        response.set_status(*value);
+        FakeConciergeClient()->set_create_disk_image_response(
+            std::move(response));
+      } else {
+        FakeConciergeClient()->set_create_disk_image_response(absl::nullopt);
+      }
+    };
+  }
+
+  auto StartVmCallback(absl::optional<bool> success) {
+    return [this, success]() {
+      if (success.has_value()) {
+        vm_tools::concierge::StartVmResponse response;
+        response.set_success(*success);
+        FakeConciergeClient()->set_start_vm_response(std::move(response));
+      } else {
+        FakeConciergeClient()->set_start_vm_response(absl::nullopt);
+      }
+    };
+  }
+
+  void MountExpectation(Sequence seq,
+                        ash::MountError error_code,
+                        std::string mount_path) {
+    EXPECT_CALL(
+        disk_mount_manager_,
+        MountPath(kDownloadPath.AsUTF8Unsafe(), _, _, _,
+                  ash::MountType::kArchive, ash::MountAccessMode::kReadOnly, _))
+        .Times(1)
+        .InSequence(seq)
+        .WillOnce(testing::WithArg<6>(
+            [error_code, mount_path](
+                ash::disks::DiskMountManager::MountPathCallback callback) {
+              ash::MountPoint mount_info;
+              mount_info.mount_path = std::move(mount_path);
+              std::move(callback).Run(error_code, mount_info);
+            }));
+  }
+
+  void ErrorExpectation(Sequence seq) {
+    EXPECT_CALL(observer_, Error)
+        .Times(1)
+        .InSequence(seq)
+        .WillOnce(StopCallback());
+  }
+
+  template <typename T, typename F>
+  void MakeErrorPoint(T& expectation, Sequence seq, F callback) {
+    expectation.WillOnce(InvokeWithoutArgs(callback));
+    ErrorExpectation(seq);
+  }
+
+  template <typename T, typename F, typename G>
+  void MakeErrorPoint(T& expectation, Sequence seq, F callback1, G callback2) {
+    expectation.WillOnce(
+        DoAll(InvokeWithoutArgs(callback1), InvokeWithoutArgs(callback2)));
+    ErrorExpectation(seq);
+  }
+
+  // Generate expectations and actions for a test that runs the install and
+  // stops at the nth point where stopping is possible, returning true if the
+  // stop is due to an error and false if the stop is a cancel. Passing in
+  // kMaxSteps means letting the install run to completion.
+  //
+  // Takes an optional Sequence to order these expectations before/after other
+  // things.
+  bool ExpectStopOnStepN(int n, Sequence seq = {}) {
+    // Policy check step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kInstallStarted))
+              .Times(1)
+              .InSequence(seq);
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, PrefsCallback(prefs_not_installable_));
+        return true;
+      }
+
+      expectation.WillOnce(
+          InvokeWithoutArgs(PrefsCallback(prefs_installable_)));
+    }
+
+    // DLC install step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kDlcInstall))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DlcCallback("Install Error"));
+        return true;
+      }
+
+      expectation.WillOnce(
+          InvokeWithoutArgs(DlcCallback(dlcservice::kErrorNone)));
+    }
+
+    // Firmware image download step
+    {
+      auto& expectation =
+          EXPECT_CALL(
+              observer_,
+              StateChanged(BruschettaInstaller::State::kFirmwareDownload))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadErrorCallback(true));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadErrorCallback(false));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadBadHashCallback());
+        return true;
+      }
+
+      expectation.WillOnce(InvokeWithoutArgs(DownloadSuccessCallback()));
+    }
+
+    // Mount firmware image step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kFirmwareMount))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        MountExpectation(seq, ash::MountError::kSuccess,
+                         firmware_mount_path_.AsUTF8Unsafe());
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MountExpectation(seq, ash::MountError::kUnknownError, "");
+        ErrorExpectation(seq);
+        return true;
+      }
+
+      MountExpectation(seq, ash::MountError::kSuccess,
+                       firmware_mount_path_.AsUTF8Unsafe());
+    }
+
+    // Boot disk download step
+    {
+      auto& expectation =
+          EXPECT_CALL(
+              observer_,
+              StateChanged(BruschettaInstaller::State::kBootDiskDownload))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadErrorCallback(true));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadErrorCallback(false));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DownloadBadHashCallback());
+        return true;
+      }
+
+      expectation.WillOnce(InvokeWithoutArgs(DownloadSuccessCallback()));
+    }
+
+    // Mount boot disk step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kBootDiskMount))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        MountExpectation(seq, ash::MountError::kSuccess,
+                         firmware_mount_path_.AsUTF8Unsafe());
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MountExpectation(seq, ash::MountError::kUnknownError, "");
+        ErrorExpectation(seq);
+        return true;
+      }
+
+      MountExpectation(seq, ash::MountError::kSuccess,
+                       boot_disk_mount_path_.AsUTF8Unsafe());
+    }
+
+    // Open files step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kOpenFiles))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq,
+                       OpenFilesCallback(firmware_mount_path_, 0));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq,
+                       OpenFilesCallback(firmware_mount_path_, 2));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq,
+                       OpenFilesCallback(firmware_mount_path_, 1),
+                       OpenFilesCallback(boot_disk_mount_path_, 0));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq,
+                       OpenFilesCallback(firmware_mount_path_, 1),
+                       OpenFilesCallback(boot_disk_mount_path_, 2));
+        return true;
+      }
+
+      expectation.WillOnce(DoAll(OpenFilesCallback(firmware_mount_path_, 1),
+                                 OpenFilesCallback(boot_disk_mount_path_, 1)));
+    }
+
+    // Create VM disk step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kCreateVmDisk))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, DiskImageCallback(absl::nullopt));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(
+            expectation, seq,
+            DiskImageCallback(
+                vm_tools::concierge::DiskImageStatus::DISK_STATUS_FAILED));
+        return true;
+      }
+
+      expectation.WillOnce(InvokeWithoutArgs(DiskImageCallback(
+          vm_tools::concierge::DiskImageStatus::DISK_STATUS_CREATED)));
+    }
+
+    // Start VM step
+    {
+      auto& expectation =
+          EXPECT_CALL(observer_,
+                      StateChanged(BruschettaInstaller::State::kStartVm))
+              .Times(1)
+              .InSequence(seq);
+
+      if (!n--) {
+        expectation.WillOnce(CancelCallback());
+        return false;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, PrefsCallback(prefs_not_installable_));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, StartVmCallback(absl::nullopt));
+        return true;
+      }
+      if (!n--) {
+        MakeErrorPoint(expectation, seq, StartVmCallback(false));
+        return true;
+      }
+
+      expectation.WillOnce(InvokeWithoutArgs(StartVmCallback(true)));
+    }
+
+    // Open terminal step
+    EXPECT_CALL(observer_,
+                StateChanged(BruschettaInstaller::State::kLaunchTerminal))
+        .Times(1)
+        .InSequence(seq);
+    // Dialog closes after this without further action from us
+
+    // Make sure all input steps other then kMaxSteps got handled earlier.
+    if (n != 0) {
+      ADD_FAILURE() << "Steps input is too high, try setting kMaxSteps to "
+                    << kMaxSteps - n;
+    }
+
+    // Like a cancellation, finishing the install closes the installer.
+    return false;
+  }
+
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::RunLoop run_loop_, run_loop_2_;
+
+  base::Value::Dict prefs_installable_, prefs_not_installable_;
+
+  base::FilePath firmware_mount_path_, boot_disk_mount_path_;
+
+  TestingProfile profile_;
+  std::unique_ptr<BruschettaInstaller> installer_;
+
+  MockObserver observer_;
+  // Pointer owned by DiskMountManager
+  ash::disks::MockDiskMountManager& disk_mount_manager_{
+      *new ash::disks::MockDiskMountManager};
+
+  download::test::TestDownloadService* download_service_;
+  BruschettaDownloadClient download_client_{&profile_};
+
+ private:
+  // Called when the installer exists, suitable for base::BindOnce.
+  void CloseCallback() {
+    // Delete the installer object after it requests closure so we can tell if
+    // it does anything afterwards that assumes it hasn't been deleted yet.
+    installer_.reset();
+    run_loop_.Quit();
+    run_loop_2_.Quit();
+  }
+};
+
+TEST_F(BruschettaInstallerTest, CloseOnPrompt) {
+  installer_->Cancel();
+  run_loop_.Run();
+
+  EXPECT_FALSE(installer_);
+}
+
+TEST_F(BruschettaInstallerTest, InstallSuccess) {
+  ExpectStopOnStepN(kMaxSteps);
+
+  installer_->Install(kVmName, kVmConfigId);
+  run_loop_.Run();
+
+  EXPECT_FALSE(installer_);
+}
+
+TEST_F(BruschettaInstallerTest, TwoInstalls) {
+  ExpectStopOnStepN(kMaxSteps);
+
+  installer_->Install(kVmName, kVmConfigId);
+  installer_->Install(kVmName, kVmConfigId);
+  run_loop_.Run();
+
+  EXPECT_FALSE(installer_);
+}
+
+TEST_P(BruschettaInstallerTest, StopDuringInstall) {
+  bool is_error = ExpectStopOnStepN(GetParam());
+
+  installer_->Install(kVmName, kVmConfigId);
+  run_loop_.Run();
+
+  if (is_error) {
+    // Installer should remain open in error state, tell it to close.
+    EXPECT_TRUE(installer_);
+    installer_->Cancel();
+    run_loop_2_.Run();
+  }
+
+  EXPECT_FALSE(installer_);
+}
+
+TEST_P(BruschettaInstallerTest, ErrorAndRetry) {
+  Sequence seq;
+
+  bool is_error = ExpectStopOnStepN(GetParam(), seq);
+  if (is_error) {
+    ExpectStopOnStepN(kMaxSteps, seq);
+  }
+
+  installer_->Install(kVmName, kVmConfigId);
+  run_loop_.Run();
+
+  if (!is_error) {
+    return;
+  }
+
+  // Installer should remain open in error state, retry the install.
+  EXPECT_TRUE(installer_);
+
+  // Clean up the fake mount paths, since these should be different on each run.
+  ASSERT_TRUE(base::DeletePathRecursively(firmware_mount_path_));
+  ASSERT_TRUE(base::DeletePathRecursively(boot_disk_mount_path_));
+
+  installer_->Install(kVmName, kVmConfigId);
+  run_loop_2_.Run();
+
+  EXPECT_FALSE(installer_);
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         BruschettaInstallerTest,
+                         testing::Range(0, kMaxSteps));
+
+TEST_F(BruschettaInstallerTest, AllStepsTested) {
+  // Meta-test to check that kMaxSteps is set correctly.
+
+  // We generate a lot of gmock expectations just to ignore them, and taking
+  // stack traces for all of them is really slow, so disable stack traces for
+  // this test.
+  ::testing::FLAGS_gtest_stack_trace_depth = 0;
+
+  absl::optional<int> new_max_steps;
+
+  for (int i = 0; i < 1000; i++) {
+    testing::TestPartResultArray failures;
+    testing::ScopedFakeTestPartResultReporter test_reporter{
+        testing::ScopedFakeTestPartResultReporter::
+            INTERCEPT_ONLY_CURRENT_THREAD,
+        &failures};
+
+    ExpectStopOnStepN(i);
+
+    if (failures.size() > 0) {
+      new_max_steps = i - 1;
+      break;
+    }
+  }
+  if (new_max_steps.has_value()) {
+    if (*new_max_steps != kMaxSteps) {
+      ADD_FAILURE() << "kMaxSteps needs to be updated. Try setting it to "
+                    << *new_max_steps;
+    }
+  } else {
+    ADD_FAILURE() << "Was unable to find the correct value for kMaxSteps";
+  }
+
+  // Force the expectations we just set to be checked here so we can ignore all
+  // the failures.
+  {
+    testing::TestPartResultArray failures;
+    testing::ScopedFakeTestPartResultReporter test_reporter{
+        testing::ScopedFakeTestPartResultReporter::
+            INTERCEPT_ONLY_CURRENT_THREAD,
+        &failures};
+
+    testing::Mock::VerifyAndClearExpectations(&observer_);
+    testing::Mock::VerifyAndClearExpectations(&disk_mount_manager_);
+  }
+}
+
+}  // namespace
+
+}  // namespace bruschetta
diff --git a/chrome/browser/ash/bruschetta/bruschetta_util.cc b/chrome/browser/ash/bruschetta/bruschetta_util.cc
index 023b2226..79964e8 100644
--- a/chrome/browser/ash/bruschetta/bruschetta_util.cc
+++ b/chrome/browser/ash/bruschetta/bruschetta_util.cc
@@ -30,6 +30,8 @@
 const char kBruschettaVmName[] = "bru";
 const char kBruschettaDisplayName[] = "Bruschetta";
 
+const char kBiosPath[] = "Downloads/bios";
+
 const char* BruschettaResultString(const BruschettaResult res) {
 #define ENTRY(name)            \
   case BruschettaResult::name: \
@@ -66,4 +68,11 @@
   return base::FilePath("/mnt/shared");
 }
 
+absl::optional<const base::Value::Dict*> GetInstallableConfig(
+    const Profile* profile,
+    const std::string& config_id) {
+  return GetConfigWithEnabledLevel(profile, config_id,
+                                   prefs::PolicyEnabledState::INSTALL_ALLOWED);
+}
+
 }  // namespace bruschetta
diff --git a/chrome/browser/ash/bruschetta/bruschetta_util.h b/chrome/browser/ash/bruschetta/bruschetta_util.h
index 217f091..eca4313 100644
--- a/chrome/browser/ash/bruschetta/bruschetta_util.h
+++ b/chrome/browser/ash/bruschetta/bruschetta_util.h
@@ -15,6 +15,8 @@
 extern const char kBruschettaVmName[];
 extern const char kBruschettaDisplayName[];
 
+extern const char kBiosPath[];
+
 enum class BruschettaResult {
   kUnknown,
   kSuccess,
@@ -37,6 +39,10 @@
 
 base::FilePath BruschettaChromeOSBaseDirectory();
 
+absl::optional<const base::Value::Dict*> GetInstallableConfig(
+    const Profile* profile,
+    const std::string& config_id);
+
 }  // namespace bruschetta
 
 #endif  // CHROME_BROWSER_ASH_BRUSCHETTA_BRUSCHETTA_UTIL_H_
diff --git a/chrome/browser/ash/crosapi/browser_manager.cc b/chrome/browser/ash/crosapi/browser_manager.cc
index 7c709f7..916b9a8 100644
--- a/chrome/browser/ash/crosapi/browser_manager.cc
+++ b/chrome/browser/ash/crosapi/browser_manager.cc
@@ -979,6 +979,17 @@
     options.environment["WAYLAND_DEBUG"] = "1";
   }
 
+  // LsbRelease and LsbReleaseTime are used by sys_info in Lacros to determine
+  // hardware class.
+  std::unique_ptr<base::Environment> env = base::Environment::Create();
+  std::string lsb_release;
+  std::string lsb_release_time;
+  if (env->GetVar(base::kLsbReleaseKey, &lsb_release) &&
+      env->GetVar(base::kLsbReleaseTimeKey, &lsb_release_time)) {
+    options.environment[base::kLsbReleaseKey] = std::move(lsb_release);
+    options.environment[base::kLsbReleaseTimeKey] = std::move(lsb_release_time);
+  }
+
   std::string additional_env =
       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
           ash::switches::kLacrosChromeAdditionalEnv);
diff --git a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
index e10b83d..3d056f6 100644
--- a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
+++ b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
@@ -21,7 +21,7 @@
 #include "chromeos/dbus/missive/missive_client.h"
 #include "components/reporting/proto/synced/interface.pb.h"
 #include "components/reporting/proto/synced/status.pb.h"
-#include "components/reporting/resources/memory_resource_impl.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage_selector/storage_selector.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
@@ -60,7 +60,7 @@
         upload_provider)
     : origin_thread_id_(base::PlatformThread::CurrentId()),
       origin_thread_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
-      memory_resource_(base::MakeRefCounted<::reporting::MemoryResourceImpl>(
+      memory_resource_(base::MakeRefCounted<::reporting::ResourceManager>(
           kDefaultMemoryAllocation)),
       upload_provider_(std::move(upload_provider)) {
   DCHECK(upload_provider_.get());
diff --git a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.h b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.h
index b99ab81..b7c565f 100644
--- a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.h
+++ b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.h
@@ -20,7 +20,7 @@
 #include "chromeos/ash/components/dbus/services/cros_dbus_service.h"
 #include "chromeos/dbus/missive/missive_client.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage_selector/storage_selector.h"
 #include "dbus/exported_object.h"
 #include "dbus/message.h"
@@ -79,7 +79,7 @@
   const scoped_refptr<base::SingleThreadTaskRunner> origin_thread_runner_;
 
   // Memory resource for upload requests and responses.
-  scoped_refptr<::reporting::ResourceInterface> memory_resource_;
+  scoped_refptr<::reporting::ResourceManager> memory_resource_;
 
   // Upload Provider.
   const std::unique_ptr<::reporting::EncryptedReportingUploadProvider>
diff --git a/chrome/browser/ash/dbus/fusebox_service_provider.cc b/chrome/browser/ash/dbus/fusebox_service_provider.cc
index 43d07ee..6cc4e251 100644
--- a/chrome/browser/ash/dbus/fusebox_service_provider.cc
+++ b/chrome/browser/ash/dbus/fusebox_service_provider.cc
@@ -48,79 +48,6 @@
   std::move(sender).Run(std::move(response));
 }
 
-void ReplyToClose(dbus::MethodCall* method_call,
-                  dbus::ExportedObject::ResponseSender sender,
-                  int32_t posix_error_code) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::unique_ptr<dbus::Response> response =
-      dbus::Response::FromMethodCall(method_call);
-  dbus::MessageWriter writer(response.get());
-
-  writer.AppendInt32(posix_error_code);
-
-  std::move(sender).Run(std::move(response));
-}
-
-void ReplyToOpen(dbus::MethodCall* method_call,
-                 dbus::ExportedObject::ResponseSender sender,
-                 int32_t posix_error_code) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::unique_ptr<dbus::Response> response =
-      dbus::Response::FromMethodCall(method_call);
-  dbus::MessageWriter writer(response.get());
-
-  writer.AppendInt32(posix_error_code);
-  // For historical reasons, append a second parameter that's no longer used.
-  writer.AppendUint64(0);
-
-  std::move(sender).Run(std::move(response));
-}
-
-void ReplyToRead(dbus::MethodCall* method_call,
-                 dbus::ExportedObject::ResponseSender sender,
-                 int32_t posix_error_code,
-                 const uint8_t* data_ptr,
-                 size_t data_len) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::unique_ptr<dbus::Response> response =
-      dbus::Response::FromMethodCall(method_call);
-  dbus::MessageWriter writer(response.get());
-
-  writer.AppendInt32(posix_error_code);
-  writer.AppendArrayOfBytes(data_ptr, data_len);
-
-  std::move(sender).Run(std::move(response));
-}
-
-void ReplyToStat(dbus::MethodCall* method_call,
-                 dbus::ExportedObject::ResponseSender sender,
-                 int32_t posix_error_code,
-                 const base::File::Info& info,
-                 bool read_only) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::unique_ptr<dbus::Response> response =
-      dbus::Response::FromMethodCall(method_call);
-  dbus::MessageWriter writer(response.get());
-
-  writer.AppendInt32(posix_error_code);
-  // For historical reasons, the D-Bus protocol uses a *signed* int32_t, even
-  // though /usr/include/x86_64-linux-gnu/bits/typesizes.h says "#define
-  // __MODE_T_TYPE __U32_TYPE" (and hence MakeModeBits returns an *unsigned*
-  // uint32_t). We use a static_cast to convert between them.
-  writer.AppendInt32(static_cast<int32_t>(
-      fusebox::Server::MakeModeBits(info.is_directory, read_only)));
-  writer.AppendInt64(info.size);
-  writer.AppendDouble(info.last_accessed.ToDoubleT());
-  writer.AppendDouble(info.last_modified.ToDoubleT());
-  writer.AppendDouble(info.creation_time.ToDoubleT());
-
-  std::move(sender).Run(std::move(response));
-}
-
 }  // namespace
 
 FuseBoxServiceProvider::FuseBoxServiceProvider() : server_(this) {}
@@ -130,25 +57,6 @@
 void FuseBoxServiceProvider::Start(scoped_refptr<dbus::ExportedObject> object) {
   exported_object_ = object;
 
-  // TODO(b/255520194): remove the deprecated Stat, Open, Read and Close
-  // methods. They have been replaced by Stat2, Open2, Read2 and Close2.
-  object->ExportMethod(fusebox::kFuseBoxServiceInterface, fusebox::kCloseMethod,
-                       base::BindRepeating(&FuseBoxServiceProvider::Close,
-                                           weak_ptr_factory_.GetWeakPtr()),
-                       base::BindOnce(&OnExportedCallback));
-  object->ExportMethod(fusebox::kFuseBoxServiceInterface, fusebox::kOpenMethod,
-                       base::BindRepeating(&FuseBoxServiceProvider::Open,
-                                           weak_ptr_factory_.GetWeakPtr()),
-                       base::BindOnce(&OnExportedCallback));
-  object->ExportMethod(fusebox::kFuseBoxServiceInterface, fusebox::kReadMethod,
-                       base::BindRepeating(&FuseBoxServiceProvider::Read,
-                                           weak_ptr_factory_.GetWeakPtr()),
-                       base::BindOnce(&OnExportedCallback));
-  object->ExportMethod(fusebox::kFuseBoxServiceInterface, fusebox::kStatMethod,
-                       base::BindRepeating(&FuseBoxServiceProvider::Stat,
-                                           weak_ptr_factory_.GetWeakPtr()),
-                       base::BindOnce(&OnExportedCallback));
-
   ExportProtoMethod(fusebox::kClose2Method, &fusebox::Server::Close2);
   ExportProtoMethod(fusebox::kCreateMethod, &fusebox::Server::Create);
   ExportProtoMethod(fusebox::kMkDirMethod, &fusebox::Server::MkDir);
@@ -188,71 +96,6 @@
   exported_object_->SendSignal(&signal);
 }
 
-void FuseBoxServiceProvider::Close(
-    dbus::MethodCall* method_call,
-    dbus::ExportedObject::ResponseSender sender) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  dbus::MessageReader reader(method_call);
-  std::string fs_url_as_string;
-  if (!reader.PopString(&fs_url_as_string)) {
-    ReplyToClose(method_call, std::move(sender), EINVAL);
-    return;
-  }
-
-  server_.Close(fs_url_as_string,
-                base::BindOnce(&ReplyToClose, method_call, std::move(sender)));
-}
-
-void FuseBoxServiceProvider::Open(dbus::MethodCall* method_call,
-                                  dbus::ExportedObject::ResponseSender sender) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  dbus::MessageReader reader(method_call);
-  std::string fs_url_as_string;
-  if (!reader.PopString(&fs_url_as_string)) {
-    ReplyToOpen(method_call, std::move(sender), EINVAL);
-    return;
-  }
-
-  server_.Open(fs_url_as_string,
-               base::BindOnce(&ReplyToOpen, method_call, std::move(sender)));
-}
-
-void FuseBoxServiceProvider::Read(dbus::MethodCall* method_call,
-                                  dbus::ExportedObject::ResponseSender sender) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  dbus::MessageReader reader(method_call);
-  std::string fs_url_as_string;
-  int64_t offset = 0;
-  int32_t length = 0;
-  if (!reader.PopString(&fs_url_as_string) || !reader.PopInt64(&offset) ||
-      !reader.PopInt32(&length)) {
-    ReplyToRead(method_call, std::move(sender), EINVAL, nullptr, 0);
-    return;
-  }
-
-  server_.Read(fs_url_as_string, offset, length,
-               base::BindOnce(&ReplyToRead, method_call, std::move(sender)));
-}
-
-void FuseBoxServiceProvider::Stat(dbus::MethodCall* method_call,
-                                  dbus::ExportedObject::ResponseSender sender) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  dbus::MessageReader reader(method_call);
-  std::string fs_url_as_string;
-  if (!reader.PopString(&fs_url_as_string)) {
-    ReplyToStat(method_call, std::move(sender), EINVAL, base::File::Info(),
-                false);
-    return;
-  }
-
-  server_.Stat(fs_url_as_string,
-               base::BindOnce(&ReplyToStat, method_call, std::move(sender)));
-}
-
 template <typename RequestProto, typename ResponseProto>
 void FuseBoxServiceProvider::ServeProtoMethod(
     ServerMethodPtr<RequestProto, ResponseProto> method,
diff --git a/chrome/browser/ash/dbus/fusebox_service_provider.h b/chrome/browser/ash/dbus/fusebox_service_provider.h
index e62ae07d..85e3957 100644
--- a/chrome/browser/ash/dbus/fusebox_service_provider.h
+++ b/chrome/browser/ash/dbus/fusebox_service_provider.h
@@ -31,19 +31,7 @@
   void OnRegisterFSURLPrefix(const std::string& subdir) override;
   void OnUnregisterFSURLPrefix(const std::string& subdir) override;
 
-  // D-Bus methods.
-  //
-  // In terms of semantics, they're roughly equivalent to the C standard
-  // library functions of the same name. For example, the Stat method here
-  // corresponds to the standard stat function described by "man 2 stat".
-  void Close(dbus::MethodCall* method_call,
-             dbus::ExportedObject::ResponseSender sender);
-  void Open(dbus::MethodCall* method_call,
-            dbus::ExportedObject::ResponseSender sender);
-  void Read(dbus::MethodCall* method_call,
-            dbus::ExportedObject::ResponseSender sender);
-  void Stat(dbus::MethodCall* method_call,
-            dbus::ExportedObject::ResponseSender sender);
+  // D-Bus template methods.
 
   template <typename RequestProto, typename ResponseProto>
   using ServerMethodPtr = void (fusebox::Server::*)(
@@ -59,6 +47,8 @@
   void ExportProtoMethod(const std::string& method_name,
                          ServerMethodPtr<RequestProto, ResponseProto> method);
 
+  // Private fields.
+
   scoped_refptr<dbus::ExportedObject> exported_object_;
   fusebox::Server server_;
 
diff --git a/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc b/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc
index adc3a64..699fc3c 100644
--- a/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc
+++ b/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc
@@ -109,7 +109,7 @@
   Profile* profile = Profile::FromBrowserContext(context_);
   if (SpeechRecognitionRecognizerClientImpl::
           GetOnDeviceSpeechRecognitionAvailability(locale_) ==
-      ash::SpeechRecognitionAvailability::kSodaAvailable) {
+      ash::OnDeviceRecognitionAvailability::kAvailable) {
     type_ = speech::SpeechRecognitionType::kOnDevice;
     speech_recognizer_ =
         std::make_unique<SpeechRecognitionRecognizerClientImpl>(
diff --git a/chrome/browser/ash/file_system_provider/event_dispatcher.h b/chrome/browser/ash/file_system_provider/event_dispatcher.h
new file mode 100644
index 0000000..87d232c
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/event_dispatcher.h
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace extensions {
+struct Event;
+}  // namespace extensions
+
+namespace ash::file_system_provider {
+
+// The interface for sending fileSystemProvider events created by |Operation|
+// instances to an extension. The dispatcher is specific to a single extension.
+class EventDispatcher {
+ public:
+  virtual ~EventDispatcher() = default;
+  // Dispatch a fileSystemProvider event to the target extension.
+  // |file_system_id| will be non-null for operations scoped to a specific
+  // filesystem, and null for operations that don't apply to any existing
+  // filesystem (like mount).
+  virtual bool DispatchEvent(int request_id,
+                             absl::optional<std::string> file_system_id,
+                             std::unique_ptr<extensions::Event> event) = 0;
+};
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_H_
diff --git a/chrome/browser/ash/file_system_provider/event_dispatcher_impl.cc b/chrome/browser/ash/file_system_provider/event_dispatcher_impl.cc
new file mode 100644
index 0000000..80bdac46
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/event_dispatcher_impl.cc
@@ -0,0 +1,78 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_system_provider/event_dispatcher_impl.h"
+
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
+#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
+#include "extensions/browser/event_router.h"
+#include "url/gurl.h"
+
+namespace ash::file_system_provider {
+
+EventDispatcherImpl::EventDispatcherImpl(
+    const extensions::ExtensionId& extension_id,
+    extensions::EventRouter* event_router,
+    RequestManager* request_manager)
+    : extension_id_(extension_id),
+      event_router_(event_router),
+      request_manager_(request_manager) {}
+
+EventDispatcherImpl::~EventDispatcherImpl() = default;
+
+bool EventDispatcherImpl::DispatchEvent(
+    int request_id,
+    absl::optional<std::string> file_system_id,
+    std::unique_ptr<extensions::Event> event) {
+  // If ash has a matching extension, forward the event. This should not be
+  // needed once Lacros is the only browser on all devices.
+  if (event_router_->ExtensionHasEventListener(extension_id_,
+                                               event->event_name)) {
+    event_router_->DispatchEventToExtension(extension_id_, std::move(event));
+    return true;
+  }
+
+  if (extension_id_ == guest_os::kTerminalSystemAppId) {
+    GURL terminal(chrome::kChromeUIUntrustedTerminalURL);
+    if (event_router_->URLHasEventListener(terminal, event->event_name)) {
+      event_router_->DispatchEventToURL(terminal, std::move(event));
+      return true;
+    }
+  }
+
+  // If there are any Lacros remotes, forward the message to the first one. This
+  // does not support multiple remotes.
+  auto& remotes = crosapi::CrosapiManager::Get()
+                      ->crosapi_ash()
+                      ->file_system_provider_service_ash()
+                      ->remotes();
+  if (!remotes.empty()) {
+    auto remote = remotes.begin();
+    auto callback = base::BindOnce(&EventDispatcherImpl::OperationForwarded,
+                                   weak_ptr_factory_.GetWeakPtr(), request_id);
+    (*remote)->ForwardOperation(
+        extension_id_, static_cast<int32_t>(event->histogram_value),
+        std::move(event->event_name), std::move(event->event_args),
+        std::move(callback));
+  }
+  return !remotes.empty();
+}
+
+void EventDispatcherImpl::OperationForwarded(int request_id,
+                                             bool delivery_failure) {
+  // Successful deliveries will get a response through the FileSystemProvider
+  // mojom path.
+  if (!delivery_failure) {
+    return;
+  }
+  request_manager_->RejectRequest(request_id, std::make_unique<RequestValue>(),
+                                  base::File::FILE_ERROR_FAILED);
+}
+
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/event_dispatcher_impl.h b/chrome/browser/ash/file_system_provider/event_dispatcher_impl.h
new file mode 100644
index 0000000..847e1eb
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/event_dispatcher_impl.h
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_IMPL_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_IMPL_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/file_system_provider/event_dispatcher.h"
+#include "extensions/common/extension_id.h"
+
+namespace extensions {
+class EventRouter;
+}  // namespace extensions
+
+namespace ash::file_system_provider {
+
+class RequestManager;
+
+// Routes fileSystemProvider events to an extension locally or in Lacros.
+class EventDispatcherImpl : public EventDispatcher {
+ public:
+  EventDispatcherImpl(const extensions::ExtensionId& extension_id,
+                      extensions::EventRouter* event_router,
+                      RequestManager* request_manager);
+  ~EventDispatcherImpl() override;
+
+  EventDispatcherImpl(const EventDispatcherImpl&) = delete;
+  EventDispatcherImpl& operator=(const EventDispatcherImpl&) = delete;
+
+  bool DispatchEvent(int request_id,
+                     absl::optional<std::string> file_system_id,
+                     std::unique_ptr<extensions::Event> event) override;
+
+ private:
+  // This method is only used when Lacros is enabled. It's a callback from
+  // Lacros indicating whether the operation was successfully forwarded. If the
+  // operation could not be forwarded then the file system request manager must
+  // be informed.
+  void OperationForwarded(int request_id, bool delivery_failure);
+
+  const extensions::ExtensionId extension_id_;
+  const raw_ptr<extensions::EventRouter> event_router_;
+  const raw_ptr<RequestManager> request_manager_;
+
+  base::WeakPtrFactory<EventDispatcherImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_EVENT_DISPATCHER_IMPL_H_
diff --git a/chrome/browser/ash/file_system_provider/operations/abort.cc b/chrome/browser/ash/file_system_provider/operations/abort.cc
index 4e68356..9073120 100644
--- a/chrome/browser/ash/file_system_provider/operations/abort.cc
+++ b/chrome/browser/ash/file_system_provider/operations/abort.cc
@@ -13,11 +13,11 @@
 namespace file_system_provider {
 namespace operations {
 
-Abort::Abort(extensions::EventRouter* event_router,
+Abort::Abort(EventDispatcher* dispatcher,
              const ProvidedFileSystemInfo& file_system_info,
              int operation_request_id,
              storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       operation_request_id_(operation_request_id),
       callback_(std::move(callback)) {}
 
diff --git a/chrome/browser/ash/file_system_provider/operations/abort.h b/chrome/browser/ash/file_system_provider/operations/abort.h
index b35635b..537df41 100644
--- a/chrome/browser/ash/file_system_provider/operations/abort.h
+++ b/chrome/browser/ash/file_system_provider/operations/abort.h
@@ -14,10 +14,6 @@
 #include "chrome/browser/ash/file_system_provider/request_value.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -25,7 +21,7 @@
 // Aborts an operation. Created per request.
 class Abort : public Operation {
  public:
-  Abort(extensions::EventRouter* event_router,
+  Abort(EventDispatcher* dispatcher,
         const ProvidedFileSystemInfo& file_system_info,
         int operation_request_id,
         storage::AsyncFileUtil::StatusCallback callback);
diff --git a/chrome/browser/ash/file_system_provider/operations/abort_unittest.cc b/chrome/browser/ash/file_system_provider/operations/abort_unittest.cc
index 2e346b07..b1bd290 100644
--- a/chrome/browser/ash/file_system_provider/operations/abort_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/abort_unittest.cc
@@ -54,11 +54,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Abort abort(nullptr, file_system_info_, kOperationRequestId,
+  Abort abort(&dispatcher, file_system_info_, kOperationRequestId,
               base::BindOnce(&util::LogStatusCallback, &callback_log));
-  abort.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(abort.Execute(kRequestId));
 
@@ -83,11 +80,8 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Abort abort(nullptr, file_system_info_, kOperationRequestId,
+  Abort abort(&dispatcher, file_system_info_, kOperationRequestId,
               base::BindOnce(&util::LogStatusCallback, &callback_log));
-  abort.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(abort.Execute(kRequestId));
 }
@@ -96,11 +90,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Abort abort(nullptr, file_system_info_, kOperationRequestId,
+  Abort abort(&dispatcher, file_system_info_, kOperationRequestId,
               base::BindOnce(&util::LogStatusCallback, &callback_log));
-  abort.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(abort.Execute(kRequestId));
 
@@ -114,11 +105,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Abort abort(nullptr, file_system_info_, kOperationRequestId,
+  Abort abort(&dispatcher, file_system_info_, kOperationRequestId,
               base::BindOnce(&util::LogStatusCallback, &callback_log));
-  abort.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(abort.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/add_watcher.cc b/chrome/browser/ash/file_system_provider/operations/add_watcher.cc
index 4352025..a39a60f5 100644
--- a/chrome/browser/ash/file_system_provider/operations/add_watcher.cc
+++ b/chrome/browser/ash/file_system_provider/operations/add_watcher.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-AddWatcher::AddWatcher(extensions::EventRouter* event_router,
+AddWatcher::AddWatcher(EventDispatcher* dispatcher,
                        const ProvidedFileSystemInfo& file_system_info,
                        const base::FilePath& entry_path,
                        bool recursive,
                        storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_path_(entry_path),
       recursive_(recursive),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/add_watcher.h b/chrome/browser/ash/file_system_provider/operations/add_watcher.h
index 66b9a78..45a4162 100644
--- a/chrome/browser/ash/file_system_provider/operations/add_watcher.h
+++ b/chrome/browser/ash/file_system_provider/operations/add_watcher.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -31,7 +27,7 @@
 // files.
 class AddWatcher : public Operation {
  public:
-  AddWatcher(extensions::EventRouter* event_router,
+  AddWatcher(EventDispatcher* dispatcher,
              const ProvidedFileSystemInfo& file_system_info,
              const base::FilePath& entry_path,
              bool recursive,
diff --git a/chrome/browser/ash/file_system_provider/operations/add_watcher_unittest.cc b/chrome/browser/ash/file_system_provider/operations/add_watcher_unittest.cc
index fefda00..cbd9881e 100644
--- a/chrome/browser/ash/file_system_provider/operations/add_watcher_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/add_watcher_unittest.cc
@@ -56,12 +56,9 @@
   util::StatusCallbackLog callback_log;
 
   AddWatcher add_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  add_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(add_watcher.Execute(kRequestId));
 
@@ -90,12 +87,9 @@
   util::StatusCallbackLog callback_log;
 
   AddWatcher add_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  add_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(add_watcher.Execute(kRequestId));
 }
@@ -105,12 +99,9 @@
   util::StatusCallbackLog callback_log;
 
   AddWatcher add_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  add_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(add_watcher.Execute(kRequestId));
 
@@ -125,12 +116,9 @@
   util::StatusCallbackLog callback_log;
 
   AddWatcher add_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  add_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(add_watcher.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/close_file.cc b/chrome/browser/ash/file_system_provider/operations/close_file.cc
index 6ef6d2c4..74d23f92 100644
--- a/chrome/browser/ash/file_system_provider/operations/close_file.cc
+++ b/chrome/browser/ash/file_system_provider/operations/close_file.cc
@@ -13,11 +13,11 @@
 namespace file_system_provider {
 namespace operations {
 
-CloseFile::CloseFile(extensions::EventRouter* event_router,
+CloseFile::CloseFile(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info,
                      int open_request_id,
                      storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       open_request_id_(open_request_id),
       callback_(std::move(callback)) {}
 
diff --git a/chrome/browser/ash/file_system_provider/operations/close_file.h b/chrome/browser/ash/file_system_provider/operations/close_file.h
index f0ee77d..9a018b5 100644
--- a/chrome/browser/ash/file_system_provider/operations/close_file.h
+++ b/chrome/browser/ash/file_system_provider/operations/close_file.h
@@ -14,10 +14,6 @@
 #include "chrome/browser/ash/file_system_provider/request_value.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -27,7 +23,7 @@
 // not download the file locally. Created per request.
 class CloseFile : public Operation {
  public:
-  CloseFile(extensions::EventRouter* event_router,
+  CloseFile(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info,
             int open_request_id,
             storage::AsyncFileUtil::StatusCallback callback);
diff --git a/chrome/browser/ash/file_system_provider/operations/close_file_unittest.cc b/chrome/browser/ash/file_system_provider/operations/close_file_unittest.cc
index 777d9a4..6dd4873d 100644
--- a/chrome/browser/ash/file_system_provider/operations/close_file_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/close_file_unittest.cc
@@ -54,11 +54,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CloseFile close_file(nullptr, file_system_info_, kOpenRequestId,
+  CloseFile close_file(&dispatcher, file_system_info_, kOpenRequestId,
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  close_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(close_file.Execute(kRequestId));
 
@@ -84,11 +81,8 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CloseFile close_file(nullptr, file_system_info_, kOpenRequestId,
+  CloseFile close_file(&dispatcher, file_system_info_, kOpenRequestId,
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  close_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(close_file.Execute(kRequestId));
 }
@@ -97,11 +91,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CloseFile close_file(nullptr, file_system_info_, kOpenRequestId,
+  CloseFile close_file(&dispatcher, file_system_info_, kOpenRequestId,
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  close_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(close_file.Execute(kRequestId));
 
@@ -115,11 +106,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CloseFile close_file(nullptr, file_system_info_, kOpenRequestId,
+  CloseFile close_file(&dispatcher, file_system_info_, kOpenRequestId,
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  close_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(close_file.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/configure.cc b/chrome/browser/ash/file_system_provider/operations/configure.cc
index b4095a89..cd50669 100644
--- a/chrome/browser/ash/file_system_provider/operations/configure.cc
+++ b/chrome/browser/ash/file_system_provider/operations/configure.cc
@@ -11,11 +11,10 @@
 namespace file_system_provider {
 namespace operations {
 
-Configure::Configure(extensions::EventRouter* event_router,
+Configure::Configure(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info,
                      storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
-      callback_(std::move(callback)) {}
+    : Operation(dispatcher, file_system_info), callback_(std::move(callback)) {}
 
 Configure::~Configure() {
 }
diff --git a/chrome/browser/ash/file_system_provider/operations/configure.h b/chrome/browser/ash/file_system_provider/operations/configure.h
index e2e6561..ebb8c66f 100644
--- a/chrome/browser/ash/file_system_provider/operations/configure.h
+++ b/chrome/browser/ash/file_system_provider/operations/configure.h
@@ -11,10 +11,6 @@
 #include "chrome/browser/ash/file_system_provider/operations/operation.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 
@@ -26,7 +22,7 @@
 // extension's configure request. Created per request.
 class Configure : public Operation {
  public:
-  Configure(extensions::EventRouter* event_router,
+  Configure(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info,
             storage::AsyncFileUtil::StatusCallback callback);
 
diff --git a/chrome/browser/ash/file_system_provider/operations/configure_unittest.cc b/chrome/browser/ash/file_system_provider/operations/configure_unittest.cc
index ba4efa5a..af831bee1 100644
--- a/chrome/browser/ash/file_system_provider/operations/configure_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/configure_unittest.cc
@@ -52,11 +52,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Configure configure(nullptr, file_system_info_,
+  Configure configure(&dispatcher, file_system_info_,
                       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  configure.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(configure.Execute(kRequestId));
 
@@ -81,11 +78,8 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Configure configure(nullptr, file_system_info_,
+  Configure configure(&dispatcher, file_system_info_,
                       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  configure.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(configure.Execute(kRequestId));
 }
@@ -94,11 +88,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Configure configure(nullptr, file_system_info_,
+  Configure configure(&dispatcher, file_system_info_,
                       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  configure.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(configure.Execute(kRequestId));
 
@@ -113,11 +104,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Configure configure(nullptr, file_system_info_,
+  Configure configure(&dispatcher, file_system_info_,
                       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  configure.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(configure.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/copy_entry.cc b/chrome/browser/ash/file_system_provider/operations/copy_entry.cc
index db57cca..fc1b90e 100644
--- a/chrome/browser/ash/file_system_provider/operations/copy_entry.cc
+++ b/chrome/browser/ash/file_system_provider/operations/copy_entry.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-CopyEntry::CopyEntry(extensions::EventRouter* event_router,
+CopyEntry::CopyEntry(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info,
                      const base::FilePath& source_path,
                      const base::FilePath& target_path,
                      storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       source_path_(source_path),
       target_path_(target_path),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/copy_entry.h b/chrome/browser/ash/file_system_provider/operations/copy_entry.h
index ba0d5af8..5b5b61e3 100644
--- a/chrome/browser/ash/file_system_provider/operations/copy_entry.h
+++ b/chrome/browser/ash/file_system_provider/operations/copy_entry.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -29,7 +25,7 @@
 // Copies an entry (recursively if a directory). Created per request.
 class CopyEntry : public Operation {
  public:
-  CopyEntry(extensions::EventRouter* event_router,
+  CopyEntry(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info,
             const base::FilePath& source_path,
             const base::FilePath& target_path,
diff --git a/chrome/browser/ash/file_system_provider/operations/copy_entry_unittest.cc b/chrome/browser/ash/file_system_provider/operations/copy_entry_unittest.cc
index b5bb517..ceae3315 100644
--- a/chrome/browser/ash/file_system_provider/operations/copy_entry_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/copy_entry_unittest.cc
@@ -58,12 +58,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CopyEntry copy_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  CopyEntry copy_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  copy_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(copy_entry.Execute(kRequestId));
 
@@ -90,12 +87,9 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CopyEntry copy_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  CopyEntry copy_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  copy_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(copy_entry.Execute(kRequestId));
 }
@@ -109,12 +103,9 @@
       base::FilePath() /* mount_path */, false /* configurable */,
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
-  CopyEntry copy_entry(nullptr, read_only_file_system_info,
+  CopyEntry copy_entry(&dispatcher, read_only_file_system_info,
                        base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  copy_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(copy_entry.Execute(kRequestId));
 }
@@ -123,12 +114,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CopyEntry copy_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  CopyEntry copy_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  copy_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(copy_entry.Execute(kRequestId));
 
@@ -142,12 +130,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  CopyEntry copy_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  CopyEntry copy_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  copy_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(copy_entry.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/create_directory.cc b/chrome/browser/ash/file_system_provider/operations/create_directory.cc
index a8f8c33..b626f078 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_directory.cc
+++ b/chrome/browser/ash/file_system_provider/operations/create_directory.cc
@@ -14,12 +14,12 @@
 namespace operations {
 
 CreateDirectory::CreateDirectory(
-    extensions::EventRouter* event_router,
+    EventDispatcher* dispatcher,
     const ProvidedFileSystemInfo& file_system_info,
     const base::FilePath& directory_path,
     bool recursive,
     storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       directory_path_(directory_path),
       recursive_(recursive),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/create_directory.h b/chrome/browser/ash/file_system_provider/operations/create_directory.h
index 13e610a..72ab9bed 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_directory.h
+++ b/chrome/browser/ash/file_system_provider/operations/create_directory.h
@@ -17,10 +17,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -30,7 +26,7 @@
 // directory already exists. Created per request.
 class CreateDirectory : public Operation {
  public:
-  CreateDirectory(extensions::EventRouter* event_router,
+  CreateDirectory(EventDispatcher* dispatcher,
                   const ProvidedFileSystemInfo& file_system_info,
                   const base::FilePath& directory_path,
                   bool recursive,
diff --git a/chrome/browser/ash/file_system_provider/operations/create_directory_unittest.cc b/chrome/browser/ash/file_system_provider/operations/create_directory_unittest.cc
index 56a3065a..50a997f 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_directory_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/create_directory_unittest.cc
@@ -59,12 +59,9 @@
   util::StatusCallbackLog callback_log;
 
   CreateDirectory create_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_directory.Execute(kRequestId));
 
@@ -93,12 +90,9 @@
   util::StatusCallbackLog callback_log;
 
   CreateDirectory create_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(create_directory.Execute(kRequestId));
 }
@@ -113,12 +107,9 @@
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
   CreateDirectory create_directory(
-      nullptr, read_only_file_system_info, base::FilePath(kDirectoryPath),
+      &dispatcher, read_only_file_system_info, base::FilePath(kDirectoryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(create_directory.Execute(kRequestId));
 }
@@ -128,12 +119,9 @@
   util::StatusCallbackLog callback_log;
 
   CreateDirectory create_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_directory.Execute(kRequestId));
 
@@ -148,12 +136,9 @@
   util::StatusCallbackLog callback_log;
 
   CreateDirectory create_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_directory.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/create_file.cc b/chrome/browser/ash/file_system_provider/operations/create_file.cc
index 19cc509..22e43f8 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_file.cc
+++ b/chrome/browser/ash/file_system_provider/operations/create_file.cc
@@ -13,11 +13,11 @@
 namespace file_system_provider {
 namespace operations {
 
-CreateFile::CreateFile(extensions::EventRouter* event_router,
+CreateFile::CreateFile(EventDispatcher* dispatcher,
                        const ProvidedFileSystemInfo& file_system_info,
                        const base::FilePath& file_path,
                        storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       file_path_(file_path),
       callback_(std::move(callback)) {}
 
diff --git a/chrome/browser/ash/file_system_provider/operations/create_file.h b/chrome/browser/ash/file_system_provider/operations/create_file.h
index 1116abcf..98f68e0 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_file.h
+++ b/chrome/browser/ash/file_system_provider/operations/create_file.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -30,7 +26,7 @@
 // the FILE_ERROR_EXISTS error. Created per request.
 class CreateFile : public Operation {
  public:
-  CreateFile(extensions::EventRouter* event_router,
+  CreateFile(EventDispatcher* dispatcher,
              const ProvidedFileSystemInfo& file_system_info,
              const base::FilePath& file_path,
              storage::AsyncFileUtil::StatusCallback callback);
diff --git a/chrome/browser/ash/file_system_provider/operations/create_file_unittest.cc b/chrome/browser/ash/file_system_provider/operations/create_file_unittest.cc
index 1b707d2..6dbdc284 100644
--- a/chrome/browser/ash/file_system_provider/operations/create_file_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/create_file_unittest.cc
@@ -57,11 +57,8 @@
   util::StatusCallbackLog callback_log;
 
   CreateFile create_file(
-      nullptr, file_system_info_, base::FilePath(kFilePath),
+      &dispatcher, file_system_info_, base::FilePath(kFilePath),
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_file.Execute(kRequestId));
 
@@ -89,11 +86,8 @@
   util::StatusCallbackLog callback_log;
 
   CreateFile create_file(
-      nullptr, file_system_info_, base::FilePath(kFilePath),
+      &dispatcher, file_system_info_, base::FilePath(kFilePath),
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(create_file.Execute(kRequestId));
 }
@@ -108,11 +102,8 @@
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
   CreateFile create_file(
-      nullptr, read_only_file_system_info, base::FilePath(kFilePath),
+      &dispatcher, read_only_file_system_info, base::FilePath(kFilePath),
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(create_file.Execute(kRequestId));
 }
@@ -122,11 +113,8 @@
   util::StatusCallbackLog callback_log;
 
   CreateFile create_file(
-      nullptr, file_system_info_, base::FilePath(kFilePath),
+      &dispatcher, file_system_info_, base::FilePath(kFilePath),
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_file.Execute(kRequestId));
 
@@ -141,11 +129,8 @@
   util::StatusCallbackLog callback_log;
 
   CreateFile create_file(
-      nullptr, file_system_info_, base::FilePath(kFilePath),
+      &dispatcher, file_system_info_, base::FilePath(kFilePath),
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  create_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(create_file.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/delete_entry.cc b/chrome/browser/ash/file_system_provider/operations/delete_entry.cc
index 743906e..78c2787 100644
--- a/chrome/browser/ash/file_system_provider/operations/delete_entry.cc
+++ b/chrome/browser/ash/file_system_provider/operations/delete_entry.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-DeleteEntry::DeleteEntry(extensions::EventRouter* event_router,
+DeleteEntry::DeleteEntry(EventDispatcher* dispatcher,
                          const ProvidedFileSystemInfo& file_system_info,
                          const base::FilePath& entry_path,
                          bool recursive,
                          storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_path_(entry_path),
       recursive_(recursive),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/delete_entry.h b/chrome/browser/ash/file_system_provider/operations/delete_entry.h
index 979cf4f3..86c25fe 100644
--- a/chrome/browser/ash/file_system_provider/operations/delete_entry.h
+++ b/chrome/browser/ash/file_system_provider/operations/delete_entry.h
@@ -17,10 +17,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -29,7 +25,7 @@
 // all contents of it (recursively) will be deleted too. Created per request.
 class DeleteEntry : public Operation {
  public:
-  DeleteEntry(extensions::EventRouter* event_router,
+  DeleteEntry(EventDispatcher* dispatcher,
               const ProvidedFileSystemInfo& file_system_info,
               const base::FilePath& entry_path,
               bool recursive,
diff --git a/chrome/browser/ash/file_system_provider/operations/delete_entry_unittest.cc b/chrome/browser/ash/file_system_provider/operations/delete_entry_unittest.cc
index 32bb39d..cc13900 100644
--- a/chrome/browser/ash/file_system_provider/operations/delete_entry_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/delete_entry_unittest.cc
@@ -57,12 +57,9 @@
   util::StatusCallbackLog callback_log;
 
   DeleteEntry delete_entry(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  delete_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(delete_entry.Execute(kRequestId));
 
@@ -91,12 +88,9 @@
   util::StatusCallbackLog callback_log;
 
   DeleteEntry delete_entry(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  delete_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(delete_entry.Execute(kRequestId));
 }
@@ -111,12 +105,9 @@
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
   DeleteEntry delete_entry(
-      nullptr, read_only_file_system_info, base::FilePath(kEntryPath),
+      &dispatcher, read_only_file_system_info, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  delete_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(delete_entry.Execute(kRequestId));
 }
@@ -126,12 +117,9 @@
   util::StatusCallbackLog callback_log;
 
   DeleteEntry delete_entry(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  delete_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(delete_entry.Execute(kRequestId));
 
@@ -146,12 +134,9 @@
   util::StatusCallbackLog callback_log;
 
   DeleteEntry delete_entry(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  delete_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(delete_entry.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/execute_action.cc b/chrome/browser/ash/file_system_provider/operations/execute_action.cc
index 1039de26..a1f18016 100644
--- a/chrome/browser/ash/file_system_provider/operations/execute_action.cc
+++ b/chrome/browser/ash/file_system_provider/operations/execute_action.cc
@@ -14,12 +14,12 @@
 namespace file_system_provider {
 namespace operations {
 
-ExecuteAction::ExecuteAction(extensions::EventRouter* event_router,
+ExecuteAction::ExecuteAction(EventDispatcher* dispatcher,
                              const ProvidedFileSystemInfo& file_system_info,
                              const std::vector<base::FilePath>& entry_paths,
                              const std::string& action_id,
                              storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_paths_(entry_paths),
       action_id_(action_id),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/execute_action.h b/chrome/browser/ash/file_system_provider/operations/execute_action.h
index 202ca7a51..2d17019e9 100644
--- a/chrome/browser/ash/file_system_provider/operations/execute_action.h
+++ b/chrome/browser/ash/file_system_provider/operations/execute_action.h
@@ -21,10 +21,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -34,7 +30,7 @@
 // request.
 class ExecuteAction : public Operation {
  public:
-  ExecuteAction(extensions::EventRouter* event_router,
+  ExecuteAction(EventDispatcher* dispatcher,
                 const ProvidedFileSystemInfo& file_system_info,
                 const std::vector<base::FilePath>& entry_path,
                 const std::string& action_id,
diff --git a/chrome/browser/ash/file_system_provider/operations/execute_action_unittest.cc b/chrome/browser/ash/file_system_provider/operations/execute_action_unittest.cc
index 7c53bae..684f38f 100644
--- a/chrome/browser/ash/file_system_provider/operations/execute_action_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/execute_action_unittest.cc
@@ -63,11 +63,8 @@
   util::StatusCallbackLog callback_log;
 
   ExecuteAction execute_action(
-      nullptr, file_system_info_, entry_paths_, kActionId,
+      &dispatcher, file_system_info_, entry_paths_, kActionId,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  execute_action.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(execute_action.Execute(kRequestId));
 
@@ -98,11 +95,8 @@
   util::StatusCallbackLog callback_log;
 
   ExecuteAction execute_action(
-      nullptr, file_system_info_, entry_paths_, kActionId,
+      &dispatcher, file_system_info_, entry_paths_, kActionId,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  execute_action.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(execute_action.Execute(kRequestId));
 }
@@ -112,11 +106,8 @@
   util::StatusCallbackLog callback_log;
 
   ExecuteAction execute_action(
-      nullptr, file_system_info_, entry_paths_, kActionId,
+      &dispatcher, file_system_info_, entry_paths_, kActionId,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  execute_action.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(execute_action.Execute(kRequestId));
 
@@ -131,11 +122,8 @@
   util::StatusCallbackLog callback_log;
 
   ExecuteAction execute_action(
-      nullptr, file_system_info_, entry_paths_, kActionId,
+      &dispatcher, file_system_info_, entry_paths_, kActionId,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  execute_action.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(execute_action.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/get_actions.cc b/chrome/browser/ash/file_system_provider/operations/get_actions.cc
index 5759a39..1de7ccf 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_actions.cc
+++ b/chrome/browser/ash/file_system_provider/operations/get_actions.cc
@@ -37,11 +37,11 @@
 
 }  // namespace
 
-GetActions::GetActions(extensions::EventRouter* event_router,
+GetActions::GetActions(EventDispatcher* dispatcher,
                        const ProvidedFileSystemInfo& file_system_info,
                        const std::vector<base::FilePath>& entry_paths,
                        ProvidedFileSystemInterface::GetActionsCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_paths_(entry_paths),
       callback_(std::move(callback)) {}
 
diff --git a/chrome/browser/ash/file_system_provider/operations/get_actions.h b/chrome/browser/ash/file_system_provider/operations/get_actions.h
index 04023aa..ff4b7aa8 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_actions.h
+++ b/chrome/browser/ash/file_system_provider/operations/get_actions.h
@@ -19,10 +19,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -31,7 +27,7 @@
 // actions request. Created per request.
 class GetActions : public Operation {
  public:
-  GetActions(extensions::EventRouter* event_router,
+  GetActions(EventDispatcher* dispatcher,
              const ProvidedFileSystemInfo& file_system_info,
              const std::vector<base::FilePath>& entry_paths,
              ProvidedFileSystemInterface::GetActionsCallback callback);
diff --git a/chrome/browser/ash/file_system_provider/operations/get_actions_unittest.cc b/chrome/browser/ash/file_system_provider/operations/get_actions_unittest.cc
index 2273e09..0efd52d 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_actions_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/get_actions_unittest.cc
@@ -117,12 +117,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  GetActions get_actions(nullptr, file_system_info_, entry_paths_,
+  GetActions get_actions(&dispatcher, file_system_info_, entry_paths_,
                          base::BindOnce(&CallbackLogger::OnGetActions,
                                         base::Unretained(&callback_logger)));
-  get_actions.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_actions.Execute(kRequestId));
 
@@ -151,12 +148,9 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  GetActions get_actions(nullptr, file_system_info_, entry_paths_,
+  GetActions get_actions(&dispatcher, file_system_info_, entry_paths_,
                          base::BindOnce(&CallbackLogger::OnGetActions,
                                         base::Unretained(&callback_logger)));
-  get_actions.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(get_actions.Execute(kRequestId));
 }
@@ -165,12 +159,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  GetActions get_actions(nullptr, file_system_info_, entry_paths_,
+  GetActions get_actions(&dispatcher, file_system_info_, entry_paths_,
                          base::BindOnce(&CallbackLogger::OnGetActions,
                                         base::Unretained(&callback_logger)));
-  get_actions.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_actions.Execute(kRequestId));
 
@@ -224,12 +215,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  GetActions get_actions(nullptr, file_system_info_, entry_paths_,
+  GetActions get_actions(&dispatcher, file_system_info_, entry_paths_,
                          base::BindOnce(&CallbackLogger::OnGetActions,
                                         base::Unretained(&callback_logger)));
-  get_actions.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_actions.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/get_metadata.cc b/chrome/browser/ash/file_system_provider/operations/get_metadata.cc
index 8fb0b0ba..3315e21 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_metadata.cc
+++ b/chrome/browser/ash/file_system_provider/operations/get_metadata.cc
@@ -142,12 +142,12 @@
 }
 
 GetMetadata::GetMetadata(
-    extensions::EventRouter* event_router,
+    EventDispatcher* dispatcher,
     const ProvidedFileSystemInfo& file_system_info,
     const base::FilePath& entry_path,
     ProvidedFileSystemInterface::MetadataFieldMask fields,
     ProvidedFileSystemInterface::GetMetadataCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_path_(entry_path),
       fields_(fields),
       callback_(std::move(callback)) {
diff --git a/chrome/browser/ash/file_system_provider/operations/get_metadata.h b/chrome/browser/ash/file_system_provider/operations/get_metadata.h
index 28bc18f..0f9137a 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_metadata.h
+++ b/chrome/browser/ash/file_system_provider/operations/get_metadata.h
@@ -19,10 +19,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -41,7 +37,7 @@
 // metadata request. Created per request.
 class GetMetadata : public Operation {
  public:
-  GetMetadata(extensions::EventRouter* event_router,
+  GetMetadata(EventDispatcher* dispatcher,
               const ProvidedFileSystemInfo& file_system_info,
               const base::FilePath& entry_path,
               ProvidedFileSystemInterface::MetadataFieldMask fields,
diff --git a/chrome/browser/ash/file_system_provider/operations/get_metadata_unittest.cc b/chrome/browser/ash/file_system_provider/operations/get_metadata_unittest.cc
index a97ede3..7ca3854 100644
--- a/chrome/browser/ash/file_system_provider/operations/get_metadata_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/get_metadata_unittest.cc
@@ -228,13 +228,10 @@
   CallbackLogger callback_logger;
 
   GetMetadata get_metadata(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL,
       base::BindOnce(&CallbackLogger::OnGetMetadata,
                      base::Unretained(&callback_logger)));
-  get_metadata.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_metadata.Execute(kRequestId));
 
@@ -263,13 +260,10 @@
   CallbackLogger callback_logger;
 
   GetMetadata get_metadata(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL,
       base::BindOnce(&CallbackLogger::OnGetMetadata,
                      base::Unretained(&callback_logger)));
-  get_metadata.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(get_metadata.Execute(kRequestId));
 }
@@ -279,7 +273,7 @@
   CallbackLogger callback_logger;
 
   GetMetadata get_metadata(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       ProvidedFileSystemInterface::METADATA_FIELD_IS_DIRECTORY |
           ProvidedFileSystemInterface::METADATA_FIELD_NAME |
           ProvidedFileSystemInterface::METADATA_FIELD_SIZE |
@@ -288,9 +282,6 @@
           ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL,
       base::BindOnce(&CallbackLogger::OnGetMetadata,
                      base::Unretained(&callback_logger)));
-  get_metadata.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_metadata.Execute(kRequestId));
 
@@ -339,7 +330,7 @@
   CallbackLogger callback_logger;
 
   GetMetadata get_metadata(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       ProvidedFileSystemInterface::METADATA_FIELD_IS_DIRECTORY |
           ProvidedFileSystemInterface::METADATA_FIELD_NAME |
           ProvidedFileSystemInterface::METADATA_FIELD_SIZE |
@@ -348,9 +339,6 @@
           ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL,
       base::BindOnce(&CallbackLogger::OnGetMetadata,
                      base::Unretained(&callback_logger)));
-  get_metadata.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_metadata.Execute(kRequestId));
 
@@ -393,13 +381,10 @@
   CallbackLogger callback_logger;
 
   GetMetadata get_metadata(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL,
       base::BindOnce(&CallbackLogger::OnGetMetadata,
                      base::Unretained(&callback_logger)));
-  get_metadata.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(get_metadata.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/move_entry.cc b/chrome/browser/ash/file_system_provider/operations/move_entry.cc
index d790ef6..eb958d7 100644
--- a/chrome/browser/ash/file_system_provider/operations/move_entry.cc
+++ b/chrome/browser/ash/file_system_provider/operations/move_entry.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-MoveEntry::MoveEntry(extensions::EventRouter* event_router,
+MoveEntry::MoveEntry(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info,
                      const base::FilePath& source_path,
                      const base::FilePath& target_path,
                      storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       source_path_(source_path),
       target_path_(target_path),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/move_entry.h b/chrome/browser/ash/file_system_provider/operations/move_entry.h
index f3486e7fa..2199f7b7 100644
--- a/chrome/browser/ash/file_system_provider/operations/move_entry.h
+++ b/chrome/browser/ash/file_system_provider/operations/move_entry.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -29,7 +25,7 @@
 // Copies an entry (recursively if a directory). Created per request.
 class MoveEntry : public Operation {
  public:
-  MoveEntry(extensions::EventRouter* event_router,
+  MoveEntry(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info,
             const base::FilePath& source_path,
             const base::FilePath& target_path,
diff --git a/chrome/browser/ash/file_system_provider/operations/move_entry_unittest.cc b/chrome/browser/ash/file_system_provider/operations/move_entry_unittest.cc
index abc2249..29cb9fc 100644
--- a/chrome/browser/ash/file_system_provider/operations/move_entry_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/move_entry_unittest.cc
@@ -58,12 +58,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  MoveEntry move_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  MoveEntry move_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  move_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(move_entry.Execute(kRequestId));
 
@@ -90,12 +87,9 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  MoveEntry move_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  MoveEntry move_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  move_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(move_entry.Execute(kRequestId));
 }
@@ -109,12 +103,9 @@
       base::FilePath() /* mount_path */, false /* configurable */,
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
-  MoveEntry move_entry(nullptr, read_only_file_system_info,
+  MoveEntry move_entry(&dispatcher, read_only_file_system_info,
                        base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  move_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(move_entry.Execute(kRequestId));
 }
@@ -123,12 +114,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  MoveEntry move_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  MoveEntry move_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  move_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(move_entry.Execute(kRequestId));
 
@@ -142,12 +130,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  MoveEntry move_entry(nullptr, file_system_info_, base::FilePath(kSourcePath),
-                       base::FilePath(kTargetPath),
+  MoveEntry move_entry(&dispatcher, file_system_info_,
+                       base::FilePath(kSourcePath), base::FilePath(kTargetPath),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  move_entry.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(move_entry.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/open_file.cc b/chrome/browser/ash/file_system_provider/operations/open_file.cc
index 2c95ba82..0409efd 100644
--- a/chrome/browser/ash/file_system_provider/operations/open_file.cc
+++ b/chrome/browser/ash/file_system_provider/operations/open_file.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-OpenFile::OpenFile(extensions::EventRouter* event_router,
+OpenFile::OpenFile(EventDispatcher* dispatcher,
                    const ProvidedFileSystemInfo& file_system_info,
                    const base::FilePath& file_path,
                    OpenFileMode mode,
                    ProvidedFileSystemInterface::OpenFileCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       file_path_(file_path),
       mode_(mode),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/open_file.h b/chrome/browser/ash/file_system_provider/operations/open_file.h
index 933f2e9..16489d0 100644
--- a/chrome/browser/ash/file_system_provider/operations/open_file.h
+++ b/chrome/browser/ash/file_system_provider/operations/open_file.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -30,7 +26,7 @@
 // operation will fail. Created per request.
 class OpenFile : public Operation {
  public:
-  OpenFile(extensions::EventRouter* event_router,
+  OpenFile(EventDispatcher* dispatcher,
            const ProvidedFileSystemInfo& file_system_info,
            const base::FilePath& file_path,
            OpenFileMode mode,
diff --git a/chrome/browser/ash/file_system_provider/operations/open_file_unittest.cc b/chrome/browser/ash/file_system_provider/operations/open_file_unittest.cc
index b3789726..7607c03 100644
--- a/chrome/browser/ash/file_system_provider/operations/open_file_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/open_file_unittest.cc
@@ -93,13 +93,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  OpenFile open_file(nullptr, file_system_info_, base::FilePath(kFilePath),
+  OpenFile open_file(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                      OPEN_FILE_MODE_READ,
                      base::BindOnce(&CallbackLogger::OnOpenFile,
                                     base::Unretained(&callback_logger)));
-  open_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(open_file.Execute(kRequestId));
 
@@ -127,13 +124,10 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  OpenFile open_file(nullptr, file_system_info_, base::FilePath(kFilePath),
+  OpenFile open_file(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                      OPEN_FILE_MODE_READ,
                      base::BindOnce(&CallbackLogger::OnOpenFile,
                                     base::Unretained(&callback_logger)));
-  open_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(open_file.Execute(kRequestId));
 }
@@ -149,26 +143,20 @@
 
   // Opening for read on a read-only file system is allowed.
   {
-    OpenFile open_file(nullptr, read_only_file_system_info,
+    OpenFile open_file(&dispatcher, read_only_file_system_info,
                        base::FilePath(kFilePath), OPEN_FILE_MODE_READ,
                        base::BindOnce(&CallbackLogger::OnOpenFile,
                                       base::Unretained(&callback_logger)));
-    open_file.SetDispatchEventImplForTesting(base::BindRepeating(
-        &util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-        base::Unretained(&dispatcher)));
 
     EXPECT_TRUE(open_file.Execute(kRequestId));
   }
 
   // Opening for write on a read-only file system is forbidden and must fail.
   {
-    OpenFile open_file(nullptr, read_only_file_system_info,
+    OpenFile open_file(&dispatcher, read_only_file_system_info,
                        base::FilePath(kFilePath), OPEN_FILE_MODE_WRITE,
                        base::BindOnce(&CallbackLogger::OnOpenFile,
                                       base::Unretained(&callback_logger)));
-    open_file.SetDispatchEventImplForTesting(base::BindRepeating(
-        &util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-        base::Unretained(&dispatcher)));
 
     EXPECT_FALSE(open_file.Execute(kRequestId));
   }
@@ -178,13 +166,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  OpenFile open_file(nullptr, file_system_info_, base::FilePath(kFilePath),
+  OpenFile open_file(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                      OPEN_FILE_MODE_READ,
                      base::BindOnce(&CallbackLogger::OnOpenFile,
                                     base::Unretained(&callback_logger)));
-  open_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(open_file.Execute(kRequestId));
 
@@ -200,13 +185,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  OpenFile open_file(nullptr, file_system_info_, base::FilePath(kFilePath),
+  OpenFile open_file(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                      OPEN_FILE_MODE_READ,
                      base::BindOnce(&CallbackLogger::OnOpenFile,
                                     base::Unretained(&callback_logger)));
-  open_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(open_file.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/operation.cc b/chrome/browser/ash/file_system_provider/operations/operation.cc
index 8fb51ea..fcfbc2b 100644
--- a/chrome/browser/ash/file_system_provider/operations/operation.cc
+++ b/chrome/browser/ash/file_system_provider/operations/operation.cc
@@ -7,18 +7,9 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
-#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
+#include "chrome/browser/ash/file_system_provider/event_dispatcher.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
-#include "chrome/browser/ash/file_system_provider/service.h"
-#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/common/webui_url_constants.h"
-#include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
 #include "extensions/browser/event_router.h"
-#include "extensions/common/extension_id.h"
 
 namespace ash {
 namespace file_system_provider {
@@ -26,110 +17,24 @@
 
 namespace {
 
-// This method is only used when Lacros is enabled. It's a callback from Lacros
-// indicating whether the operation was successfully forwarded. If the operation
-// could not be forwarded then the file system request manager must be informed.
-void OperationForwarded(ProviderId provider_id,
-                        const std::string& file_system_id,
-                        int request_id,
-                        bool delivery_failure) {
-  // Successful deliveries will go through the FileSystemProvider mojom path.
-  if (!delivery_failure)
-    return;
-  // When Lacros is enabled the primary profile is the only profile.
-  Service* const service =
-      Service::Get(ProfileManager::GetPrimaryUserProfile());
-  DCHECK(service);
-  ProvidedFileSystemInterface* const file_system =
-      service->GetProvidedFileSystem(provider_id, file_system_id);
-  if (!file_system)
-    return;
-  file_system->GetRequestManager()->RejectRequest(
-      request_id, std::make_unique<RequestValue>(),
-      base::File::FILE_ERROR_FAILED);
-}
-
-// Default implementation for dispatching an event. Can be replaced for unit
-// tests by Operation::SetDispatchEventImplForTest().
-bool DispatchEventImpl(extensions::EventRouter* event_router,
-                       const extensions::ExtensionId& extension_id,
-                       ProviderId provider_id,
-                       const std::string& file_system_id,
-                       int request_id,
-                       extensions::events::HistogramValue histogram_value,
-                       const std::string& event_name,
-                       base::Value::List event_args) {
-  // If ash has a matching extension, forward the event. This should not be
-  // needed once Lacros is the only browser on all devices.
-  if (event_router->ExtensionHasEventListener(extension_id, event_name)) {
-    event_router->DispatchEventToExtension(
-        extension_id, std::make_unique<extensions::Event>(
-                          histogram_value, event_name, std::move(event_args)));
-    return true;
-  }
-
-  if (extension_id == guest_os::kTerminalSystemAppId) {
-    GURL terminal(chrome::kChromeUIUntrustedTerminalURL);
-    if (event_router->URLHasEventListener(terminal, event_name)) {
-      event_router->DispatchEventToURL(
-          terminal, std::make_unique<extensions::Event>(
-                        histogram_value, event_name, std::move(event_args)));
-      return true;
-    }
-  }
-
-  // If there are any Lacros remotes, forward the message to the first one. This
-  // does not support multiple remotes.
-  auto& remotes = crosapi::CrosapiManager::Get()
-                      ->crosapi_ash()
-                      ->file_system_provider_service_ash()
-                      ->remotes();
-  if (!remotes.empty()) {
-    auto remote = remotes.begin();
-    auto callback = base::BindOnce(&OperationForwarded, provider_id,
-                                   file_system_id, request_id);
-    (*remote)->ForwardOperation(
-        extension_id, static_cast<int32_t>(histogram_value), event_name,
-        std::move(event_args), std::move(callback));
-  }
-  return !remotes.empty();
-}
 
 }  // namespace
 
-Operation::Operation(extensions::EventRouter* event_router,
+Operation::Operation(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info)
-    : file_system_info_(file_system_info),
-      dispatch_event_impl_(base::BindRepeating(
-          &DispatchEventImpl,
-          event_router,
-          file_system_info_.provider_id().GetExtensionId())) {}
+    : file_system_info_(file_system_info), event_dispatcher_(dispatcher) {}
 
 Operation::~Operation() {
 }
 
-void Operation::SetDispatchEventImplForTesting(
-    const DispatchEventImplCallback& callback) {
-  auto wrapped_callback = base::BindRepeating(
-      [](const DispatchEventImplCallback& callback, ProviderId provider_id,
-         const std::string& file_system_id, int request_id,
-         extensions::events::HistogramValue histogram_value,
-         const std::string& event_name, base::Value::List event_args) {
-        auto event = std::make_unique<extensions::Event>(
-            histogram_value, event_name, std::move(event_args));
-        return callback.Run(std::move(event));
-      },
-      callback);
-  dispatch_event_impl_ = wrapped_callback;
-}
-
 bool Operation::SendEvent(int request_id,
                           extensions::events::HistogramValue histogram_value,
                           const std::string& event_name,
                           base::Value::List event_args) {
-  return dispatch_event_impl_.Run(
-      file_system_info_.provider_id(), file_system_info_.file_system_id(),
-      request_id, histogram_value, event_name, std::move(event_args));
+  auto event = std::make_unique<extensions::Event>(histogram_value, event_name,
+                                                   std::move(event_args));
+  return event_dispatcher_->DispatchEvent(
+      request_id, file_system_info_.file_system_id(), std::move(event));
 }
 
 }  // namespace operations
diff --git a/chrome/browser/ash/file_system_provider/operations/operation.h b/chrome/browser/ash/file_system_provider/operations/operation.h
index c9dd3a4..d2db8ab7 100644
--- a/chrome/browser/ash/file_system_provider/operations/operation.h
+++ b/chrome/browser/ash/file_system_provider/operations/operation.h
@@ -16,22 +16,18 @@
 #include "extensions/browser/extension_event_histogram_value.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-struct Event;
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
+
+class ProvidedFileSystemInfo;
+class EventDispatcher;
+
 namespace operations {
 
 // Base class for operation bridges between fileapi and providing extensions.
 class Operation : public RequestManager::HandlerInterface {
  public:
-  using DispatchEventImplCallback =
-      base::RepeatingCallback<bool(std::unique_ptr<extensions::Event> event)>;
-
-  Operation(extensions::EventRouter* event_router,
+  Operation(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info);
 
   Operation(const Operation&) = delete;
@@ -48,10 +44,6 @@
                std::unique_ptr<RequestValue> result,
                base::File::Error error) override = 0;
 
-  // Sets custom dispatchign event implementation for tests.
-  void SetDispatchEventImplForTesting(
-      const DispatchEventImplCallback& callback);
-
  protected:
   // Sends an event to the providing extension. Returns false, if the providing
   // extension does not handle the |event_name| event.
@@ -63,14 +55,7 @@
   ProvidedFileSystemInfo file_system_info_;
 
  private:
-  using DispatchEventInternalCallback =
-      base::RepeatingCallback<bool(ProviderId provider_id,
-                                   const std::string& file_system_id,
-                                   int request_id,
-                                   extensions::events::HistogramValue,
-                                   const std::string&,
-                                   base::Value::List)>;
-  DispatchEventInternalCallback dispatch_event_impl_;
+  raw_ptr<EventDispatcher> event_dispatcher_;
 };
 
 }  // namespace operations
diff --git a/chrome/browser/ash/file_system_provider/operations/read_directory.cc b/chrome/browser/ash/file_system_provider/operations/read_directory.cc
index 3059d91e..1b1244b 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_directory.cc
+++ b/chrome/browser/ash/file_system_provider/operations/read_directory.cc
@@ -51,11 +51,11 @@
 }  // namespace
 
 ReadDirectory::ReadDirectory(
-    extensions::EventRouter* event_router,
+    EventDispatcher* dispatcher,
     const ProvidedFileSystemInfo& file_system_info,
     const base::FilePath& directory_path,
     storage::AsyncFileUtil::ReadDirectoryCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       directory_path_(directory_path),
       callback_(std::move(callback)) {}
 
diff --git a/chrome/browser/ash/file_system_provider/operations/read_directory.h b/chrome/browser/ash/file_system_provider/operations/read_directory.h
index 72aa164..c2ca3845 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_directory.h
+++ b/chrome/browser/ash/file_system_provider/operations/read_directory.h
@@ -17,10 +17,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -29,7 +25,7 @@
 // read directory request. Created per request.
 class ReadDirectory : public Operation {
  public:
-  ReadDirectory(extensions::EventRouter* event_router,
+  ReadDirectory(EventDispatcher* dispatcher,
                 const ProvidedFileSystemInfo& file_system_info,
                 const base::FilePath& directory_path,
                 storage::AsyncFileUtil::ReadDirectoryCallback callback);
diff --git a/chrome/browser/ash/file_system_provider/operations/read_directory_unittest.cc b/chrome/browser/ash/file_system_provider/operations/read_directory_unittest.cc
index 41027381..1a8b515 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_directory_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/read_directory_unittest.cc
@@ -125,12 +125,9 @@
   CallbackLogger callback_logger;
 
   ReadDirectory read_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       base::BindRepeating(&CallbackLogger::OnReadDirectory,
                           base::Unretained(&callback_logger)));
-  read_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_directory.Execute(kRequestId));
 
@@ -158,12 +155,9 @@
   CallbackLogger callback_logger;
 
   ReadDirectory read_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       base::BindRepeating(&CallbackLogger::OnReadDirectory,
                           base::Unretained(&callback_logger)));
-  read_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(read_directory.Execute(kRequestId));
 }
@@ -173,12 +167,9 @@
   CallbackLogger callback_logger;
 
   ReadDirectory read_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       base::BindRepeating(&CallbackLogger::OnReadDirectory,
                           base::Unretained(&callback_logger)));
-  read_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_directory.Execute(kRequestId));
 
@@ -220,12 +211,9 @@
   CallbackLogger callback_logger;
 
   ReadDirectory read_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       base::BindRepeating(&CallbackLogger::OnReadDirectory,
                           base::Unretained(&callback_logger)));
-  read_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_directory.Execute(kRequestId));
 
@@ -263,12 +251,9 @@
   CallbackLogger callback_logger;
 
   ReadDirectory read_directory(
-      nullptr, file_system_info_, base::FilePath(kDirectoryPath),
+      &dispatcher, file_system_info_, base::FilePath(kDirectoryPath),
       base::BindRepeating(&CallbackLogger::OnReadDirectory,
                           base::Unretained(&callback_logger)));
-  read_directory.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_directory.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/read_file.cc b/chrome/browser/ash/file_system_provider/operations/read_file.cc
index 55899c99..edaf837 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_file.cc
+++ b/chrome/browser/ash/file_system_provider/operations/read_file.cc
@@ -46,14 +46,14 @@
 }  // namespace
 
 ReadFile::ReadFile(
-    extensions::EventRouter* event_router,
+    EventDispatcher* dispatcher,
     const ProvidedFileSystemInfo& file_system_info,
     int file_handle,
     scoped_refptr<net::IOBuffer> buffer,
     int64_t offset,
     int length,
     ProvidedFileSystemInterface::ReadChunkReceivedCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       file_handle_(file_handle),
       buffer_(buffer),
       offset_(offset),
diff --git a/chrome/browser/ash/file_system_provider/operations/read_file.h b/chrome/browser/ash/file_system_provider/operations/read_file.h
index 597aff1..4d5f17e 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_file.h
+++ b/chrome/browser/ash/file_system_provider/operations/read_file.h
@@ -18,10 +18,6 @@
 #include "net/base/io_buffer.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -30,7 +26,7 @@
 // Created per request.
 class ReadFile : public Operation {
  public:
-  ReadFile(extensions::EventRouter* event_router,
+  ReadFile(EventDispatcher* dispatcher,
            const ProvidedFileSystemInfo& file_system_info,
            int file_handle,
            scoped_refptr<net::IOBuffer> buffer,
diff --git a/chrome/browser/ash/file_system_provider/operations/read_file_unittest.cc b/chrome/browser/ash/file_system_provider/operations/read_file_unittest.cc
index 5ac62664..228a72e 100644
--- a/chrome/browser/ash/file_system_provider/operations/read_file_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/read_file_unittest.cc
@@ -102,13 +102,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  ReadFile read_file(nullptr, file_system_info_, kFileHandle, io_buffer_.get(),
-                     kOffset, kLength,
+  ReadFile read_file(&dispatcher, file_system_info_, kFileHandle,
+                     io_buffer_.get(), kOffset, kLength,
                      base::BindRepeating(&CallbackLogger::OnReadFile,
                                          base::Unretained(&callback_logger)));
-  read_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_file.Execute(kRequestId));
 
@@ -136,13 +133,10 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  ReadFile read_file(nullptr, file_system_info_, kFileHandle, io_buffer_.get(),
-                     kOffset, kLength,
+  ReadFile read_file(&dispatcher, file_system_info_, kFileHandle,
+                     io_buffer_.get(), kOffset, kLength,
                      base::BindRepeating(&CallbackLogger::OnReadFile,
                                          base::Unretained(&callback_logger)));
-  read_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(read_file.Execute(kRequestId));
 }
@@ -154,13 +148,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  ReadFile read_file(nullptr, file_system_info_, kFileHandle, io_buffer_.get(),
-                     kOffset, kLength,
+  ReadFile read_file(&dispatcher, file_system_info_, kFileHandle,
+                     io_buffer_.get(), kOffset, kLength,
                      base::BindRepeating(&CallbackLogger::OnReadFile,
                                          base::Unretained(&callback_logger)));
-  read_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_file.Execute(kRequestId));
 
@@ -195,13 +186,10 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   CallbackLogger callback_logger;
 
-  ReadFile read_file(nullptr, file_system_info_, kFileHandle, io_buffer_.get(),
-                     kOffset, kLength,
+  ReadFile read_file(&dispatcher, file_system_info_, kFileHandle,
+                     io_buffer_.get(), kOffset, kLength,
                      base::BindRepeating(&CallbackLogger::OnReadFile,
                                          base::Unretained(&callback_logger)));
-  read_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(read_file.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/remove_watcher.cc b/chrome/browser/ash/file_system_provider/operations/remove_watcher.cc
index 5e18bc2..ac9a921 100644
--- a/chrome/browser/ash/file_system_provider/operations/remove_watcher.cc
+++ b/chrome/browser/ash/file_system_provider/operations/remove_watcher.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-RemoveWatcher::RemoveWatcher(extensions::EventRouter* event_router,
+RemoveWatcher::RemoveWatcher(EventDispatcher* dispatcher,
                              const ProvidedFileSystemInfo& file_system_info,
                              const base::FilePath& entry_path,
                              bool recursive,
                              storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       entry_path_(entry_path),
       recursive_(recursive),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/remove_watcher.h b/chrome/browser/ash/file_system_provider/operations/remove_watcher.h
index 5becc4e..7e01bc0 100644
--- a/chrome/browser/ash/file_system_provider/operations/remove_watcher.h
+++ b/chrome/browser/ash/file_system_provider/operations/remove_watcher.h
@@ -18,10 +18,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -29,7 +25,7 @@
 // Removes a watcher at |entry_path| with the |recursive| mode.
 class RemoveWatcher : public Operation {
  public:
-  RemoveWatcher(extensions::EventRouter* event_router,
+  RemoveWatcher(EventDispatcher* dispatcher,
                 const ProvidedFileSystemInfo& file_system_info,
                 const base::FilePath& entry_path,
                 bool recursive,
diff --git a/chrome/browser/ash/file_system_provider/operations/remove_watcher_unittest.cc b/chrome/browser/ash/file_system_provider/operations/remove_watcher_unittest.cc
index 7636d84..5bba538 100644
--- a/chrome/browser/ash/file_system_provider/operations/remove_watcher_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/remove_watcher_unittest.cc
@@ -56,12 +56,9 @@
   util::StatusCallbackLog callback_log;
 
   RemoveWatcher remove_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  remove_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(remove_watcher.Execute(kRequestId));
 
@@ -90,12 +87,9 @@
   util::StatusCallbackLog callback_log;
 
   RemoveWatcher remove_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  remove_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(remove_watcher.Execute(kRequestId));
 }
@@ -105,12 +99,9 @@
   util::StatusCallbackLog callback_log;
 
   RemoveWatcher remove_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  remove_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(remove_watcher.Execute(kRequestId));
 
@@ -125,12 +116,9 @@
   util::StatusCallbackLog callback_log;
 
   RemoveWatcher remove_watcher(
-      nullptr, file_system_info_, base::FilePath(kEntryPath),
+      &dispatcher, file_system_info_, base::FilePath(kEntryPath),
       true /* recursive */,
       base::BindOnce(&util::LogStatusCallback, &callback_log));
-  remove_watcher.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(remove_watcher.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/test_util.cc b/chrome/browser/ash/file_system_provider/operations/test_util.cc
index dfce2a5..b9fa5f3 100644
--- a/chrome/browser/ash/file_system_provider/operations/test_util.cc
+++ b/chrome/browser/ash/file_system_provider/operations/test_util.cc
@@ -17,10 +17,11 @@
     : dispatch_reply_(dispatch_reply) {
 }
 
-LoggingDispatchEventImpl::~LoggingDispatchEventImpl() {
-}
+LoggingDispatchEventImpl::~LoggingDispatchEventImpl() = default;
 
-bool LoggingDispatchEventImpl::OnDispatchEventImpl(
+bool LoggingDispatchEventImpl::DispatchEvent(
+    int request_id,
+    absl::optional<std::string> file_system_id,
     std::unique_ptr<extensions::Event> event) {
   events_.push_back(std::move(event));
   return dispatch_reply_;
diff --git a/chrome/browser/ash/file_system_provider/operations/test_util.h b/chrome/browser/ash/file_system_provider/operations/test_util.h
index a27764e..341d7f6 100644
--- a/chrome/browser/ash/file_system_provider/operations/test_util.h
+++ b/chrome/browser/ash/file_system_provider/operations/test_util.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/files/file.h"
+#include "chrome/browser/ash/file_system_provider/event_dispatcher.h"
 
 namespace extensions {
 struct Event;
@@ -21,17 +22,19 @@
 
 // Fake event dispatcher implementation with extra logging capability. Acts as
 // a providing extension end-point.
-class LoggingDispatchEventImpl {
+class LoggingDispatchEventImpl : public EventDispatcher {
  public:
   explicit LoggingDispatchEventImpl(bool dispatch_reply);
 
   LoggingDispatchEventImpl(const LoggingDispatchEventImpl&) = delete;
   LoggingDispatchEventImpl& operator=(const LoggingDispatchEventImpl&) = delete;
 
-  virtual ~LoggingDispatchEventImpl();
+  ~LoggingDispatchEventImpl() override;
 
   // Handles sending an event to a providing extension.
-  bool OnDispatchEventImpl(std::unique_ptr<extensions::Event> event);
+  bool DispatchEvent(int request_id,
+                     absl::optional<std::string> file_system_id,
+                     std::unique_ptr<extensions::Event> event) override;
 
   // Returns events sent to providing extensions.
   std::vector<std::unique_ptr<extensions::Event>>& events() { return events_; }
diff --git a/chrome/browser/ash/file_system_provider/operations/truncate.cc b/chrome/browser/ash/file_system_provider/operations/truncate.cc
index fb31670d..f9e9241 100644
--- a/chrome/browser/ash/file_system_provider/operations/truncate.cc
+++ b/chrome/browser/ash/file_system_provider/operations/truncate.cc
@@ -13,12 +13,12 @@
 namespace file_system_provider {
 namespace operations {
 
-Truncate::Truncate(extensions::EventRouter* event_router,
+Truncate::Truncate(EventDispatcher* dispatcher,
                    const ProvidedFileSystemInfo& file_system_info,
                    const base::FilePath& file_path,
                    int64_t length,
                    storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       file_path_(file_path),
       length_(length),
       callback_(std::move(callback)) {}
diff --git a/chrome/browser/ash/file_system_provider/operations/truncate.h b/chrome/browser/ash/file_system_provider/operations/truncate.h
index 0d1a5e5..49d8aba 100644
--- a/chrome/browser/ash/file_system_provider/operations/truncate.h
+++ b/chrome/browser/ash/file_system_provider/operations/truncate.h
@@ -20,10 +20,6 @@
 class FilePath;
 }  // namespace base
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -32,7 +28,7 @@
 // the FILE_ERROR_EXISTS error. Created per request.
 class Truncate : public Operation {
  public:
-  Truncate(extensions::EventRouter* event_router,
+  Truncate(EventDispatcher* dispatcher,
            const ProvidedFileSystemInfo& file_system_info,
            const base::FilePath& file_path,
            int64_t length,
diff --git a/chrome/browser/ash/file_system_provider/operations/truncate_unittest.cc b/chrome/browser/ash/file_system_provider/operations/truncate_unittest.cc
index 52446fb..e40c1d70 100644
--- a/chrome/browser/ash/file_system_provider/operations/truncate_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/truncate_unittest.cc
@@ -59,12 +59,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Truncate truncate(nullptr, file_system_info_, base::FilePath(kFilePath),
+  Truncate truncate(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                     kTruncateLength,
                     base::BindOnce(&util::LogStatusCallback, &callback_log));
-  truncate.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(truncate.Execute(kRequestId));
 
@@ -91,12 +88,9 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Truncate truncate(nullptr, file_system_info_, base::FilePath(kFilePath),
+  Truncate truncate(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                     kTruncateLength,
                     base::BindOnce(&util::LogStatusCallback, &callback_log));
-  truncate.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(truncate.Execute(kRequestId));
 }
@@ -110,12 +104,9 @@
       base::FilePath() /* mount_path */, false /* configurable */,
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
-  Truncate truncate(nullptr, file_system_info_, base::FilePath(kFilePath),
+  Truncate truncate(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                     kTruncateLength,
                     base::BindOnce(&util::LogStatusCallback, &callback_log));
-  truncate.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(truncate.Execute(kRequestId));
 }
@@ -124,12 +115,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Truncate truncate(nullptr, file_system_info_, base::FilePath(kFilePath),
+  Truncate truncate(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                     kTruncateLength,
                     base::BindOnce(&util::LogStatusCallback, &callback_log));
-  truncate.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(truncate.Execute(kRequestId));
 
@@ -143,12 +131,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Truncate truncate(nullptr, file_system_info_, base::FilePath(kFilePath),
+  Truncate truncate(&dispatcher, file_system_info_, base::FilePath(kFilePath),
                     kTruncateLength,
                     base::BindOnce(&util::LogStatusCallback, &callback_log));
-  truncate.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(truncate.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/unmount.cc b/chrome/browser/ash/file_system_provider/operations/unmount.cc
index fe168422..79860bd2 100644
--- a/chrome/browser/ash/file_system_provider/operations/unmount.cc
+++ b/chrome/browser/ash/file_system_provider/operations/unmount.cc
@@ -11,11 +11,10 @@
 namespace file_system_provider {
 namespace operations {
 
-Unmount::Unmount(extensions::EventRouter* event_router,
+Unmount::Unmount(EventDispatcher* dispatcher,
                  const ProvidedFileSystemInfo& file_system_info,
                  storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
-      callback_(std::move(callback)) {}
+    : Operation(dispatcher, file_system_info), callback_(std::move(callback)) {}
 
 Unmount::~Unmount() {
 }
diff --git a/chrome/browser/ash/file_system_provider/operations/unmount.h b/chrome/browser/ash/file_system_provider/operations/unmount.h
index 3043cd0..3188ea8f 100644
--- a/chrome/browser/ash/file_system_provider/operations/unmount.h
+++ b/chrome/browser/ash/file_system_provider/operations/unmount.h
@@ -11,10 +11,6 @@
 #include "chrome/browser/ash/file_system_provider/operations/operation.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 
@@ -26,7 +22,7 @@
 // extension's unmount request. Created per request.
 class Unmount : public Operation {
  public:
-  Unmount(extensions::EventRouter* event_router,
+  Unmount(EventDispatcher* dispatcher,
           const ProvidedFileSystemInfo& file_system_info,
           storage::AsyncFileUtil::StatusCallback callback);
 
diff --git a/chrome/browser/ash/file_system_provider/operations/unmount_unittest.cc b/chrome/browser/ash/file_system_provider/operations/unmount_unittest.cc
index b0d4906..649eff7 100644
--- a/chrome/browser/ash/file_system_provider/operations/unmount_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/unmount_unittest.cc
@@ -52,11 +52,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Unmount unmount(nullptr, file_system_info_,
+  Unmount unmount(&dispatcher, file_system_info_,
                   base::BindOnce(&util::LogStatusCallback, &callback_log));
-  unmount.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(unmount.Execute(kRequestId));
 
@@ -81,11 +78,8 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Unmount unmount(nullptr, file_system_info_,
+  Unmount unmount(&dispatcher, file_system_info_,
                   base::BindOnce(&util::LogStatusCallback, &callback_log));
-  unmount.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(unmount.Execute(kRequestId));
 }
@@ -97,11 +91,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Unmount unmount(nullptr, file_system_info_,
+  Unmount unmount(&dispatcher, file_system_info_,
                   base::BindOnce(&util::LogStatusCallback, &callback_log));
-  unmount.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(unmount.Execute(kRequestId));
 
@@ -116,11 +107,8 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  Unmount unmount(nullptr, file_system_info_,
+  Unmount unmount(&dispatcher, file_system_info_,
                   base::BindOnce(&util::LogStatusCallback, &callback_log));
-  unmount.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(unmount.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/operations/write_file.cc b/chrome/browser/ash/file_system_provider/operations/write_file.cc
index 681da1d..d89fe69 100644
--- a/chrome/browser/ash/file_system_provider/operations/write_file.cc
+++ b/chrome/browser/ash/file_system_provider/operations/write_file.cc
@@ -16,14 +16,14 @@
 namespace file_system_provider {
 namespace operations {
 
-WriteFile::WriteFile(extensions::EventRouter* event_router,
+WriteFile::WriteFile(EventDispatcher* dispatcher,
                      const ProvidedFileSystemInfo& file_system_info,
                      int file_handle,
                      scoped_refptr<net::IOBuffer> buffer,
                      int64_t offset,
                      int length,
                      storage::AsyncFileUtil::StatusCallback callback)
-    : Operation(event_router, file_system_info),
+    : Operation(dispatcher, file_system_info),
       file_handle_(file_handle),
       buffer_(buffer),
       offset_(offset),
diff --git a/chrome/browser/ash/file_system_provider/operations/write_file.h b/chrome/browser/ash/file_system_provider/operations/write_file.h
index cc7ffe18..06c1b77 100644
--- a/chrome/browser/ash/file_system_provider/operations/write_file.h
+++ b/chrome/browser/ash/file_system_provider/operations/write_file.h
@@ -18,10 +18,6 @@
 #include "net/base/io_buffer.h"
 #include "storage/browser/file_system/async_file_util.h"
 
-namespace extensions {
-class EventRouter;
-}  // namespace extensions
-
 namespace ash {
 namespace file_system_provider {
 namespace operations {
@@ -31,7 +27,7 @@
 // Created per request.
 class WriteFile : public Operation {
  public:
-  WriteFile(extensions::EventRouter* event_router,
+  WriteFile(EventDispatcher* dispatcher,
             const ProvidedFileSystemInfo& file_system_info,
             int file_handle,
             scoped_refptr<net::IOBuffer> buffer,
diff --git a/chrome/browser/ash/file_system_provider/operations/write_file_unittest.cc b/chrome/browser/ash/file_system_provider/operations/write_file_unittest.cc
index ac1cffa..75890efa 100644
--- a/chrome/browser/ash/file_system_provider/operations/write_file_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operations/write_file_unittest.cc
@@ -61,12 +61,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  WriteFile write_file(nullptr, file_system_info_, kFileHandle,
+  WriteFile write_file(&dispatcher, file_system_info_, kFileHandle,
                        io_buffer_.get(), kOffset, io_buffer_->size(),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  write_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(write_file.Execute(kRequestId));
 
@@ -96,12 +93,9 @@
   util::LoggingDispatchEventImpl dispatcher(false /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  WriteFile write_file(nullptr, file_system_info_, kFileHandle,
+  WriteFile write_file(&dispatcher, file_system_info_, kFileHandle,
                        io_buffer_.get(), kOffset, io_buffer_->size(),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  write_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(write_file.Execute(kRequestId));
 }
@@ -115,12 +109,9 @@
       base::FilePath() /* mount_path */, false /* configurable */,
       true /* watchable */, extensions::SOURCE_FILE, IconSet());
 
-  WriteFile write_file(nullptr, read_only_file_system_info, kFileHandle,
+  WriteFile write_file(&dispatcher, read_only_file_system_info, kFileHandle,
                        io_buffer_.get(), kOffset, io_buffer_->size(),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  write_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_FALSE(write_file.Execute(kRequestId));
 }
@@ -129,12 +120,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  WriteFile write_file(nullptr, file_system_info_, kFileHandle,
+  WriteFile write_file(&dispatcher, file_system_info_, kFileHandle,
                        io_buffer_.get(), kOffset, io_buffer_->size(),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  write_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(write_file.Execute(kRequestId));
 
@@ -148,12 +136,9 @@
   util::LoggingDispatchEventImpl dispatcher(true /* dispatch_reply */);
   util::StatusCallbackLog callback_log;
 
-  WriteFile write_file(nullptr, file_system_info_, kFileHandle,
+  WriteFile write_file(&dispatcher, file_system_info_, kFileHandle,
                        io_buffer_.get(), kOffset, io_buffer_->size(),
                        base::BindOnce(&util::LogStatusCallback, &callback_log));
-  write_file.SetDispatchEventImplForTesting(
-      base::BindRepeating(&util::LoggingDispatchEventImpl::OnDispatchEventImpl,
-                          base::Unretained(&dispatcher)));
 
   EXPECT_TRUE(write_file.Execute(kRequestId));
 
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.cc b/chrome/browser/ash/file_system_provider/provided_file_system.cc
index cbe1106..4f8c324 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.cc
@@ -13,6 +13,7 @@
 #include "base/files/file.h"
 #include "base/memory/ptr_util.h"
 #include "base/trace_event/trace_event.h"
+#include "chrome/browser/ash/file_system_provider/event_dispatcher_impl.h"
 #include "chrome/browser/ash/file_system_provider/notification_manager.h"
 #include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/operations/abort.h"
@@ -142,6 +143,10 @@
           profile,
           file_system_info.provider_id().GetExtensionId(),
           notification_manager_.get())),
+      event_dispatcher_(std::make_unique<EventDispatcherImpl>(
+          file_system_info_.provider_id().GetExtensionId(),
+          event_router_,
+          request_manager_.get())),
       watcher_queue_(1) {
   DCHECK_EQ(ProviderId::EXTENSION, file_system_info.provider_id().GetType());
 }
@@ -156,6 +161,9 @@
 void ProvidedFileSystem::SetEventRouterForTesting(
     extensions::EventRouter* event_router) {
   event_router_ = event_router;
+  event_dispatcher_ = std::make_unique<EventDispatcherImpl>(
+      file_system_info_.provider_id().GetExtensionId(), event_router_,
+      request_manager_.get());
 }
 
 void ProvidedFileSystem::SetNotificationManagerForTesting(
@@ -170,9 +178,9 @@
     storage::AsyncFileUtil::StatusCallback callback) {
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
-      REQUEST_UNMOUNT,
-      std::make_unique<operations::Unmount>(event_router_, file_system_info_,
-                                            std::move(split_callback.first)));
+      REQUEST_UNMOUNT, std::make_unique<operations::Unmount>(
+                           event_dispatcher_.get(), file_system_info_,
+                           std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
     return AbortCallback();
@@ -191,8 +199,8 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       GET_METADATA, std::make_unique<operations::GetMetadata>(
-                        event_router_, file_system_info_, entry_path, fields,
-                        std::move(split_callback.first)));
+                        event_dispatcher_.get(), file_system_info_, entry_path,
+                        fields, std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second)
         .Run(base::WrapUnique<EntryMetadata>(nullptr),
@@ -213,7 +221,7 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       GET_ACTIONS, std::make_unique<operations::GetActions>(
-                       event_router_, file_system_info_, entry_paths,
+                       event_dispatcher_.get(), file_system_info_, entry_paths,
                        std::move(split_callback.first)));
   if (!request_id) {
     // If the provider doesn't listen for GetActions requests, treat it as
@@ -232,9 +240,10 @@
     storage::AsyncFileUtil::StatusCallback callback) {
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
-      EXECUTE_ACTION, std::make_unique<operations::ExecuteAction>(
-                          event_router_, file_system_info_, entry_paths,
-                          action_id, std::move(split_callback.first)));
+      EXECUTE_ACTION,
+      std::make_unique<operations::ExecuteAction>(
+          event_dispatcher_.get(), file_system_info_, entry_paths, action_id,
+          std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
     return AbortCallback();
@@ -248,9 +257,9 @@
     const base::FilePath& directory_path,
     storage::AsyncFileUtil::ReadDirectoryCallback callback) {
   const int request_id = request_manager_->CreateRequest(
-      READ_DIRECTORY,
-      std::make_unique<operations::ReadDirectory>(
-          event_router_, file_system_info_, directory_path, callback));
+      READ_DIRECTORY, std::make_unique<operations::ReadDirectory>(
+                          event_dispatcher_.get(), file_system_info_,
+                          directory_path, callback));
   if (!request_id) {
     callback.Run(base::File::FILE_ERROR_SECURITY,
                  storage::AsyncFileUtil::EntryList(),
@@ -271,8 +280,8 @@
       "file_system_provider", "ProvidedFileSystem::ReadFile", "length", length);
   const int request_id = request_manager_->CreateRequest(
       READ_FILE, std::make_unique<operations::ReadFile>(
-                     event_router_, file_system_info_, file_handle, buffer,
-                     offset, length, callback));
+                     event_dispatcher_.get(), file_system_info_, file_handle,
+                     buffer, offset, length, callback));
   if (!request_id) {
     callback.Run(0 /* chunk_length */,
                  false /* has_more */,
@@ -292,11 +301,12 @@
   // signals an error (by returning request_id == 0).
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
-      OPEN_FILE, std::make_unique<operations::OpenFile>(
-                     event_router_, file_system_info_, file_path, mode,
-                     base::BindOnce(&ProvidedFileSystem::OnOpenFileCompleted,
-                                    weak_ptr_factory_.GetWeakPtr(), file_path,
-                                    mode, std::move(split_callback.first))));
+      OPEN_FILE,
+      std::make_unique<operations::OpenFile>(
+          event_dispatcher_.get(), file_system_info_, file_path, mode,
+          base::BindOnce(&ProvidedFileSystem::OnOpenFileCompleted,
+                         weak_ptr_factory_.GetWeakPtr(), file_path, mode,
+                         std::move(split_callback.first))));
   if (!request_id) {
     std::move(split_callback.second)
         .Run(0 /* file_handle */, base::File::FILE_ERROR_SECURITY);
@@ -314,7 +324,7 @@
   const int request_id = request_manager_->CreateRequest(
       CLOSE_FILE,
       std::make_unique<operations::CloseFile>(
-          event_router_, file_system_info_, file_handle,
+          event_dispatcher_.get(), file_system_info_, file_handle,
           base::BindOnce(&ProvidedFileSystem::OnCloseFileCompleted,
                          weak_ptr_factory_.GetWeakPtr(), file_handle,
                          std::move(split_callback.first))));
@@ -333,9 +343,10 @@
     storage::AsyncFileUtil::StatusCallback callback) {
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
-      CREATE_DIRECTORY, std::make_unique<operations::CreateDirectory>(
-                            event_router_, file_system_info_, directory_path,
-                            recursive, std::move(split_callback.first)));
+      CREATE_DIRECTORY,
+      std::make_unique<operations::CreateDirectory>(
+          event_dispatcher_.get(), file_system_info_, directory_path, recursive,
+          std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
     return AbortCallback();
@@ -352,8 +363,8 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       DELETE_ENTRY, std::make_unique<operations::DeleteEntry>(
-                        event_router_, file_system_info_, entry_path, recursive,
-                        std::move(split_callback.first)));
+                        event_dispatcher_.get(), file_system_info_, entry_path,
+                        recursive, std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
     return AbortCallback();
@@ -369,7 +380,7 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       CREATE_FILE, std::make_unique<operations::CreateFile>(
-                       event_router_, file_system_info_, file_path,
+                       event_dispatcher_.get(), file_system_info_, file_path,
                        std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
@@ -387,7 +398,7 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       COPY_ENTRY, std::make_unique<operations::CopyEntry>(
-                      event_router_, file_system_info_, source_path,
+                      event_dispatcher_.get(), file_system_info_, source_path,
                       target_path, std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
@@ -411,7 +422,7 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       WRITE_FILE, std::make_unique<operations::WriteFile>(
-                      event_router_, file_system_info_, file_handle,
+                      event_dispatcher_.get(), file_system_info_, file_handle,
                       base::WrapRefCounted(buffer), offset, length,
                       std::move(split_callback.first)));
   if (!request_id) {
@@ -430,7 +441,7 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       MOVE_ENTRY, std::make_unique<operations::MoveEntry>(
-                      event_router_, file_system_info_, source_path,
+                      event_dispatcher_.get(), file_system_info_, source_path,
                       target_path, std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
@@ -448,8 +459,8 @@
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
       TRUNCATE, std::make_unique<operations::Truncate>(
-                    event_router_, file_system_info_, file_path, length,
-                    std::move(split_callback.first)));
+                    event_dispatcher_.get(), file_system_info_, file_path,
+                    length, std::move(split_callback.first)));
   if (!request_id) {
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
     return AbortCallback();
@@ -536,20 +547,21 @@
     storage::AsyncFileUtil::StatusCallback callback) {
   auto split_callback = base::SplitOnceCallback(std::move(callback));
   const int request_id = request_manager_->CreateRequest(
-      CONFIGURE,
-      std::make_unique<operations::Configure>(event_router_, file_system_info_,
-                                              std::move(split_callback.first)));
+      CONFIGURE, std::make_unique<operations::Configure>(
+                     event_dispatcher_.get(), file_system_info_,
+                     std::move(split_callback.first)));
   if (!request_id)
     std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
 }
 
 void ProvidedFileSystem::Abort(int operation_request_id) {
   if (!request_manager_->CreateRequest(
-          ABORT, std::make_unique<operations::Abort>(
-                     event_router_, file_system_info_, operation_request_id,
-                     base::BindOnce(&ProvidedFileSystem::OnAbortCompleted,
-                                    weak_ptr_factory_.GetWeakPtr(),
-                                    operation_request_id)))) {
+          ABORT,
+          std::make_unique<operations::Abort>(
+              event_dispatcher_.get(), file_system_info_, operation_request_id,
+              base::BindOnce(&ProvidedFileSystem::OnAbortCompleted,
+                             weak_ptr_factory_.GetWeakPtr(),
+                             operation_request_id)))) {
     // If the aborting event is not handled, then the operation should simply
     // be not aborted. Instead we'll wait until it completes.
     LOG(ERROR) << "Failed to create an abort request.";
@@ -604,7 +616,8 @@
   const int request_id = request_manager_->CreateRequest(
       ADD_WATCHER,
       std::make_unique<operations::AddWatcher>(
-          event_router_, file_system_info_, args.entry_path, args.recursive,
+          event_dispatcher_.get(), file_system_info_, args.entry_path,
+          args.recursive,
           base::BindOnce(&ProvidedFileSystem::OnAddWatcherInQueueCompleted,
                          weak_ptr_factory_.GetWeakPtr(), args.token,
                          args.entry_path, args.recursive, subscriber,
@@ -648,7 +661,7 @@
   request_manager_->CreateRequest(
       REMOVE_WATCHER,
       std::make_unique<operations::RemoveWatcher>(
-          event_router_, file_system_info_, entry_path, recursive,
+          event_dispatcher_.get(), file_system_info_, entry_path, recursive,
           base::BindOnce(&ProvidedFileSystem::OnRemoveWatcherInQueueCompleted,
                          weak_ptr_factory_.GetWeakPtr(), token, origin, key,
                          std::move(callback), true /* extension_response */)));
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.h b/chrome/browser/ash/file_system_provider/provided_file_system.h
index 222aaa6..9152459 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.h
@@ -42,6 +42,7 @@
 namespace file_system_provider {
 
 class NotificationManagerInterface;
+class EventDispatcher;
 
 // Automatically calls the |update_callback| after all of the callbacks created
 // with |CreateCallback| are called.
@@ -249,6 +250,7 @@
   ProvidedFileSystemInfo file_system_info_;
   std::unique_ptr<NotificationManagerInterface> notification_manager_;
   std::unique_ptr<OperationRequestManager> request_manager_;
+  std::unique_ptr<EventDispatcher> event_dispatcher_;
   Watchers watchers_;
   Queue watcher_queue_;
   OpenedFiles opened_files_;
diff --git a/chrome/browser/ash/fusebox/fusebox_server.cc b/chrome/browser/ash/fusebox/fusebox_server.cc
index 7b45e2b..8ee5c21 100644
--- a/chrome/browser/ash/fusebox/fusebox_server.cc
+++ b/chrome/browser/ash/fusebox/fusebox_server.cc
@@ -292,33 +292,6 @@
           metadata_fields, std::move(outer_callback)));
 }
 
-void RunReadCallbackFailure(Server::ReadCallback callback,
-                            base::File::Error error_code) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::move(callback).Run(FileErrorToErrno(error_code), nullptr, 0);
-}
-
-void RunReadCallbackTypical(
-    Server::ReadCallback callback,
-    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
-    std::unique_ptr<storage::FileStreamReader> fs_reader,
-    scoped_refptr<net::IOBuffer> buffer,
-    int length) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (length < 0) {
-    std::move(callback).Run(NetErrorToErrno(length), nullptr, 0);
-  } else {
-    std::move(callback).Run(0, reinterpret_cast<uint8_t*>(buffer->data()),
-                            length);
-  }
-
-  auto task_runner = content::GetIOThreadTaskRunner({});
-  task_runner->DeleteSoon(FROM_HERE, fs_reader.release());
-  task_runner->ReleaseSoon(FROM_HERE, std::move(buffer));
-}
-
 void RunRead2CallbackFailure(Server::Read2Callback callback,
                              base::File::Error error_code) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -455,54 +428,6 @@
   std::move(callback).Run(response_proto);
 }
 
-void ReadOnIOThread(scoped_refptr<storage::FileSystemContext> fs_context,
-                    storage::FileSystemURL fs_url,
-                    int64_t offset,
-                    int64_t length,
-                    Server::ReadCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-
-  std::unique_ptr<storage::FileStreamReader> fs_reader =
-      fs_context->CreateFileStreamReader(fs_url, offset, length, base::Time());
-  if (!fs_reader) {
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(&RunReadCallbackFailure, std::move(callback),
-                                  base::File::Error::FILE_ERROR_INVALID_URL));
-    return;
-  }
-
-  scoped_refptr<net::IOBuffer> buffer =
-      base::MakeRefCounted<net::IOBuffer>(length);
-
-  // Save the pointer before we std::move fs_reader into a base::OnceCallback.
-  // The std::move keeps the underlying storage::FileStreamReader alive while
-  // any network I/O is pending. Without the std::move, the underlying
-  // storage::FileStreamReader would get destroyed at the end of this function.
-  auto* saved_fs_reader = fs_reader.get();
-
-  auto pair = base::SplitOnceCallback(base::BindPostTask(
-      content::GetUIThreadTaskRunner({}),
-      base::BindOnce(&RunReadCallbackTypical, std::move(callback), fs_context,
-                     std::move(fs_reader), buffer)));
-
-  int result =
-      saved_fs_reader->Read(buffer.get(), length, std::move(pair.first));
-  if (result != net::ERR_IO_PENDING) {  // The read was synchronous.
-    std::move(pair.second).Run(result);
-  }
-}
-
-void RunStatCallback(
-    Server::StatCallback callback,
-    scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
-    bool read_only,
-    base::File::Error error_code,
-    const base::File::Info& info) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::move(callback).Run(FileErrorToErrno(error_code), info, read_only);
-}
-
 void RunStat2Callback(
     Server::Stat2Callback callback,
     scoped_refptr<storage::FileSystemContext> fs_context,  // See § above.
@@ -850,21 +775,6 @@
   return base::Value(std::move(dict));
 }
 
-void Server::Close(const std::string& fs_url_as_string,
-                   CloseCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
-  if (common.error_code != base::File::Error::FILE_OK) {
-    std::move(callback).Run(FileErrorToErrno(common.error_code));
-    return;
-  }
-
-  // Fail with an invalid operation error for now. TODO(crbug.com/1249754)
-  // implement MTP device writing.
-  std::move(callback).Run(ENOTSUP);
-}
-
 void Server::Close2(const fusebox_staging::Close2RequestProto& request_proto,
                     Close2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -989,20 +899,6 @@
                      std::move(outer_callback)));
 }
 
-void Server::Open(const std::string& fs_url_as_string, OpenCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
-  if (common.error_code != base::File::Error::FILE_OK) {
-    std::move(callback).Run(FileErrorToErrno(common.error_code));
-    return;
-  }
-
-  // Fail with an invalid operation error for now. TODO(crbug.com/1249754)
-  // implement MTP device writing.
-  std::move(callback).Run(ENOTSUP);
-}
-
 void Server::Open2(const fusebox_staging::Open2RequestProto& request_proto,
                    Open2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -1037,24 +933,6 @@
   std::move(callback).Run(response_proto);
 }
 
-void Server::Read(const std::string& fs_url_as_string,
-                  int64_t offset,
-                  int32_t length,
-                  ReadCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
-  if (common.error_code != base::File::Error::FILE_OK) {
-    std::move(callback).Run(FileErrorToErrno(common.error_code), nullptr, 0);
-    return;
-  }
-
-  content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ReadOnIOThread, common.fs_context, common.fs_url, offset,
-                     static_cast<int64_t>(length), std::move(callback)));
-}
-
 void Server::Read2(const fusebox_staging::Read2RequestProto& request_proto,
                    Read2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -1181,40 +1059,6 @@
                      common.fs_url, std::move(outer_callback)));
 }
 
-void Server::Stat(const std::string& fs_url_as_string, StatCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
-  if (common.is_moniker_root) {
-    base::File::Info info;
-    info.is_directory = true;
-    std::move(callback).Run(0, info, false);
-    return;
-  } else if (common.error_code != base::File::Error::FILE_OK) {
-    std::move(callback).Run(FileErrorToErrno(common.error_code),
-                            base::File::Info(), false);
-    return;
-  }
-
-  constexpr auto metadata_fields =
-      storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
-      storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
-      storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED;
-
-  auto outer_callback =
-      base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
-                         base::BindOnce(&RunStatCallback, std::move(callback),
-                                        common.fs_context, common.read_only));
-
-  content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata),
-          // Unretained is safe: common.fs_context owns its operation_runner.
-          base::Unretained(common.fs_context->operation_runner()),
-          common.fs_url, metadata_fields, std::move(outer_callback)));
-}
-
 void Server::Stat2(const fusebox_staging::Stat2RequestProto& request_proto,
                    Stat2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/ash/fusebox/fusebox_server.h b/chrome/browser/ash/fusebox/fusebox_server.h
index 8da46a47..7a66406 100644
--- a/chrome/browser/ash/fusebox/fusebox_server.h
+++ b/chrome/browser/ash/fusebox/fusebox_server.h
@@ -88,12 +88,6 @@
   // POSIX filename that identifies a file or directory, but are a
   // storage::FileSystemURL (in string form).
 
-  // Close is a placeholder and is not implemented yet.
-  //
-  // TODO(crbug.com/1249754) implement MTP device writing.
-  using CloseCallback = base::OnceCallback<void(int32_t posix_error_code)>;
-  void Close(const std::string& fs_url_as_string, CloseCallback callback);
-
   // Close2 closes a virtual file opened by Open2.
   using Close2Callback = base::OnceCallback<void(
       const fusebox_staging::Close2ResponseProto& response)>;
@@ -112,26 +106,12 @@
   void MkDir(const fusebox_staging::MkDirRequestProto& request,
              MkDirCallback callback);
 
-  // Open is a placeholder and is not implemented yet.
-  //
-  // TODO(crbug.com/1249754) implement MTP device writing.
-  using OpenCallback = base::OnceCallback<void(int32_t posix_error_code)>;
-  void Open(const std::string& fs_url_as_string, OpenCallback callback);
-
   // Open2 opens a virtual file for reading and/or writing.
   using Open2Callback = base::OnceCallback<void(
       const fusebox_staging::Open2ResponseProto& response)>;
   void Open2(const fusebox_staging::Open2RequestProto& request,
              Open2Callback callback);
 
-  // Read returns the file's byte contents at the given offset and length.
-  using ReadCallback = base::OnceCallback<
-      void(int32_t posix_error_code, const uint8_t* data_ptr, size_t data_len)>;
-  void Read(const std::string& fs_url_as_string,
-            int64_t offset,
-            int32_t length,
-            ReadCallback callback);
-
   // Read2 reads from a virtual file opened by Open2.
   using Read2Callback = base::OnceCallback<void(
       const fusebox_staging::Read2ResponseProto& response)>;
@@ -158,15 +138,7 @@
   void RmDir(const fusebox_staging::RmDirRequestProto& request,
              RmDirCallback callback);
 
-  // Stat returns the file or directory's metadata.
-  using StatCallback = base::OnceCallback<void(int32_t posix_error_code,
-                                               const base::File::Info& info,
-                                               bool read_only)>;
-  void Stat(const std::string& fs_url_as_string, StatCallback callback);
-
   // Stat2 returns the file or directory's metadata.
-  //
-  // Unlike Stat, it speaks protobufs.
   using Stat2Callback = base::OnceCallback<void(
       const fusebox_staging::Stat2ResponseProto& response)>;
   void Stat2(const fusebox_staging::Stat2RequestProto& request,
diff --git a/chrome/browser/ash/input_method/autocorrect_enums.h b/chrome/browser/ash/input_method/autocorrect_enums.h
index 5431c626..dcd740b7 100644
--- a/chrome/browser/ash/input_method/autocorrect_enums.h
+++ b/chrome/browser/ash/input_method/autocorrect_enums.h
@@ -35,7 +35,8 @@
   kDefaultToForceEnabled = 5,
   kForceEnabledToDisabled = 6,
   kForceEnabledToDefault = 7,
-  kMaxValue = kForceEnabledToDefault,
+  kForceEnabledToEnabled = 8,
+  kMaxValue = kForceEnabledToEnabled,
 };
 
 // Must match with IMEAutocorrectCompatibilitySummary in enums.xml
diff --git a/chrome/browser/ash/input_method/pref_change_recorder.cc b/chrome/browser/ash/input_method/pref_change_recorder.cc
index 94b0e05..b7b13142 100644
--- a/chrome/browser/ash/input_method/pref_change_recorder.cc
+++ b/chrome/browser/ash/input_method/pref_change_recorder.cc
@@ -122,6 +122,11 @@
     return AutocorrectPrefStateTransition::kForceEnabledToDisabled;
   }
 
+  if (previous_value == AutocorrectPreference::kEnabledByDefault &&
+      new_value == AutocorrectPreference::kEnabled) {
+    return AutocorrectPrefStateTransition::kForceEnabledToEnabled;
+  }
+
   // Note that we do not record kEnabledByDefault to kDefault (this transition
   // would occur in a rampdown of the enabled by default experiment). Recording
   // this transition would require code to run outside of the enabled by default
diff --git a/chrome/browser/ash/input_method/pref_change_recorder_unittest.cc b/chrome/browser/ash/input_method/pref_change_recorder_unittest.cc
index 683adca..b6c4d1a 100644
--- a/chrome/browser/ash/input_method/pref_change_recorder_unittest.cc
+++ b/chrome/browser/ash/input_method/pref_change_recorder_unittest.cc
@@ -308,6 +308,48 @@
       /*expected_bucket_count=*/1);
 }
 
+TEST_P(RecordsEnabledByDefaultTransitions,
+       RecordsEnabledByDefaultToEnabledWhenSetToModest) {
+  const EnabledByDefaultMetricCase& test_case = GetParam();
+  base::HistogramTester histograms_;
+  FakeInputMethodOptions options(profile_.GetPrefs(), test_case.engine_id);
+  feature_list_.InitWithFeatures({features::kAutocorrectByDefault}, {});
+
+  // User was previously marked as active in the enabled by default group.
+  SetPhysicalKeyboardAutocorrectAsEnabledByDefault(profile_.GetPrefs(),
+                                                   test_case.engine_id);
+  // Start observing changes ...
+  PrefChangeRecorder recorder(profile_.GetPrefs());
+  options.SetPkAutocorrectLevel(1);  // set as enabled (modest)
+
+  histograms_.ExpectTotalCount(test_case.metric_name, 1);
+  histograms_.ExpectUniqueSample(
+      test_case.metric_name,
+      /*sample=*/AutocorrectPrefStateTransition::kForceEnabledToEnabled,
+      /*expected_bucket_count=*/1);
+}
+
+TEST_P(RecordsEnabledByDefaultTransitions,
+       RecordsEnabledByDefaultToEnabledWhenSetToAggressive) {
+  const EnabledByDefaultMetricCase& test_case = GetParam();
+  base::HistogramTester histograms_;
+  FakeInputMethodOptions options(profile_.GetPrefs(), test_case.engine_id);
+  feature_list_.InitWithFeatures({features::kAutocorrectByDefault}, {});
+
+  // User was previously marked as active in the enabled by default group.
+  SetPhysicalKeyboardAutocorrectAsEnabledByDefault(profile_.GetPrefs(),
+                                                   test_case.engine_id);
+  // Start observing changes ...
+  PrefChangeRecorder recorder(profile_.GetPrefs());
+  options.SetPkAutocorrectLevel(2);  // set as enabled (aggressive)
+
+  histograms_.ExpectTotalCount(test_case.metric_name, 1);
+  histograms_.ExpectUniqueSample(
+      test_case.metric_name,
+      /*sample=*/AutocorrectPrefStateTransition::kForceEnabledToEnabled,
+      /*expected_bucket_count=*/1);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     PrefChangeRecorderTest,
     RecordsEnabledByDefaultTransitions,
diff --git a/chrome/browser/ash/input_method/suggestions_service_client.cc b/chrome/browser/ash/input_method/suggestions_service_client.cc
index 680a8797..2c12e7f 100644
--- a/chrome/browser/ash/input_method/suggestions_service_client.cc
+++ b/chrome/browser/ash/input_method/suggestions_service_client.cc
@@ -37,6 +37,12 @@
     return MultiWordExperimentGroup::kGboardRelaxedB;
   if (finch_trial == "gboard_relaxed_c")
     return MultiWordExperimentGroup::kGboardRelaxedC;
+  if (finch_trial == "gboard_d")
+    return MultiWordExperimentGroup::kGboardD;
+  if (finch_trial == "gboard_e")
+    return MultiWordExperimentGroup::kGboardE;
+  if (finch_trial == "gboard_f")
+    return MultiWordExperimentGroup::kGboardF;
   return MultiWordExperimentGroup::kDefault;
 }
 
diff --git a/chrome/browser/ash/input_method/ui/indexed_suggestion_candidate_button.cc b/chrome/browser/ash/input_method/ui/indexed_suggestion_candidate_button.cc
index 5e552d8..968fb4c 100644
--- a/chrome/browser/ash/input_method/ui/indexed_suggestion_candidate_button.cc
+++ b/chrome/browser/ash/input_method/ui/indexed_suggestion_candidate_button.cc
@@ -13,9 +13,10 @@
 #include "ui/views/layout/flex_layout.h"
 
 namespace ui::ime {
-const int kTopBottomPadding = 4;
+const int kTopPadding = 4;
+const int kBottomPadding = 0;
 const int kLeftRightPadding = 2;
-const int kBetweenSpacing = 4;
+const int kBetweenSpacing = 1;
 const int kBorderRadius = 2;
 const int kCandidateSquareSide = 24;
 const views::Label::CustomFont kCandidateTextFont = {
@@ -40,7 +41,7 @@
       views::BoxLayout::Orientation::kVertical,
       gfx::Insets()
           .set_left_right(kLeftRightPadding, kLeftRightPadding)
-          .set_top_bottom(kTopBottomPadding, kTopBottomPadding),
+          .set_top_bottom(kTopPadding, kBottomPadding),
       /* between_child_spacing=*/kBetweenSpacing));
 
   if (create_legacy_candidate) {
diff --git a/chrome/browser/ash/lock_screen_apps/app_manager_impl_unittest.cc b/chrome/browser/ash/lock_screen_apps/app_manager_impl_unittest.cc
index 7443da7..81b0da5d 100644
--- a/chrome/browser/ash/lock_screen_apps/app_manager_impl_unittest.cc
+++ b/chrome/browser/ash/lock_screen_apps/app_manager_impl_unittest.cc
@@ -318,17 +318,17 @@
     std::string version = test_app.version;
     bool supports_lock_screen = test_app.supports_lock_screen;
 
-    std::unique_ptr<base::DictionaryValue> background =
+    base::Value::Dict background =
         DictionaryBuilder()
-            .Set("scripts", ListBuilder().Append("background.js").Build())
-            .Build();
-    std::unique_ptr<base::ListValue> action_handlers =
+            .Set("scripts", ListBuilder().Append("background.js").BuildList())
+            .BuildDict();
+    base::Value::List action_handlers =
         ListBuilder()
             .Append(DictionaryBuilder()
                         .Set("action", "new_note")
                         .Set("enabled_on_lock_screen", supports_lock_screen)
-                        .Build())
-            .Build();
+                        .BuildDict())
+            .BuildList();
 
     DictionaryBuilder manifest_builder;
     manifest_builder.Set("name", "Note taking app")
@@ -336,8 +336,8 @@
         .Set("manifest_version", 2)
         .Set("app", DictionaryBuilder()
                         .Set("background", std::move(background))
-                        .Build())
-        .Set("permissions", ListBuilder().Append("lockScreen").Build())
+                        .BuildDict())
+        .Set("permissions", ListBuilder().Append("lockScreen").BuildList())
         .Set("action_handlers", std::move(action_handlers));
 
     base::FilePath extension_path =
@@ -345,7 +345,7 @@
 
     scoped_refptr<const extensions::Extension> extension =
         extensions::ExtensionBuilder()
-            .SetManifest(manifest_builder.Build())
+            .SetManifest(manifest_builder.BuildDict())
             .SetID(id)
             .SetPath(extension_path)
             .SetLocation(GetAppLocation(appType))
diff --git a/chrome/browser/ash/lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc b/chrome/browser/ash/lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc
index f4611d2..20ab5c9 100644
--- a/chrome/browser/ash/lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc
+++ b/chrome/browser/ash/lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc
@@ -295,17 +295,17 @@
 
   // Creates a lock screen enabled note taking app.
   scoped_refptr<const extensions::Extension> CreateTestNoteTakingApp() {
-    std::unique_ptr<base::DictionaryValue> background =
+    base::Value::Dict background =
         DictionaryBuilder()
-            .Set("scripts", ListBuilder().Append("background.js").Build())
-            .Build();
-    std::unique_ptr<base::ListValue> action_handlers =
+            .Set("scripts", ListBuilder().Append("background.js").BuildList())
+            .BuildDict();
+    base::Value::List action_handlers =
         ListBuilder()
             .Append(DictionaryBuilder()
                         .Set("action", "new_note")
                         .Set("enabled_on_lock_screen", true)
-                        .Build())
-            .Build();
+                        .BuildDict())
+            .BuildList();
 
     DictionaryBuilder manifest_builder;
     manifest_builder.Set("name", "Note taking app")
@@ -313,12 +313,12 @@
         .Set("version", "1.1")
         .Set("app", DictionaryBuilder()
                         .Set("background", std::move(background))
-                        .Build())
-        .Set("permissions", ListBuilder().Append("lockScreen").Build())
+                        .BuildDict())
+        .Set("permissions", ListBuilder().Append("lockScreen").BuildList())
         .Set("action_handlers", std::move(action_handlers));
 
     return extensions::ExtensionBuilder()
-        .SetManifest(manifest_builder.Build())
+        .SetManifest(manifest_builder.BuildDict())
         .SetID(crx_file::id_util::GenerateId("test_app"))
         .Build();
   }
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_installer.cc b/chrome/browser/ash/plugin_vm/plugin_vm_installer.cc
index b39d9c2..9d37d62a 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_installer.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_installer.cc
@@ -53,7 +53,6 @@
 constexpr int64_t kDownloadSizeFallbackEstimate = 15LL * kBytesPerGigabyte;
 
 constexpr char kFailureReasonHistogram[] = "PluginVm.SetupFailureReason";
-constexpr char kSetupTimeHistogram[] = "PluginVm.SetupTime";
 
 constexpr char kHomeDirectory[] = "/home/chronos/user";
 
@@ -152,7 +151,6 @@
   // goes back to kIdle.
   GetWakeLock()->RequestWakeLock();
   state_ = State::kInstalling;
-  setup_start_tick_ = base::TimeTicks::Now();
   progress_ = 0;
 
   // Perform the first step asynchronously to ensure OnError() isn't called
@@ -259,7 +257,6 @@
     return;
   }
 
-  RecordPluginVmImageDownloadedSizeHistogram(info.bytes_downloaded);
   StartImport();
 }
 
@@ -850,8 +847,6 @@
 
 void PluginVmInstaller::InstallFinished() {
   LOG_FUNCTION_CALL();
-  base::UmaHistogramLongTimes(kSetupTimeHistogram,
-                              base::TimeTicks::Now() - setup_start_tick_);
   state_ = State::kIdle;
   GetWakeLock()->CancelWakeLock();
   installing_state_ = InstallingState::kInactive;
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_installer.h b/chrome/browser/ash/plugin_vm/plugin_vm_installer.h
index b5fb3bc4..3f3bb76 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_installer.h
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_installer.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
-#include "base/time/time.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_license_checker.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_service.pb.h"
@@ -273,7 +272,6 @@
   download::BackgroundDownloadService* download_service_ = nullptr;
   State state_ = State::kIdle;
   InstallingState installing_state_ = InstallingState::kInactive;
-  base::TimeTicks setup_start_tick_;
   std::string current_download_guid_;
   base::FilePath downloaded_image_;
   // Used to identify our running import with concierge.
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_installer_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_installer_unittest.cc
index b2008c5..9666b30 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_installer_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_installer_unittest.cc
@@ -76,7 +76,6 @@
 const char kHash2[] =
     "02f06421ae27144aacdc598aebcd345a5e2e634405e8578300173628fe1574bd";
 // File size set in test_download_service.
-const int kDownloadedPluginVmImageSizeInMb = 123456789u / (1024 * 1024);
 const int64_t kDefaultRequiredFreeDiskSpaceGB = 20LL;
 const int kRequiredFreeDiskSpaceGB = 40;
 const int64_t kBytesPerGigabyte = 1024 * 1024 * 1024;
@@ -526,8 +525,8 @@
 
   EXPECT_FALSE(installer_->IsProcessing());
 
-  histogram_tester_->ExpectUniqueSample(kPluginVmImageDownloadedSizeHistogram,
-                                        kDownloadedPluginVmImageSizeInMb, 1);
+  histogram_tester_->ExpectUniqueSample(kPluginVmSetupResultHistogram,
+                                        PluginVmSetupResult::kSuccess, 1);
 }
 
 TEST_F(PluginVmInstallerDownloadServiceTest,
@@ -547,8 +546,6 @@
   installer_->SetDownloadedImageForTesting(CreateZipFile());
   StartAndRunToCompletion();
 
-  histogram_tester_->ExpectUniqueSample(kPluginVmImageDownloadedSizeHistogram,
-                                        kDownloadedPluginVmImageSizeInMb, 2);
   histogram_tester_->ExpectUniqueSample(kPluginVmSetupResultHistogram,
                                         PluginVmSetupResult::kSuccess, 2);
 }
@@ -573,8 +570,6 @@
 
   StartAndRunToCompletion();
 
-  histogram_tester_->ExpectUniqueSample(kPluginVmImageDownloadedSizeHistogram,
-                                        kDownloadedPluginVmImageSizeInMb, 1);
   histogram_tester_->ExpectBucketCount(kPluginVmSetupResultHistogram,
                                        PluginVmSetupResult::kError, 1);
   histogram_tester_->ExpectBucketCount(kPluginVmSetupResultHistogram,
@@ -589,7 +584,6 @@
   installer_->Cancel();
   task_environment_.RunUntilIdle();
 
-  histogram_tester_->ExpectTotalCount(kPluginVmImageDownloadedSizeHistogram, 0);
   histogram_tester_->ExpectTotalCount(kFailureReasonHistogram, 0);
   histogram_tester_->ExpectUniqueSample(
       kPluginVmSetupResultHistogram,
@@ -604,9 +598,6 @@
 
   installer_->SetDownloadedImageForTesting(base::FilePath());
   StartAndRunToCompletion();
-
-  histogram_tester_->ExpectUniqueSample(kPluginVmImageDownloadedSizeHistogram,
-                                        kDownloadedPluginVmImageSizeInMb, 1);
 }
 
 TEST_F(PluginVmInstallerDownloadServiceTest, ImportFailedOutOfSpaceTest) {
@@ -645,7 +636,6 @@
   EXPECT_CALL(*observer_, OnError(FailureReason::INVALID_IMAGE_URL));
   StartAndRunToCompletion();
 
-  histogram_tester_->ExpectTotalCount(kPluginVmImageDownloadedSizeHistogram, 0);
   histogram_tester_->ExpectUniqueSample(kFailureReasonHistogram,
                                         FailureReason::INVALID_IMAGE_URL, 1);
 }
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.cc b/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.cc
index 29bf2e0..08c59309 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.cc
@@ -6,18 +6,10 @@
 
 namespace plugin_vm {
 
-const char kPluginVmImageDownloadedSizeHistogram[] =
-    "PluginVm.Image.DownloadedSize";
 const char kPluginVmLaunchResultHistogram[] = "PluginVm.LaunchResult";
 const char kPluginVmSetupResultHistogram[] = "PluginVm.SetupResult";
 const char kPluginVmDlcUseResultHistogram[] = "PluginVm.DlcUseResult";
 
-void RecordPluginVmImageDownloadedSizeHistogram(uint64_t bytes_downloaded) {
-  uint64_t megabytes_downloaded = bytes_downloaded / (1024 * 1024);
-  base::UmaHistogramMemoryLargeMB(kPluginVmImageDownloadedSizeHistogram,
-                                  megabytes_downloaded);
-}
-
 void RecordPluginVmLaunchResultHistogram(PluginVmLaunchResult launch_result) {
   base::UmaHistogramEnumeration(kPluginVmLaunchResultHistogram, launch_result);
 }
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.h b/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.h
index 389d792..ddd6eaf 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.h
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_metrics_util.h
@@ -9,7 +9,6 @@
 
 namespace plugin_vm {
 
-extern const char kPluginVmImageDownloadedSizeHistogram[];
 extern const char kPluginVmLaunchResultHistogram[];
 extern const char kPluginVmSetupResultHistogram[];
 extern const char kPluginVmDlcUseResultHistogram[];
@@ -66,7 +65,6 @@
   kMaxValue = kNoImageFoundDlcError,
 };
 
-void RecordPluginVmImageDownloadedSizeHistogram(uint64_t bytes_downloaded);
 void RecordPluginVmLaunchResultHistogram(PluginVmLaunchResult launch_result);
 void RecordPluginVmSetupResultHistogram(PluginVmSetupResult setup_result);
 void RecordPluginVmDlcUseResultHistogram(PluginVmDlcUseResult dlc_use_result);
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
index ee444879..f661fa5 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
@@ -523,11 +523,15 @@
 
 void PersonalizationAppAmbientProviderImpl::FetchGooglePhotosAlbumsPreviews(
     const std::vector<std::string>& album_ids) {
+  const int num_previews = features::IsAmbientSubpageUIChangeEnabled() ? 3 : 4;
+  const int preview_width =
+      features::IsAmbientSubpageUIChangeEnabled() ? 360 : kBannerWidthPx;
+  const int preview_height =
+      features::IsAmbientSubpageUIChangeEnabled() ? 130 : kBannerHeightPx;
   DCHECK(!album_ids.empty());
   google_photos_albums_previews_weak_factory_.InvalidateWeakPtrs();
   ash::AmbientBackendController::Get()->GetGooglePhotosAlbumsPreview(
-      album_ids, kBannerWidthPx, kBannerHeightPx,
-      /*num_previews=*/4,
+      album_ids, preview_width, preview_height, num_previews,
       base::BindOnce(&PersonalizationAppAmbientProviderImpl::
                          OnGooglePhotosAlbumsPreviewsFetched,
                      google_photos_albums_previews_weak_factory_.GetWeakPtr()));
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
index ac3b775..55238f5d 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
@@ -359,14 +359,15 @@
   wallpaper_provider_remote()->FlushForTesting();
 
   EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
-  EXPECT_EQ(
-      ash::WallpaperInfo(
-          {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
-           image_info.asset_id, image_info.image_url, "collection_id",
-           ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-           /*preview_mode=*/false, /*from_user=*/true,
-           /*daily_refresh_enabled=*/false, image_info.unit_id, variants}),
-      test_wallpaper_controller()->wallpaper_info().value());
+  EXPECT_TRUE(
+      test_wallpaper_controller()->wallpaper_info().value().MatchesSelection(
+          ash::WallpaperInfo(
+              {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
+               image_info.asset_id, image_info.image_url, "collection_id",
+               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+               /*preview_mode=*/false, /*from_user=*/true,
+               /*daily_refresh_enabled=*/false, image_info.unit_id,
+               variants})));
 }
 
 TEST_F(PersonalizationAppWallpaperProviderImplTest, PreviewWallpaper) {
@@ -385,14 +386,15 @@
   wallpaper_provider_remote()->FlushForTesting();
 
   EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
-  EXPECT_EQ(
-      ash::WallpaperInfo(
-          {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
-           image_info.asset_id, image_info.image_url, "collection_id",
-           ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-           /*preview_mode=*/true, /*from_user=*/true,
-           /*daily_refresh_enabled=*/false, image_info.unit_id, variants}),
-      test_wallpaper_controller()->wallpaper_info().value());
+  EXPECT_TRUE(
+      test_wallpaper_controller()->wallpaper_info().value().MatchesSelection(
+          ash::WallpaperInfo(
+              {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
+               image_info.asset_id, image_info.image_url, "collection_id",
+               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+               /*preview_mode=*/true, /*from_user=*/true,
+               /*daily_refresh_enabled=*/false, image_info.unit_id,
+               variants})));
 }
 
 TEST_F(PersonalizationAppWallpaperProviderImplTest,
@@ -963,13 +965,15 @@
 
   EXPECT_EQ(0,
             test_wallpaper_controller()->set_google_photos_wallpaper_count());
-  EXPECT_NE(ash::WallpaperInfo(
-                {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
-                 photo_id, /*daily_refresh_enabled=*/false,
-                 ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-                 /*preview_mode=*/false, "dedup_key"}),
-            test_wallpaper_controller()->wallpaper_info().value_or(
-                ash::WallpaperInfo()));
+  EXPECT_FALSE(
+      test_wallpaper_controller()
+          ->wallpaper_info()
+          .value_or(ash::WallpaperInfo())
+          .MatchesSelection(ash::WallpaperInfo(
+              {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
+               photo_id, /*daily_refresh_enabled=*/false,
+               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+               /*preview_mode=*/false, "dedup_key"})));
 
   // Test selecting a wallpaper after fetching the enterprise setting.
   FetchGooglePhotosEnabled();
@@ -981,13 +985,15 @@
 
   EXPECT_EQ(1,
             test_wallpaper_controller()->set_google_photos_wallpaper_count());
-  EXPECT_EQ(ash::WallpaperInfo(
-                {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
-                 photo_id, /*daily_refresh_enabled=*/false,
-                 ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-                 /*preview_mode=*/false, "dedup_key"}),
-            test_wallpaper_controller()->wallpaper_info().value_or(
-                ash::WallpaperInfo()));
+  EXPECT_TRUE(
+      test_wallpaper_controller()
+          ->wallpaper_info()
+          .value_or(ash::WallpaperInfo())
+          .MatchesSelection(ash::WallpaperInfo(
+              {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
+               photo_id, /*daily_refresh_enabled=*/false,
+               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+               /*preview_mode=*/false, "dedup_key"})));
 }
 
 TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
diff --git a/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc b/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc
index 6dfddde..5bc64f4 100644
--- a/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc
+++ b/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc
@@ -203,18 +203,33 @@
       net::DefineNetworkTrafficAnnotation("wilco_dtc_supportd", R"(
           semantics {
             sender: "WilcoDtcSupportd"
-            description: "Perform a web request."
+            description: 
+            "Wilco VM is used by Dell to run Dell SupportAssist product on ChromeOS" 
+              "for managed users only. Dell SupportAssist proactively monitors device health."
+              "Dell binaries are running inside the VM and can perform web requests through"
+              "first-party wilco_dtc_supportd daemon to communicate with a Dell server."
             trigger:
-                "wilco_dtc_supportd performs a web request to their server."
+            "If policy is enabled, globally running Wilco VM performs web requests. No user action "
+                  "required to trigger web request."
             data:
                 "wilco_dtc_supportd's proprietary data."
             destination: OTHER
           }
           policy {
             cookies_allowed: NO
-          }
+            policy_exception_justification:
+            "Controlled by DeviceWilcoDtcAllowed. "
+            "chrome_device_policy not supported by auditor yet."
+            # TODO(b/210911671): remove comments once the bug is fixed
+            #chrome_policy {
+            #DeviceWilcoDtcAllowed {
+            #    DeviceWilcoDtcAllowed: false
+            #  }
+            #} 
+            }
       )");
 
+
   // Start new web request.
   active_request_ = std::move(request_queue_.front());
   request_queue_.pop();
diff --git a/chrome/browser/breadcrumbs/crash_reporter_breadcrumb_observer_unittest.cc b/chrome/browser/breadcrumbs/crash_reporter_breadcrumb_observer_unittest.cc
index de227566..7966127 100644
--- a/chrome/browser/breadcrumbs/crash_reporter_breadcrumb_observer_unittest.cc
+++ b/chrome/browser/breadcrumbs/crash_reporter_breadcrumb_observer_unittest.cc
@@ -55,11 +55,6 @@
   }
 
   void TearDown() override {
-    // Clear the CrashReporterBreadcrumbObserver singleton state to
-    // avoid polluting other tests.
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
-
     // TODO(crbug.com/1269414) This should call
     // crash_reporter::ResetCrashKeysForTesting() once
     // ChromeUserManagerImpl::UpdateNumberOfUsers allows the static
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index 2a90165..7436814 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -1213,7 +1213,7 @@
     // Get stored persistent breadcrumbs from last run to set on crash reports.
     GetBreadcrumbPersistentStorageManager()->GetStoredEvents(
         base::BindOnce([](std::vector<std::string> events) {
-          breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
+          breadcrumbs::BreadcrumbManager::GetInstance()
               .SetPreviousSessionEvents(events);
         }));
   } else {
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 0e855b21..c4de49b 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -148,12 +148,14 @@
         </if>
 
         <include name="IDR_CROSTINI_INSTALLER_INDEX_HTML" file="resources\chromeos\crostini_installer\index.html" type="BINDATA" />
-        <include name="IDR_CROSTINI_INSTALLER_APP_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\crostini_installer\app.js" type="BINDATA" use_base_dir="false" />
+        <include name="IDR_CROSTINI_INSTALLER_APP_JS" file="resources\chromeos\crostini_installer\app.js" type="BINDATA" />
+        <include name="IDR_CROSTINI_INSTALLER_APP_HTML_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\crostini_installer\app.html.js" type="BINDATA" use_base_dir="false" />
         <include name="IDR_CROSTINI_INSTALLER_BROWSER_PROXY_JS" file="resources\chromeos\crostini_installer\browser_proxy.js" type="BINDATA" />
         <include name="IDR_CROSTINI_INSTALLER_MOJO_LITE_JS" file="${root_gen_dir}\chrome\browser\ui\webui\ash\crostini_installer\crostini_installer.mojom-lite.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_CROSTINI_INSTALLER_TYPES_MOJO_LITE_JS" file="${root_gen_dir}\chrome\browser\ash\crostini\crostini_types.mojom-lite.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_CROSTINI_UPGRADER_INDEX_HTML" file="resources\chromeos\crostini_upgrader\index.html" type="BINDATA" />
-        <include name="IDR_CROSTINI_UPGRADER_APP_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\crostini_upgrader\app.js" type="BINDATA" use_base_dir="false" />
+        <include name="IDR_CROSTINI_UPGRADER_APP_JS" file="resources\chromeos\crostini_upgrader\app.js" type="BINDATA" />
+        <include name="IDR_CROSTINI_UPGRADER_APP_HTML_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\crostini_upgrader\app.html.js" type="BINDATA" use_base_dir="false" />
         <include name="IDR_CROSTINI_UPGRADER_BROWSER_PROXY_JS" file="resources\chromeos\crostini_upgrader\browser_proxy.js" type="BINDATA" />
         <include name="IDR_CROSTINI_UPGRADER_MOJO_LITE_JS" file="${root_gen_dir}\chrome\browser\ui\webui\ash\crostini_upgrader\crostini_upgrader.mojom-lite.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_CRYPTOHOME_HTML" file="resources\chromeos\cryptohome.html" type="BINDATA" />
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index e6dc260a..a80897c 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -1031,7 +1031,8 @@
                                          TabSearchUI>(map);
   if (base::FeatureList::IsEnabled(features::kTabSearchUseMetricsReporter)) {
     RegisterWebUIControllerInterfaceBinder<
-        metrics_reporter::mojom::PageMetricsHost, TabSearchUI>(map);
+        metrics_reporter::mojom::PageMetricsHost, TabSearchUI, NewTabPageUI>(
+        map);
   }
 
   RegisterWebUIControllerInterfaceBinder<
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 3f60005f..10a1c5a 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -50,6 +50,7 @@
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/os_crypt/app_bound_encryption_metrics_win.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -567,6 +568,10 @@
 void ChromeBrowserMainPartsWin::PostMainMessageLoopRun() {
   base::ImportantFileWriterCleaner::GetInstance().Stop();
 
+  // The `ProfileManager` has been destroyed, so no new platform authentication
+  // requests will be created.
+  platform_auth_policy_observer_.reset();
+
   ChromeBrowserMainParts::PostMainMessageLoopRun();
 }
 
@@ -583,6 +588,12 @@
   // needs to be done before any child processes are initialized as the
   // `ModuleDatabase` is an endpoint for IPC from child processes.
   SetupModuleDatabase(&module_watcher_);
+
+  // Start up the platform auth SSO policy observer.
+  PrefService* const local_state = g_browser_process->local_state();
+  if (local_state)
+    platform_auth_policy_observer_ =
+        std::make_unique<PlatformAuthPolicyObserver>(local_state);
 }
 
 void ChromeBrowserMainPartsWin::PostProfileInit(Profile* profile,
diff --git a/chrome/browser/chrome_browser_main_win.h b/chrome/browser/chrome_browser_main_win.h
index ac1cb28..14a3cf5 100644
--- a/chrome/browser/chrome_browser_main_win.h
+++ b/chrome/browser/chrome_browser_main_win.h
@@ -12,6 +12,8 @@
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/common/conflicts/module_watcher_win.h"
 
+class PlatformAuthPolicyObserver;
+
 namespace base {
 class CommandLine;
 }
@@ -82,6 +84,9 @@
 
   // Watches module load events and forwards them to the ModuleDatabase.
   std::unique_ptr<ModuleWatcher> module_watcher_;
+
+  // Applies enterprise policies for platform auth SSO.
+  std::unique_ptr<PlatformAuthPolicyObserver> platform_auth_policy_observer_;
 };
 
 #endif  // CHROME_BROWSER_CHROME_BROWSER_MAIN_WIN_H_
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 44ee095..b59d740 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -86,6 +86,7 @@
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h"
 #include "chrome/browser/payments/payment_request_display_manager_factory.h"
 #include "chrome/browser/performance_manager/public/chrome_browser_main_extra_parts_performance_manager.h"
 #include "chrome/browser/performance_manager/public/chrome_content_browser_client_performance_manager_part.h"
@@ -1735,6 +1736,9 @@
   main_parts->AddParts(
       std::make_unique<ChromeBrowserMainExtraPartsSegmentationPlatform>());
 
+  main_parts->AddParts(
+      std::make_unique<ChromeBrowserMainExtraPartsOptimizationGuide>());
+
   return main_parts;
 }
 
@@ -7158,7 +7162,8 @@
   // at the start of navigation as part of the preloading holdback, so ignore
   // the Finch setting here.
   return prefetch::IsSomePreloadingEnabledIgnoringFinch(
-      *Profile::FromBrowserContext(browser_context)->GetPrefs());
+             *Profile::FromBrowserContext(browser_context)->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 bool ChromeContentBrowserClient::ShouldPreconnectNavigation(
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 73de2434..d8db703 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -50,7 +50,7 @@
     "//chromeos/strings",
     "//components/live_caption:constants",
     "//components/policy/proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/signin/core/browser",
     "//components/startup_metric_utils/browser:browser",
     "//mojo/public/cpp/bindings",
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
index 48c7964f..3b113f5 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
@@ -22,10 +22,12 @@
 #include "storage/browser/file_system/watcher_manager.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
 #include "chrome/browser/ash/guest_os/guest_os_terminal.h"
 #include "chrome/common/webui_url_constants.h"
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace extensions {
 namespace {
@@ -382,6 +384,32 @@
   return RespondLater();
 }
 
+bool FileSystemProviderInternal::ForwardOperationResultImpl(
+    crosapi::mojom::FSPOperationResponse response,
+    crosapi::mojom::FileSystemIdPtr file_system_id,
+    int request_id,
+    base::Value::List args) {
+  auto callback =
+      base::BindOnce(&FileSystemProviderInternal::RespondWithError, this);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  crosapi::CrosapiManager::Get()
+      ->crosapi_ash()
+      ->file_system_provider_service_ash()
+      ->OperationFinishedWithProfile(
+          response, std::move(file_system_id), request_id, std::move(args),
+          std::move(callback), Profile::FromBrowserContext(browser_context()));
+  return true;
+#else
+  if (!OperationFinishedInterfaceAvailable()) {
+    return false;
+  }
+  GetRemote()->OperationFinished(response, std::move(file_system_id),
+                                 request_id, std::move(args),
+                                 std::move(callback));
+  return true;
+#endif
+}
+
 ExtensionFunction::ResponseAction
 FileSystemProviderInternalUnmountRequestedSuccessFunction::Run() {
   using api::file_system_provider_internal::UnmountRequestedSuccess::Params;
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
index 26d26fb..91fe8b8e 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
@@ -5,18 +5,13 @@
 #ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_SYSTEM_PROVIDER_FILE_SYSTEM_PROVIDER_API_H_
 #define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_SYSTEM_PROVIDER_FILE_SYSTEM_PROVIDER_API_H_
 
+#include "base/files/file.h"
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
 #include "extensions/browser/extension_function.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/lacros/lacros_service.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -129,26 +124,16 @@
     crosapi::mojom::FileSystemIdPtr file_system_id;
     int64_t request_id;
     GetOperationMetadata(params, &file_system_id, &request_id);
-    auto callback =
-        base::BindOnce(&FileSystemProviderInternal::RespondWithError, this);
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    crosapi::CrosapiManager::Get()
-        ->crosapi_ash()
-        ->file_system_provider_service_ash()
-        ->OperationFinishedWithProfile(
-            response, std::move(file_system_id), request_id, std::move(args),
-            std::move(callback),
-            Profile::FromBrowserContext(browser_context()));
-    return true;
-#else
-    if (!OperationFinishedInterfaceAvailable())
-      return false;
-    GetRemote()->OperationFinished(response, std::move(file_system_id),
-                                   request_id, std::move(args),
-                                   std::move(callback));
-    return true;
-#endif
+    return ForwardOperationResultImpl(response, std::move(file_system_id),
+                                      request_id, std::move(args));
   }
+
+ private:
+  bool ForwardOperationResultImpl(
+      crosapi::mojom::FSPOperationResponse response,
+      crosapi::mojom::FileSystemIdPtr file_system_id,
+      int request_id,
+      base::Value::List args);
 };
 
 class FileSystemProviderInternalRespondToMountRequestFunction
diff --git a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
index 07898fff..62b9ac5e 100644
--- a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
@@ -60,12 +60,12 @@
 
  protected:
   void CreateAndInstallExtensionWithPermissions(
-      std::unique_ptr<base::ListValue> required_permissions,
-      std::unique_ptr<base::ListValue> optional_permissions) {
+      base::Value::List required_permissions,
+      base::Value::List optional_permissions) {
     app_ = extensions::ExtensionBuilder("Test ChromeOS System Extension")
                .SetManifestVersion(3)
                .SetManifestKey("chromeos_system_extension",
-                               extensions::DictionaryBuilder().Build())
+                               extensions::DictionaryBuilder().BuildDict())
                .SetManifestKey("permissions", std::move(required_permissions))
                .SetManifestKey("optional_permissions",
                                std::move(optional_permissions))
@@ -75,8 +75,8 @@
                        .Set("matches",
                             extensions::ListBuilder()
                                 .Append("*://googlechromelabs.github.io/*")
-                                .Build())
-                       .Build())
+                                .BuildList())
+                       .BuildDict())
                .SetID(kChromeOSSystemExtensionId)  // only allowlisted id
                .SetLocation(ManifestLocation::kInternal)
                .Build();
@@ -136,8 +136,8 @@
 
 TEST_F(ChromeOSPermissionMessageUnittest, OsDiagnosticsMessage) {
   CreateAndInstallExtensionWithPermissions(
-      extensions::ListBuilder().Append("os.diagnostics").Build(),
-      extensions::ListBuilder().Build());
+      extensions::ListBuilder().Append("os.diagnostics").BuildList(),
+      base::Value::List());
 
   ASSERT_EQ(0U, optional_permissions().size());
   ASSERT_EQ(1U, required_permissions().size());
@@ -148,8 +148,8 @@
 
 TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryMessage) {
   CreateAndInstallExtensionWithPermissions(
-      extensions::ListBuilder().Append("os.telemetry").Build(),
-      extensions::ListBuilder().Build());
+      extensions::ListBuilder().Append("os.telemetry").BuildList(),
+      base::Value::List());
 
   ASSERT_EQ(0U, optional_permissions().size());
   ASSERT_EQ(1U, required_permissions().size());
@@ -160,8 +160,9 @@
 
 TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetrySerialNumber) {
   CreateAndInstallExtensionWithPermissions(
-      extensions::ListBuilder().Build(),
-      extensions::ListBuilder().Append("os.telemetry.serial_number").Build());
+      base::Value::List(), extensions::ListBuilder()
+                               .Append("os.telemetry.serial_number")
+                               .BuildList());
 
   ASSERT_EQ(1U, optional_permissions().size());
   EXPECT_EQ(kTelemetrySerialNumberPermissionMessage, optional_permissions()[0]);
@@ -180,8 +181,9 @@
 
 TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryNetworkInformation) {
   CreateAndInstallExtensionWithPermissions(
-      extensions::ListBuilder().Build(),
-      extensions::ListBuilder().Append("os.telemetry.network_info").Build());
+      base::Value::List(), extensions::ListBuilder()
+                               .Append("os.telemetry.network_info")
+                               .BuildList());
 
   ASSERT_EQ(1U, optional_permissions().size());
   EXPECT_EQ(kTelemetryNetworkInformationPermissionMessage,
diff --git a/chrome/browser/download/background_download_service_factory.cc b/chrome/browser/download/background_download_service_factory.cc
index f2158097..57017d9 100644
--- a/chrome/browser/download/background_download_service_factory.cc
+++ b/chrome/browser/download/background_download_service_factory.cc
@@ -52,6 +52,7 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/bruschetta/bruschetta_download_client.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_image_download_client.h"
 #endif
 
@@ -71,6 +72,11 @@
     Profile* profile) {
   return std::make_unique<plugin_vm::PluginVmImageDownloadClient>(profile);
 }
+
+std::unique_ptr<download::Client> CreateBruschettaDownloadClient(
+    Profile* profile) {
+  return std::make_unique<bruschetta::BruschettaDownloadClient>(profile);
+}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 std::unique_ptr<download::Client>
@@ -163,6 +169,11 @@
         download::DownloadClient::PLUGIN_VM_IMAGE,
         std::make_unique<download::DeferredClientWrapper>(
             base::BindOnce(&CreatePluginVmImageDownloadClient), key)));
+
+    clients->insert(std::make_pair(
+        download::DownloadClient::BRUSCHETTA,
+        std::make_unique<download::DeferredClientWrapper>(
+            base::BindOnce(&CreateBruschettaDownloadClient), key)));
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index 3495cb1..ee473ad 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -109,6 +109,7 @@
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "components/infobars/content/content_infobar_manager.h"
 #include "net/http/http_content_disposition.h"
+#include "ui/android/window_android.h"
 #else
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
diff --git a/chrome/browser/enterprise/platform_auth/BUILD.gn b/chrome/browser/enterprise/platform_auth/BUILD.gn
index e0a373352..27b4bab 100644
--- a/chrome/browser/enterprise/platform_auth/BUILD.gn
+++ b/chrome/browser/enterprise/platform_auth/BUILD.gn
@@ -24,6 +24,7 @@
 
   public_deps = [
     "//chrome/browser",
+    "//chrome/browser/enterprise/platform_auth:features",
     "//net",
     "//testing/gmock",
   ]
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.cc b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.cc
new file mode 100644
index 0000000..6efb4672
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.cc
@@ -0,0 +1,45 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/feature_list.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_features.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+PlatformAuthPolicyObserver::PlatformAuthPolicyObserver(
+    PrefService* local_state) {
+  pref_change_registrar_.Init(local_state);
+  pref_change_registrar_.Add(
+      prefs::kCloudApAuthEnabled,
+      base::BindRepeating(&PlatformAuthPolicyObserver::OnPrefChanged,
+                          base::Unretained(this)));
+  // Initialize `PlatformAuthProviderManager` using the current policy value.
+  OnPrefChanged();
+}
+
+// static
+void PlatformAuthPolicyObserver::RegisterPrefs(
+    PrefRegistrySimple* pref_registry) {
+  pref_registry->RegisterIntegerPref(prefs::kCloudApAuthEnabled, 0);
+}
+
+void PlatformAuthPolicyObserver::OnPrefChanged() {
+  // 0 == Disabled
+  // 1 == Enabled
+  const bool enabled =
+      base::FeatureList::IsEnabled(enterprise_auth::kCloudApAuth) &&
+      pref_change_registrar_.prefs()->GetInteger(prefs::kCloudApAuthEnabled) !=
+          0;
+  enterprise_auth::PlatformAuthProviderManager::GetInstance().SetEnabled(
+      enabled, base::OnceClosure());
+}
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h
new file mode 100644
index 0000000..8c1fc552
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_POLICY_OBSERVER_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_POLICY_OBSERVER_H_
+
+#include "components/prefs/pref_change_registrar.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+// Monitors the preference (backed by administrative policy settings) that
+// controls SSO and applies its value to the platform authentication manager.
+class PlatformAuthPolicyObserver {
+ public:
+  explicit PlatformAuthPolicyObserver(PrefService* local_state);
+  PlatformAuthPolicyObserver(const PlatformAuthPolicyObserver&) = delete;
+  PlatformAuthPolicyObserver& operator=(const PlatformAuthPolicyObserver&) =
+      delete;
+
+  static void RegisterPrefs(PrefRegistrySimple* pref_registry);
+
+ private:
+  void OnPrefChanged();
+
+  PrefChangeRegistrar pref_change_registrar_;
+};
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_POLICY_OBSERVER_H_
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer_browsertest.cc b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer_browsertest.cc
new file mode 100644
index 0000000..bd8c016
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_policy_observer_browsertest.cc
@@ -0,0 +1,168 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h"
+
+#include <vector>
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_features.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+class PlatformAuthPolicyObserverTest : public InProcessBrowserTest {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(enterprise_auth::kCloudApAuth);
+    policy_provider_.SetDefaultReturns(
+        /*is_initialization_complete_return=*/true,
+        /*is_first_policy_load_complete_return=*/true);
+    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+        &policy_provider_);
+    InProcessBrowserTest::SetUp();
+  }
+
+ protected:
+  PlatformAuthPolicyObserverTest() = default;
+
+  absl::optional<PlatformAuthPolicyObserver> platform_auth_policy_observer_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
+};
+
+IN_PROC_BROWSER_TEST_F(PlatformAuthPolicyObserverTest, EnableThenDisable) {
+  auto& manager = enterprise_auth::PlatformAuthProviderManager::GetInstance();
+  // The manager should be disabled by default since the policy is disabled.
+  ASSERT_FALSE(manager.IsEnabled());
+
+  // Initialize the policy handler.
+  PrefService* prefs = g_browser_process->local_state();
+  if (prefs)
+    platform_auth_policy_observer_.emplace(prefs);
+
+  EXPECT_EQ(/*Disabled*/ 0, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // Enable the policy.
+  policy::PolicyMap policies;
+  policies.Set(policy::key::kCloudAPAuthEnabled, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+               base::Value(1), nullptr);
+  policy_provider_.UpdateChromePolicy(policies);
+
+  EXPECT_EQ(/*Enabled*/ 1, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_TRUE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // The manager should now be enabled.
+  ASSERT_TRUE(manager.IsEnabled());
+
+  // Disable the policy.
+  policies.Set(policy::key::kCloudAPAuthEnabled, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+               base::Value(0), nullptr);
+  policy_provider_.UpdateChromePolicy(policies);
+
+  EXPECT_EQ(/*Disabled*/ 0, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_TRUE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // The manager should now be disabled.
+  ASSERT_FALSE(manager.IsEnabled());
+
+  platform_auth_policy_observer_.reset();
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAuthPolicyObserverTest, EnableThenUnset) {
+  auto& manager = enterprise_auth::PlatformAuthProviderManager::GetInstance();
+  // The manager should be disabled by default since the policy is disabled.
+  ASSERT_FALSE(manager.IsEnabled());
+
+  // Initialize the policy handler.
+  PrefService* prefs = g_browser_process->local_state();
+  if (prefs)
+    platform_auth_policy_observer_.emplace(prefs);
+
+  EXPECT_EQ(/*Disabled*/ 0, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // Enable the policy.
+  policy::PolicyMap policies;
+  policies.Set(policy::key::kCloudAPAuthEnabled, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+               base::Value(1), nullptr);
+  policy_provider_.UpdateChromePolicy(policies);
+
+  EXPECT_EQ(/*Enabled*/ 1, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_TRUE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // The manager should now be enabled.
+  ASSERT_TRUE(manager.IsEnabled());
+
+  // Unset the policy.
+  policies.Erase(policy::key::kCloudAPAuthEnabled);
+  policy_provider_.UpdateChromePolicy(policies);
+
+  EXPECT_EQ(/*Disabled*/ 0, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // The manager should now be disabled.
+  ASSERT_FALSE(manager.IsEnabled());
+
+  platform_auth_policy_observer_.reset();
+}
+
+class PlatformAuthPolicyObserverFeatureDisabledTest
+    : public PlatformAuthPolicyObserverTest {
+ public:
+  void SetUp() override {
+    policy_provider_.SetDefaultReturns(
+        /*is_initialization_complete_return=*/true,
+        /*is_first_policy_load_complete_return=*/true);
+    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+        &policy_provider_);
+    InProcessBrowserTest::SetUp();
+  }
+
+ protected:
+  PlatformAuthPolicyObserverFeatureDisabledTest() = default;
+};
+
+IN_PROC_BROWSER_TEST_F(PlatformAuthPolicyObserverFeatureDisabledTest,
+                       PolicyEnabled) {
+  auto& manager = enterprise_auth::PlatformAuthProviderManager::GetInstance();
+
+  // Initialize the policy handler.
+  PrefService* prefs = g_browser_process->local_state();
+  if (prefs)
+    platform_auth_policy_observer_.emplace(prefs);
+
+  EXPECT_EQ(/*Disabled*/ 0, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // Enable the policy.
+  policy::PolicyMap policies;
+  policies.Set(policy::key::kCloudAPAuthEnabled, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
+               base::Value(1), nullptr);
+  policy_provider_.UpdateChromePolicy(policies);
+
+  EXPECT_EQ(/*Enabled*/ 1, prefs->GetInteger(prefs::kCloudApAuthEnabled));
+  EXPECT_TRUE(prefs->IsManagedPreference(prefs::kCloudApAuthEnabled));
+
+  // The manager should still be disabled because the feature is disabled.
+  ASSERT_FALSE(manager.IsEnabled());
+
+  platform_auth_policy_observer_.reset();
+}
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 288fc8a..0c1f4cb 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -468,7 +468,7 @@
 
   api::autofill_private::ValidatePhoneParams* params = &parameters->params;
 
-  // Extract the phone numbers into a ListValue.
+  // Extract the phone numbers into a base::Value::List.
   base::Value::List phone_numbers;
   for (auto phone_number : params->phone_numbers) {
     phone_numbers.Append(phone_number);
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
index 724bc799..f6d16d13 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
@@ -47,6 +47,7 @@
 #include "extensions/common/value_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
 #include "chrome/browser/supervised_user/supervised_user_service.h"
@@ -64,11 +65,15 @@
 
 const char kAllHostsPermission[] = "*://*/*";
 
-std::unique_ptr<base::DictionaryValue> DeserializeJSONTestData(
+absl::optional<base::Value::Dict> DeserializeJSONTestData(
     const base::FilePath& path,
     std::string* error) {
   JSONFileValueDeserializer deserializer(path);
-  return base::DictionaryValue::From(deserializer.Deserialize(nullptr, error));
+  std::unique_ptr<base::Value> value = deserializer.Deserialize(nullptr, error);
+  if (!value || !value->is_dict()) {
+    return absl::nullopt;
+  }
+  return std::move(*value).TakeDict();
 }
 
 // Returns a pointer to the ExtensionInfo for an extension with |id| if it
@@ -210,8 +215,9 @@
       InspectableViewsFinder::ViewList views,
       const base::FilePath& expected_output_path) {
     std::string error;
-    std::unique_ptr<base::Value> expected_output_data(
-        DeserializeJSONTestData(expected_output_path, &error));
+    absl::optional<base::Value::Dict> expected_output_data =
+        DeserializeJSONTestData(expected_output_path, &error);
+    ASSERT_TRUE(expected_output_data);
     EXPECT_EQ(std::string(), error);
 
     // Produce test output.
@@ -228,7 +234,7 @@
         extension_path.MaybeAsASCII() + ")";
     std::string expected_string;
     std::string actual_string;
-    for (auto field : expected_output_data->DictItems()) {
+    for (auto field : *expected_output_data) {
       const base::Value& expected_value = field.second;
       base::Value* actual_value =
           actual_output_data.FindByDottedPath(field.first);
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
index 4a16890..0f64c3b 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
@@ -30,6 +30,7 @@
 #include "extensions/common/extension_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using testing::Invoke;
 using testing::NiceMock;
@@ -128,9 +129,9 @@
   }
 
   // Like extension_function_test_utils::RunFunctionAndReturnError but with an
-  // explicit ListValue.
+  // explicit list of args.
   std::string RunFunctionAndReturnError(ExtensionFunction* function,
-                                        std::unique_ptr<base::ListValue> args,
+                                        base::Value::List args,
                                         Browser* browser) {
     utils::RunFunction(function, std::move(args), browser,
                        extensions::api_test_utils::NONE);
@@ -139,11 +140,10 @@
   }
 
   // Like extension_function_test_utils::RunFunctionAndReturnSingleResult but
-  // with an explicit ListValue.
-  base::Value RunFunctionAndReturnSingleResult(
-      ExtensionFunction* function,
-      std::unique_ptr<base::ListValue> args,
-      Browser* browser) {
+  // with an explicit list of args.
+  base::Value RunFunctionAndReturnSingleResult(ExtensionFunction* function,
+                                               base::Value::List args,
+                                               Browser* browser) {
     scoped_refptr<ExtensionFunction> function_owner(function);
     // Without a callback the function will not generate a result.
     function->set_has_callback(true);
@@ -176,35 +176,31 @@
     func_->set_extension(extension_.get());
   }
 
-  std::unique_ptr<base::ListValue> CreateArgs() {
-    return CreateArgsInternal(base::Value());
-  }
+  base::Value::List CreateArgs() { return CreateArgsInternal(absl::nullopt); }
 
-  std::unique_ptr<base::ListValue> CreateArgsNoRegister() {
+  base::Value::List CreateArgsNoRegister() {
     return CreateArgsInternal(base::Value(false));
   }
 
-  std::unique_ptr<base::ListValue> CreateArgsRegister() {
+  base::Value::List CreateArgsRegister() {
     return CreateArgsInternal(base::Value(true));
   }
 
-  std::unique_ptr<base::ListValue> CreateArgsInternal(
-      base::Value register_key) {
+  base::Value::List CreateArgsInternal(
+      absl::optional<base::Value> register_key) {
     static constexpr base::StringPiece kData = "challenge";
-    base::Value args(base::Value::Type::LIST);
+    base::Value::List args;
     args.Append(base::Value(base::as_bytes(base::make_span(kData))));
-    if (register_key.is_bool())
-      args.Append(std::move(register_key));
-    return base::ListValue::From(
-        base::Value::ToUniquePtrValue(std::move(args)));
+    if (register_key)
+      args.Append(std::move(*register_key));
+    return args;
   }
 
   scoped_refptr<EnterprisePlatformKeysChallengeMachineKeyFunction> func_;
-  base::ListValue args_;
 };
 
 TEST_F(EPKChallengeMachineKeyTest, ExtensionNotAllowed) {
-  base::ListValue empty_allowlist;
+  base::Value empty_allowlist(base::Value::Type::LIST);
   prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
 
   EXPECT_EQ(
@@ -216,7 +212,7 @@
   SetMockTpmChallenger();
 
   base::Value allowlist(base::Value::Type::LIST);
-  allowlist.Append(extension_->id());
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   base::Value value(
@@ -231,7 +227,7 @@
   SetMockTpmChallengerBadBase64Error();
 
   base::Value allowlist(base::Value::Type::LIST);
-  allowlist.Append(extension_->id());
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   base::Value value(
@@ -245,8 +241,8 @@
 TEST_F(EPKChallengeMachineKeyTest, KeyNotRegisteredByDefault) {
   SetMockTpmChallenger();
 
-  base::ListValue allowlist;
-  allowlist.Append(extension_->id());
+  base::Value allowlist(base::Value::Type::LIST);
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   EXPECT_CALL(*mock_tpm_challenge_key_, BuildResponse)
@@ -271,21 +267,16 @@
     prefs_->SetBoolean(prefs::kAttestationEnabled, true);
   }
 
-  std::unique_ptr<base::ListValue> CreateArgs() {
-    return CreateArgsInternal(true);
-  }
+  base::Value::List CreateArgs() { return CreateArgsInternal(true); }
 
-  std::unique_ptr<base::ListValue> CreateArgsNoRegister() {
-    return CreateArgsInternal(false);
-  }
+  base::Value::List CreateArgsNoRegister() { return CreateArgsInternal(false); }
 
-  std::unique_ptr<base::ListValue> CreateArgsInternal(bool register_key) {
+  base::Value::List CreateArgsInternal(bool register_key) {
     static constexpr base::StringPiece kData = "challenge";
-    base::Value args(base::Value::Type::LIST);
+    base::Value::List args;
     args.Append(base::Value(base::as_bytes(base::make_span(kData))));
     args.Append(register_key);
-    return base::ListValue::From(
-        base::Value::ToUniquePtrValue(std::move(args)));
+    return args;
   }
 
   EPKPChallengeKey impl_;
@@ -296,7 +287,7 @@
   SetMockTpmChallenger();
 
   base::Value allowlist(base::Value::Type::LIST);
-  allowlist.Append(extension_->id());
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   base::Value value(
@@ -311,7 +302,7 @@
   SetMockTpmChallengerBadBase64Error();
 
   base::Value allowlist(base::Value::Type::LIST);
-  allowlist.Append(extension_->id());
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   base::Value value(
@@ -323,7 +314,7 @@
 }
 
 TEST_F(EPKChallengeUserKeyTest, ExtensionNotAllowedThenErrorMessageReturned) {
-  base::ListValue empty_allowlist;
+  base::Value empty_allowlist(base::Value::Type::LIST);
   prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
 
   EXPECT_EQ(
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
index d477ae8..118ed8b 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
@@ -92,7 +92,7 @@
 const char EPKPChallengeMachineKeyTest::kFuncArgs[] = "[\"Y2hhbGxlbmdl\"]";
 
 TEST_F(EPKPChallengeMachineKeyTest, ExtensionNotAllowlisted) {
-  base::ListValue empty_allowlist;
+  base::Value empty_allowlist(base::Value::Type::LIST);
   prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
 
   EXPECT_EQ(
@@ -103,8 +103,8 @@
 TEST_F(EPKPChallengeMachineKeyTest, Success) {
   SetMockTpmChallenger();
 
-  base::ListValue allowlist;
-  allowlist.Append(extension_->id());
+  base::Value allowlist(base::Value::Type::LIST);
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
@@ -131,7 +131,7 @@
 const char EPKPChallengeUserKeyTest::kFuncArgs[] = "[\"Y2hhbGxlbmdl\", true]";
 
 TEST_F(EPKPChallengeUserKeyTest, ExtensionNotAllowlisted) {
-  base::ListValue empty_allowlist;
+  base::Value empty_allowlist(base::Value::Type::LIST);
   prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
 
   EXPECT_EQ(
@@ -142,8 +142,8 @@
 TEST_F(EPKPChallengeUserKeyTest, Success) {
   SetMockTpmChallenger();
 
-  base::ListValue allowlist;
-  allowlist.Append(extension_->id());
+  base::Value allowlist(base::Value::Type::LIST);
+  allowlist.GetList().Append(extension_->id());
   prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
 
   std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
diff --git a/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc b/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
index ae79886a..cdacd79 100644
--- a/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_get_accounts_function.cc
@@ -34,7 +34,7 @@
       IdentityManagerFactory::GetForProfile(
           Profile::FromBrowserContext(browser_context()))
           ->GetAccountsWithRefreshTokens();
-  base::ListValue infos;
+  base::Value::List infos;
 
   if (accounts.empty()) {
     return RespondNow(WithArguments(std::move(infos)));
diff --git a/chrome/browser/extensions/api/image_writer_private/extractor_browsertest.cc b/chrome/browser/extensions/api/image_writer_private/extractor_browsertest.cc
index 17464b7..653e842 100644
--- a/chrome/browser/extensions/api/image_writer_private/extractor_browsertest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/extractor_browsertest.cc
@@ -109,10 +109,10 @@
   run_loop.Run();
 }
 
-// Verify that tar.xz with a 0 byte file works.
 IN_PROC_BROWSER_TEST_F(ExtractorBrowserTest, ZeroByteTarXzFile) {
   base::ScopedAllowBlockingForTesting allow_blocking;
 
+  // Use a tar.xz containing an empty file.
   base::FilePath test_data_dir;
   ASSERT_TRUE(GetTestDataDirectory(&test_data_dir));
   properties_.image_path = test_data_dir.AppendASCII("empty_file.tar.xz");
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
index f092e5a..e71d8a3 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
@@ -424,14 +424,12 @@
   std::vector<std::string> languages =
       translate_prefs->GetAlwaysTranslateLanguages();
 
-  std::unique_ptr<base::ListValue> always_translate_languages_ =
-      (std::make_unique<base::ListValue>());
+  base::Value::List always_translate_languages;
   for (const auto& entry : languages) {
-    always_translate_languages_->Append(entry);
+    always_translate_languages.Append(entry);
   }
 
-  return RespondNow(WithArguments(
-      base::Value::FromUniquePtrValue(std::move(always_translate_languages_))));
+  return RespondNow(WithArguments(std::move(always_translate_languages)));
 }
 
 LanguageSettingsPrivateSetLanguageAlwaysTranslateStateFunction::
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_unittest.cc b/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_unittest.cc
index da1d723..605a647 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_unittest.cc
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_unittest.cc
@@ -54,10 +54,10 @@
     feature_list_.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker);
 #endif  // BUILDFLAG(IS_WIN)
 
-    base::ListValue language_codes;
+    base::Value::List language_codes;
     language_codes.Append("fr");
     profile()->GetPrefs()->Set(spellcheck::prefs::kSpellCheckDictionaries,
-                               language_codes);
+                               base::Value(std::move(language_codes)));
 
     SpellcheckServiceFactory::GetInstance()->SetTestingFactory(
         profile(), base::BindRepeating(&BuildSpellcheckService));
diff --git a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
index 07aed89..7c9ee31 100644
--- a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
@@ -63,10 +63,7 @@
 namespace web_request = extensions::api::web_request;
 namespace dnr_api = extensions::api::declarative_net_request;
 
-using base::DictionaryValue;
-using base::ListValue;
 using base::Time;
-using base::Value;
 using helpers::CalculateOnAuthRequiredDelta;
 using helpers::CalculateOnBeforeRequestDelta;
 using helpers::CalculateOnBeforeSendHeadersDelta;
@@ -130,13 +127,14 @@
 bool GenerateInfoSpec(content::BrowserContext* browser_context,
                       const std::string& values,
                       int* result) {
-  // Create a base::ListValue of strings.
-  base::ListValue list_value;
-  for (const std::string& cur :
-       base::SplitString(values, ",", base::KEEP_WHITESPACE,
-                         base::SPLIT_WANT_NONEMPTY))
-    list_value.Append(cur);
-  return ExtraInfoSpec::InitFromValue(browser_context, list_value, result);
+  // Create a base::Value::List of strings.
+  base::Value::List list;
+  for (const std::string& cur : base::SplitString(
+           values, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
+    list.Append(cur);
+  }
+  return ExtraInfoSpec::InitFromValue(browser_context,
+                                      base::Value(std::move(list)), result);
 }
 
 }  // namespace
diff --git a/chrome/browser/extensions/permission_messages_unittest.cc b/chrome/browser/extensions/permission_messages_unittest.cc
index 61fb923..293beb1 100644
--- a/chrome/browser/extensions/permission_messages_unittest.cc
+++ b/chrome/browser/extensions/permission_messages_unittest.cc
@@ -60,8 +60,8 @@
 
  protected:
   void CreateAndInstallExtensionWithPermissions(
-      std::unique_ptr<base::ListValue> required_permissions,
-      std::unique_ptr<base::ListValue> optional_permissions) {
+      base::Value::List required_permissions,
+      base::Value::List optional_permissions) {
     app_ = ExtensionBuilder("Test")
                .SetManifestKey("permissions", std::move(required_permissions))
                .SetManifestKey("optional_permissions",
@@ -124,8 +124,8 @@
 // other (the 'history' permission has superset permissions).
 TEST_F(PermissionMessagesUnittest, HistoryHidesTabsMessage) {
   CreateAndInstallExtensionWithPermissions(
-      ListBuilder().Append("tabs").Append("history").Build(),
-      ListBuilder().Build());
+      ListBuilder().Append("tabs").Append("history").BuildList(),
+      base::Value::List());
 
   ASSERT_EQ(1U, required_permissions().size());
   EXPECT_EQ(
@@ -139,8 +139,8 @@
 // permission, only the new coalesced message is displayed.
 TEST_F(PermissionMessagesUnittest, MixedPermissionMessagesCoalesceOnceGranted) {
   CreateAndInstallExtensionWithPermissions(
-      ListBuilder().Append("tabs").Build(),
-      ListBuilder().Append("history").Build());
+      ListBuilder().Append("tabs").BuildList(),
+      ListBuilder().Append("history").BuildList());
 
   ASSERT_EQ(1U, required_permissions().size());
   EXPECT_EQ(
@@ -178,8 +178,8 @@
 TEST_F(PermissionMessagesUnittest,
        AntiTest_PromptCanRequestSubsetOfAlreadyGrantedPermissions) {
   CreateAndInstallExtensionWithPermissions(
-      ListBuilder().Append("history").Build(),
-      ListBuilder().Append("tabs").Build());
+      ListBuilder().Append("history").BuildList(),
+      ListBuilder().Append("tabs").BuildList());
 
   ASSERT_EQ(1U, required_permissions().size());
   EXPECT_EQ(
@@ -219,8 +219,8 @@
 TEST_F(PermissionMessagesUnittest,
        AntiTest_PromptCanBeEmptyButCausesChangeInPermissions) {
   CreateAndInstallExtensionWithPermissions(
-      ListBuilder().Append("tabs").Build(),
-      ListBuilder().Append("sessions").Build());
+      ListBuilder().Append("tabs").BuildList(),
+      ListBuilder().Append("sessions").BuildList());
 
   ASSERT_EQ(1U, required_permissions().size());
   EXPECT_EQ(
diff --git a/chrome/browser/extensions/site_permissions_helper_unittest.cc b/chrome/browser/extensions/site_permissions_helper_unittest.cc
index f184bf9..3ec0e1d 100644
--- a/chrome/browser/extensions/site_permissions_helper_unittest.cc
+++ b/chrome/browser/extensions/site_permissions_helper_unittest.cc
@@ -26,12 +26,11 @@
 
 namespace {
 
-std::unique_ptr<base::ListValue> ToListValue(
-    const std::vector<std::string>& permissions) {
-  extensions::ListBuilder builder;
+base::Value::List ToValueList(const std::vector<std::string>& permissions) {
+  base::Value::List list;
   for (const std::string& permission : permissions)
-    builder.Append(permission);
-  return builder.Build();
+    list.Append(permission);
+  return list;
 }
 
 }  // namespace
@@ -85,7 +84,7 @@
   auto extension =
       extensions::ExtensionBuilder(name)
           .SetManifestVersion(3)
-          .SetManifestKey("host_permissions", ToListValue(host_permissions))
+          .SetManifestKey("host_permissions", ToValueList(host_permissions))
           .AddPermissions(permissions)
           .SetID(crx_file::id_util::GenerateId(name))
           .Build();
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 03c2898..c51d692 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2682,12 +2682,12 @@
   {
     "name": "enable-migrate-default-chrome-app-to-web-apps-gsuite",
     "owners": [ "alancutter", "desktop-pwas-team@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 113
   },
   {
     "name": "enable-migrate-default-chrome-app-to-web-apps-non-gsuite",
     "owners": [ "alancutter", "desktop-pwas-team@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 113
   },
   {
     "name": "enable-nacl",
@@ -5091,6 +5091,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "omnibox-keep-secondary-zero-suggest",
+    "owners": [ "ender@google.com", "mahmadi@google.com", "chrome-desktop-search@google.com" ],
+    "expiry_milestone": 115
+  },
+  {
     "name": "omnibox-keyboard-paste-button",
     "owners": ["christianxu", "stkhapugin", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 110
@@ -5221,11 +5226,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "omnibox-retain-suggestions-with-headers",
-    "owners": [ "ender@google.com", "mahmadi@google.com", "chrome-omnibox-team@google.com" ],
-    "expiry_milestone": 112
-  },
-  {
     "name": "omnibox-rich-autocompletion",
     "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 115
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 70e6a7e..8bfc18d 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1965,6 +1965,12 @@
     kOmniboxHistoryQuickProviderSpecificityScoreCountUniqueHostsDescription[] =
         "When enabled, HQP doesn't demote same-host suggestions.";
 
+const char kOmniboxKeepSecondaryZeroSuggestName[] =
+    "Keeps all secondary zero-prefix suggestions.";
+const char kOmniboxKeepSecondaryZeroSuggestDescription[] =
+    "Keeps all zero-prefix suggestions in the second column and does not count "
+    "them toward the overall zero-suggest limit.";
+
 const char kOmniboxMatchToolbarAndStatusBarColorName[] =
     "Omnibox Omnibox Match Toolbar And Status Bar Color";
 const char kOmniboxMatchToolbarAndStatusBarColorDescription[] =
@@ -2127,13 +2133,6 @@
     "Expand the last word in the shortcut text to be a complete word from the "
     "suggestion text.";
 
-const char kOmniboxRetainSuggestionsWithHeadersName[] =
-    "Retain complete set of suggestions with headers";
-const char kOmniboxRetainSuggestionsWithHeadersDescription[] =
-    "Given a list of suggestions, all suggestions for which a header metadata "
-    "is available will be retained as a whole and not be counted towards the "
-    "limit.";
-
 const char kOmniboxMaxZeroSuggestMatchesName[] =
     "Omnibox Max Zero Suggest Matches";
 const char kOmniboxMaxZeroSuggestMatchesDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index e61caaa4..c7b06074 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1116,6 +1116,9 @@
 extern const char
     kOmniboxHistoryQuickProviderSpecificityScoreCountUniqueHostsDescription[];
 
+extern const char kOmniboxKeepSecondaryZeroSuggestName[];
+extern const char kOmniboxKeepSecondaryZeroSuggestDescription[];
+
 extern const char kOmniboxMlLogUrlScoringSignalsName[];
 extern const char kOmniboxMlLogUrlScoringSignalsDescription[];
 
@@ -1182,9 +1185,6 @@
 extern const char kOmniboxSpareRendererName[];
 extern const char kOmniboxSpareRendererDescription[];
 
-extern const char kOmniboxRetainSuggestionsWithHeadersName[];
-extern const char kOmniboxRetainSuggestionsWithHeadersDescription[];
-
 extern const char kOmniboxZeroSuggestPrefetchingName[];
 extern const char kOmniboxZeroSuggestPrefetchingDescription[];
 
diff --git a/chrome/browser/media/cast_mirroring_service_host.cc b/chrome/browser/media/cast_mirroring_service_host.cc
index ed12c84..f60d6087 100644
--- a/chrome/browser/media/cast_mirroring_service_host.cc
+++ b/chrome/browser/media/cast_mirroring_service_host.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/ui/tab_sharing/tab_sharing_ui.h"
+#include "components/access_code_cast/common/access_code_cast_metrics.h"
 #include "components/mirroring/browser/single_client_video_capture_host.h"
 #include "components/mirroring/mojom/cast_message_channel.mojom.h"
 #include "components/mirroring/mojom/mirroring_service.mojom.h"
@@ -201,7 +202,9 @@
     Observe(GetContents(source_media_id_.web_contents_id));
 }
 
-CastMirroringServiceHost::~CastMirroringServiceHost() {}
+CastMirroringServiceHost::~CastMirroringServiceHost() {
+  RecordTabUIUsageMetricsIfNeededAndReset();
+}
 
 void CastMirroringServiceHost::Start(
     mojom::SessionParametersPtr session_params,
@@ -473,6 +476,7 @@
   web_contents_audio_muter_.reset();
   audio_stream_factory_.reset();
   gpu_client_.reset();
+  RecordTabUIUsageMetricsIfNeededAndReset();
 }
 
 void CastMirroringServiceHost::ShowCaptureIndicator() {
@@ -539,6 +543,9 @@
                           weak_factory_for_ui_.GetWeakPtr()),
       /*label=*/std::string(), /*screen_capture_ids=*/{},
       content::MediaStreamUI::StateChangeCallback());
+
+  if (!tab_switching_count_)
+    tab_switching_count_ = 0;
 }
 
 void CastMirroringServiceHost::SwitchMirroringSourceTab(
@@ -554,6 +561,26 @@
   web_contents_audio_muter_.reset();
 
   mirroring_service_->SwitchMirroringSourceTab();
+  tab_switching_count_.value() += 1;
+}
+
+void CastMirroringServiceHost::RecordTabUIUsageMetricsIfNeededAndReset() {
+  if (!tab_switching_count_)
+    return;
+
+  if (tab_switching_count_.value() > 0) {
+    AccessCodeCastMetrics::RecordTabSwitcherUsageCase(
+        AccessCodeCastUiTabSwitcherUsage::
+            kTabSwitcherUiShownAndUsedToSwitchTabs);
+  } else {
+    AccessCodeCastMetrics::RecordTabSwitcherUsageCase(
+        AccessCodeCastUiTabSwitcherUsage::kTabSwitcherUiShownAndNotUsed);
+  }
+
+  AccessCodeCastMetrics::RecordTabSwitchesCountInTabSession(
+      tab_switching_count_.value());
+
+  tab_switching_count_.reset();
 }
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/media/cast_mirroring_service_host.h b/chrome/browser/media/cast_mirroring_service_host.h
index b538273..bdeb5c4 100644
--- a/chrome/browser/media/cast_mirroring_service_host.h
+++ b/chrome/browser/media/cast_mirroring_service_host.h
@@ -128,6 +128,10 @@
 
   void SwitchMirroringSourceTab(const content::DesktopMediaID& media_id);
 
+  // Records metrics about the usage of Tab Switcher UI, and resets data members
+  // used for metrics collection.
+  void RecordTabUIUsageMetricsIfNeededAndReset();
+
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   // OffscreenTab::Owner implementation.
   void DestroyTab(OffscreenTab* tab) override;
@@ -170,6 +174,11 @@
 
   const bool tab_switching_ui_enabled_;
 
+  // Represents the number of times a tab was switched during an active
+  // mirroring session using tab switcher UI bar. Mainly used for metrics
+  // collecting.
+  absl::optional<int> tab_switching_count_;
+
   // Used for calls supplied to `media_stream_ui_`, mainly to handle callbacks
   // for TabSharingUIViews. Invalidated every time a new UI is created.
   base::WeakPtrFactory<CastMirroringServiceHost> weak_factory_for_ui_{this};
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_feature.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_feature.cc
index 733a535..29fdb0bc 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_feature.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_feature.cc
@@ -27,6 +27,7 @@
 
 // Provide a tab switching UI bar while casting (mirroring) when AccessCodeCast
 // is enabled.
+// To be enabled by Default.
 BASE_FEATURE(kAccessCodeCastTabSwitchingUI,
              "AccessCodeCastTabSwitchingUI",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_browsertest.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_browsertest.cc
index 7e913f2..be48e44 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_browsertest.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_browsertest.cc
@@ -46,7 +46,7 @@
     : public AccessCodeCastIntegrationBrowserTest {};
 
 // TODO(b/242928209): Saved device tests are flaky on linux-rel/Mac/ChromeOS.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_LINUX)
 #define MAYBE_PRE_InstantExpiration DISABLED_PRE_InstantExpiration
 #define MAYBE_InstantExpiration DISABLED_InstantExpiration
 #else
@@ -93,10 +93,14 @@
   UpdateRoutes({media_route_cast});
   base::RunLoop().RunUntilIdle();
 
-  // Recorded once from the route created when pressing submit, and then again
-  // when we manually call `UpdateRoutes`.
+// Recorded once from the route created when pressing submit, and then again
+// when we manually call `UpdateRoutes`.
+// TODO(b/262287112): AccessCodeCast.Discovery.DeviceDurationOnRoute is
+// recorded twice for saved devices browser tests on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
   histogram_tester.ExpectTotalCount(
-      "AccessCodeCast.Discovery.DeviceDurationOnRoute", 2);
+      "AccessCodeCast.Discovery.DeviceDurationOnRoute", 1);
+#endif
 
   EXPECT_CALL(*mock_cast_media_sink_service_impl(), DisconnectAndRemoveSink(_));
   UpdateRoutes({});
@@ -121,7 +125,6 @@
   if (base::win::GetVersion() >= base::win::Version::WIN10)
     GTEST_SKIP() << "This test is flaky on win10";
 #endif
-
   // This test is run after an instant expiration device was successfully
   // added to the browser. Upon restart it should not exists in prefs nor should
   // it be added to the media router.
@@ -141,7 +144,7 @@
 }
 
 // TODO(b/242928209): Saved device tests are flaky on linux-rel/Mac
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_LINUX)
 #define MAYBE_PRE_SavedDevice DISABLED_PRE_SavedDevice
 #define MAYBE_SavedDevice DISABLED_SavedDevice
 #else
diff --git a/chrome/browser/navigation_predictor/anchor_element_preloader.cc b/chrome/browser/navigation_predictor/anchor_element_preloader.cc
index 63f33fd..4aaddcb 100644
--- a/chrome/browser/navigation_predictor/anchor_element_preloader.cc
+++ b/chrome/browser/navigation_predictor/anchor_element_preloader.cc
@@ -57,11 +57,13 @@
       ToPreloadingPredictor(ChromePreloadingPredictor::kPointerDownOnAnchor),
       content::PreloadingType::kPreconnect, match_callback);
 
-  if (!prefetch::IsSomePreloadingEnabled(
-          *Profile::FromBrowserContext(render_frame_host_->GetBrowserContext())
-               ->GetPrefs())) {
-    attempt->SetEligibility(
-        content::PreloadingEligibility::kPreloadingDisabled);
+  if (content::PreloadingEligibility eligibility =
+          prefetch::IsSomePreloadingEnabled(
+              *Profile::FromBrowserContext(
+                   render_frame_host_->GetBrowserContext())
+                   ->GetPrefs());
+      eligibility != content::PreloadingEligibility::kEligible) {
+    attempt->SetEligibility(eligibility);
     return;
   }
 
diff --git a/chrome/browser/offline_pages/offliner_helper.cc b/chrome/browser/offline_pages/offliner_helper.cc
index 1bb89f6..5a515f6c 100644
--- a/chrome/browser/offline_pages/offliner_helper.cc
+++ b/chrome/browser/offline_pages/offliner_helper.cc
@@ -22,8 +22,9 @@
 
 bool IsNetworkPredictionDisabled(content::BrowserContext* browser_context) {
   DCHECK(Profile::FromBrowserContext(browser_context)->GetPrefs());
-  return !prefetch::IsSomePreloadingEnabled(
-      *Profile::FromBrowserContext(browser_context)->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(
+             *Profile::FromBrowserContext(browser_context)->GetPrefs()) !=
+         content::PreloadingEligibility::kEligible;
 }
 
 }  // namespace offline_pages
diff --git a/chrome/browser/optimization_guide/browser_test_util.cc b/chrome/browser/optimization_guide/browser_test_util.cc
index 2fbba18..0363081 100644
--- a/chrome/browser/optimization_guide/browser_test_util.cc
+++ b/chrome/browser/optimization_guide/browser_test_util.cc
@@ -48,4 +48,39 @@
   }
 }
 
+std::unique_ptr<optimization_guide::proto::GetModelsResponse>
+BuildGetModelsResponse() {
+  std::unique_ptr<optimization_guide::proto::GetModelsResponse>
+      get_models_response =
+          std::make_unique<optimization_guide::proto::GetModelsResponse>();
+
+  optimization_guide::proto::PredictionModel* prediction_model =
+      get_models_response->add_models();
+  optimization_guide::proto::ModelInfo* model_info =
+      prediction_model->mutable_model_info();
+  model_info->set_version(2);
+  model_info->set_optimization_target(
+      optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  model_info->add_supported_model_engine_versions(
+      optimization_guide::proto::ModelEngineVersion::
+          MODEL_ENGINE_VERSION_TFLITE_2_8);
+  prediction_model->mutable_model()->set_download_url(
+      "https://example.com/model");
+
+  return get_models_response;
+}
+
+ModelFileObserver::ModelFileObserver() = default;
+
+ModelFileObserver::~ModelFileObserver() = default;
+
+void ModelFileObserver::OnModelUpdated(
+    proto::OptimizationTarget optimization_target,
+    const ModelInfo& model_info) {
+  optimization_target_ = optimization_target;
+  model_info_ = model_info;
+  if (file_received_callback_)
+    std::move(file_received_callback_).Run(optimization_target, model_info);
+}
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/browser_test_util.h b/chrome/browser/optimization_guide/browser_test_util.h
index 23d8a0d..18d91c4 100644
--- a/chrome/browser/optimization_guide/browser_test_util.h
+++ b/chrome/browser/optimization_guide/browser_test_util.h
@@ -7,6 +7,10 @@
 
 #include <string>
 
+#include "base/functional/callback.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "components/optimization_guide/proto/models.pb.h"
+
 namespace base {
 class HistogramTester;
 }  // namespace base
@@ -20,6 +24,43 @@
     const std::string& histogram_name,
     int count);
 
+// Builds a test models response.
+std::unique_ptr<optimization_guide::proto::GetModelsResponse>
+BuildGetModelsResponse();
+
+// Helper to receive modelinfo updates.
+class ModelFileObserver : public OptimizationTargetModelObserver {
+ public:
+  using ModelFileReceivedCallback =
+      base::OnceCallback<void(proto::OptimizationTarget, const ModelInfo&)>;
+
+  ModelFileObserver();
+  ~ModelFileObserver() override;
+
+  void set_model_file_received_callback(ModelFileReceivedCallback callback) {
+    file_received_callback_ = std::move(callback);
+  }
+
+  absl::optional<proto::OptimizationTarget> optimization_target() const {
+    return optimization_target_;
+  }
+
+  absl::optional<ModelInfo> model_info() { return model_info_; }
+
+  // OptimizationTargetModelObserver implementation:
+  void OnModelUpdated(proto::OptimizationTarget optimization_target,
+                      const ModelInfo& model_info) override;
+
+ private:
+  ModelFileReceivedCallback file_received_callback_;
+
+  // Holds the optimization target that was received from modelinfo updates.
+  absl::optional<proto::OptimizationTarget> optimization_target_;
+
+  // Holds the modelinfo that was received from modelinfo updates.
+  absl::optional<ModelInfo> model_info_;
+};
+
 }  // namespace optimization_guide
 
 #endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_BROWSER_TEST_UTIL_H_
diff --git a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc
new file mode 100644
index 0000000..17cb5ca7
--- /dev/null
+++ b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.cc
@@ -0,0 +1,57 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/prediction_manager.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
+
+namespace {
+
+// Prefix for the model store directory.
+const base::FilePath::CharType kOptimizationGuideModelStoreDirPrefix[] =
+    FILE_PATH_LITERAL("optimization_guide_model_store");
+
+}  // namespace
+
+void ChromeBrowserMainExtraPartsOptimizationGuide::PreCreateThreads() {
+  if (!optimization_guide::features::IsInstallWideModelStoreEnabled())
+    return;
+
+  base::FilePath model_downloads_dir;
+  base::PathService::Get(chrome::DIR_USER_DATA, &model_downloads_dir);
+  model_downloads_dir =
+      model_downloads_dir.Append(kOptimizationGuideModelStoreDirPrefix);
+  // Create and initialize the install-wide model store.
+  optimization_guide::PredictionModelStore::GetInstance()->Initialize(
+      g_browser_process->local_state(), model_downloads_dir);
+}
+
+void ChromeBrowserMainExtraPartsOptimizationGuide::PostProfileInit(
+    Profile* profile,
+    bool is_initial_profile) {
+  if (!optimization_guide::features::IsInstallWideModelStoreEnabled())
+    return;
+
+  if (profile->IsOffTheRecord())
+    return;
+
+  auto* optimization_guide_keyed_service =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
+  if (!optimization_guide_keyed_service)
+    return;
+
+  optimization_guide_keyed_service->GetPredictionManager()
+      ->MaybeInitializeModelDownloads(
+          optimization_guide_keyed_service
+              ->BackgroundDownloadServiceProvider());
+}
diff --git a/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h
new file mode 100644
index 0000000..e93db7c
--- /dev/null
+++ b/chrome/browser/optimization_guide/chrome_browser_main_extra_parts_optimization_guide.h
@@ -0,0 +1,24 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
+#define CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
+
+#include "chrome/browser/chrome_browser_main_extra_parts.h"
+
+class Profile;
+
+// This class is used to initialize the optimization guide model store which is
+// install-wide, as part of Chrome browser process startup.
+class ChromeBrowserMainExtraPartsOptimizationGuide
+    : public ChromeBrowserMainExtraParts {
+ public:
+  ChromeBrowserMainExtraPartsOptimizationGuide() = default;
+
+  // ChromeBrowserMainExtraParts implementation:
+  void PreCreateThreads() override;
+  void PostProfileInit(Profile* profile, bool is_initial_profile) override;
+};
+
+#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_CHROME_BROWSER_MAIN_EXTRA_PARTS_OPTIMIZATION_GUIDE_H_
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index 68e4ac3..70bf6ac 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -36,6 +36,7 @@
 #include "components/optimization_guide/core/optimization_guide_store.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/prediction_manager.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
 #include "components/optimization_guide/core/tab_url_provider.h"
 #include "components/optimization_guide/core/top_host_provider.h"
 #include "components/optimization_guide/proto/models.pb.h"
@@ -209,16 +210,19 @@
             : nullptr;
     hint_store = hint_store_ ? hint_store_->AsWeakPtr() : nullptr;
 
-    prediction_model_and_features_store_ = std::make_unique<
-        optimization_guide::OptimizationGuideStore>(
-        proto_db_provider,
-        profile_path.Append(
-            optimization_guide::kOptimizationGuidePredictionModelMetadataStore),
-        base::ThreadPool::CreateSequencedTaskRunner(
-            {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
-        profile->GetPrefs());
-    prediction_model_and_features_store =
-        prediction_model_and_features_store_->AsWeakPtr();
+    if (!optimization_guide::features::IsInstallWideModelStoreEnabled()) {
+      prediction_model_and_features_store_ =
+          std::make_unique<optimization_guide::OptimizationGuideStore>(
+              proto_db_provider,
+              profile_path.Append(
+                  optimization_guide::
+                      kOptimizationGuidePredictionModelMetadataStore),
+              base::ThreadPool::CreateSequencedTaskRunner(
+                  {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
+              profile->GetPrefs());
+      prediction_model_and_features_store =
+          prediction_model_and_features_store_->AsWeakPtr();
+    }
   }
 
   optimization_guide_logger_ = std::make_unique<OptimizationGuideLogger>();
@@ -228,7 +232,8 @@
       MaybeCreatePushNotificationManager(profile),
       optimization_guide_logger_.get());
   base::FilePath model_downloads_dir;
-  if (!profile->IsOffTheRecord()) {
+  if (!optimization_guide::features::IsInstallWideModelStoreEnabled() &&
+      !profile->IsOffTheRecord()) {
     // Do not explicitly hand off the model downloads directory to
     // off-the-record profiles. Underneath the hood, this variable is only used
     // in non off-the-record profiles to know where to download the model files
@@ -239,8 +244,11 @@
   }
 
   prediction_manager_ = std::make_unique<optimization_guide::PredictionManager>(
-      prediction_model_and_features_store, url_loader_factory,
-      profile->GetPrefs(), profile->IsOffTheRecord(),
+      prediction_model_and_features_store,
+      optimization_guide::features::IsInstallWideModelStoreEnabled()
+          ? optimization_guide::PredictionModelStore::GetInstance()
+          : nullptr,
+      url_loader_factory, profile->GetPrefs(), profile->IsOffTheRecord(),
       g_browser_process->GetApplicationLocale(), model_downloads_dir,
       optimization_guide_logger_.get(),
       base::BindOnce(
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index ef4cdf3f..4331c1e 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -35,16 +35,18 @@
 class OptimizationGuideBridge;
 }  // namespace android
 class ChromeHintsManager;
+class ModelInfo;
 class OptimizationGuideStore;
 class PredictionManager;
 class PredictionManagerBrowserTestBase;
 class PredictionModelDownloadClient;
+class PredictionModelStoreBrowserTest;
 class PushNotificationManager;
-class ModelInfo;
 class TabUrlProvider;
 class TopHostProvider;
 }  // namespace optimization_guide
 
+class ChromeBrowserMainExtraPartsOptimizationGuide;
 class GURL;
 class OptimizationGuideLogger;
 class OptimizationGuideNavigationData;
@@ -129,15 +131,17 @@
   }
 
  private:
+  friend class ChromeBrowserMainExtraPartsOptimizationGuide;
   friend class ChromeBrowsingDataRemoverDelegate;
   friend class HintsFetcherBrowserTest;
+  friend class OptimizationGuideInternalsUI;
   friend class OptimizationGuideKeyedServiceBrowserTest;
   friend class OptimizationGuideMessageHandler;
   friend class OptimizationGuideWebContentsObserver;
-  friend class optimization_guide::PredictionModelDownloadClient;
   friend class optimization_guide::PredictionManagerBrowserTestBase;
+  friend class optimization_guide::PredictionModelDownloadClient;
+  friend class optimization_guide::PredictionModelStoreBrowserTest;
   friend class optimization_guide::android::OptimizationGuideBridge;
-  friend class OptimizationGuideInternalsUI;
 
   // Initializes |this|.
   void Initialize();
@@ -189,6 +193,8 @@
   // Manages the storing, loading, and fetching of hints.
   std::unique_ptr<optimization_guide::ChromeHintsManager> hints_manager_;
 
+  // TODO(crbug/1358568): Remove this old model store once the new model store
+  // is launched.
   // The store of optimization target prediction models and features.
   std::unique_ptr<optimization_guide::OptimizationGuideStore>
       prediction_model_and_features_store_;
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index 0035797b..3152f14 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -58,28 +58,9 @@
 
 constexpr int kSuccessfulModelVersion = 123;
 
-std::unique_ptr<optimization_guide::proto::GetModelsResponse>
-BuildGetModelsResponse() {
-  std::unique_ptr<optimization_guide::proto::GetModelsResponse>
-      get_models_response =
-          std::make_unique<optimization_guide::proto::GetModelsResponse>();
-
-  std::unique_ptr<optimization_guide::proto::PredictionModel> prediction_model =
-      std::make_unique<optimization_guide::proto::PredictionModel>();
-  optimization_guide::proto::ModelInfo* model_info =
-      prediction_model->mutable_model_info();
-  model_info->set_version(2);
-  model_info->set_optimization_target(
-      optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
-  model_info->add_supported_model_engine_versions(
-      optimization_guide::proto::ModelEngineVersion::
-          MODEL_ENGINE_VERSION_TFLITE_2_8);
-  prediction_model->mutable_model()->set_download_url(
-      "https://example.com/model");
-  *get_models_response->add_models() = *prediction_model.get();
-
-  return get_models_response;
-}
+// Timeout to allow the model file to be downloaded, unzipped and sent to the
+// model file observers.
+constexpr base::TimeDelta kModelFileDownloadTimeout = base::Seconds(60);
 
 enum class PredictionModelsFetcherRemoteResponseType {
   kSuccessfulWithValidModelFile = 0,
@@ -93,28 +74,6 @@
 
 namespace optimization_guide {
 
-class ModelFileObserver : public OptimizationTargetModelObserver {
- public:
-  using ModelFileReceivedCallback =
-      base::OnceCallback<void(proto::OptimizationTarget, const ModelInfo&)>;
-
-  ModelFileObserver() = default;
-  ~ModelFileObserver() override = default;
-
-  void set_model_file_received_callback(ModelFileReceivedCallback callback) {
-    file_received_callback_ = std::move(callback);
-  }
-
-  void OnModelUpdated(proto::OptimizationTarget optimization_target,
-                      const ModelInfo& model_info) override {
-    if (file_received_callback_)
-      std::move(file_received_callback_).Run(optimization_target, model_info);
-  }
-
- private:
-  ModelFileReceivedCallback file_received_callback_;
-};
-
 // Abstract base class for browser testing Prediction Manager.
 // Actual class fixtures should implement InitializeFeatureList to set up
 // features used in tests.
@@ -144,8 +103,6 @@
   }
 
   void SetUpOnMainThread() override {
-    content::NetworkConnectionChangeSimulator().SetConnectionType(
-        network::mojom::ConnectionType::CONNECTION_2G);
     https_server_ = std::make_unique<net::EmbeddedTestServer>(
         net::EmbeddedTestServer::TYPE_HTTPS);
     https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
@@ -168,8 +125,6 @@
   }
 
   void SetUpCommandLine(base::CommandLine* cmd) override {
-    cmd->AppendSwitch("enable-spdy-proxy-auth");
-
     cmd->AppendSwitch(optimization_guide::switches::
                           kDisableCheckingUserPermissionsForTesting);
     cmd->AppendSwitchASCII(optimization_guide::switches::kFetchHintsOverride,
@@ -205,9 +160,6 @@
     return optimization_guide_keyed_service->GetPredictionManager();
   }
 
-  GURL https_url_with_content() { return https_url_with_content_; }
-  GURL https_url_without_content() { return https_url_without_content_; }
-
  protected:
   // Virtualize for testing different feature configurations.
   virtual void InitializeFeatureList() = 0;
@@ -278,7 +230,8 @@
       PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile;
 };
 
-class PredictionManagerBrowserTest : public PredictionManagerBrowserTestBase {
+class PredictionManagerBrowserTest : public testing::WithParamInterface<bool>,
+                                     public PredictionManagerBrowserTestBase {
  public:
   PredictionManagerBrowserTest() = default;
   ~PredictionManagerBrowserTest() override = default;
@@ -287,21 +240,30 @@
   PredictionManagerBrowserTest& operator=(const PredictionManagerBrowserTest&) =
       delete;
 
+  bool ShouldEnableInstallWideModelStore() const { return GetParam(); }
+
  private:
   void InitializeFeatureList() override {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {optimization_guide::features::kOptimizationHints, {}},
-            {optimization_guide::features::kRemoteOptimizationGuideFetching,
-             {}},
-            {optimization_guide::features::kOptimizationTargetPrediction,
-             {{"fetch_startup_delay_ms", "2000"}}},
-        },
-        {});
+    std::vector<base::test::FeatureRefAndParams> enabled_features = {
+        {optimization_guide::features::kOptimizationHints, {}},
+        {optimization_guide::features::kRemoteOptimizationGuideFetching, {}},
+        {optimization_guide::features::kOptimizationTargetPrediction,
+         {{"fetch_startup_delay_ms", "2000"}}},
+    };
+    if (ShouldEnableInstallWideModelStore()) {
+      enabled_features.emplace_back(
+          features::kOptimizationGuideInstallWideModelStore,
+          base::FieldTrialParams());
+    }
+    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
   }
 };
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
+INSTANTIATE_TEST_SUITE_P(All,
+                         PredictionManagerBrowserTest,
+                         /*use_install_wide_model_store=*/testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(PredictionManagerBrowserTest,
                        ComponentUpdatesPrefDisabled) {
   ModelFileObserver model_file_observer;
   SetResponseType(PredictionModelsFetcherRemoteResponseType::kUnsuccessful);
@@ -322,7 +284,7 @@
       0);
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerBrowserTest,
                        ModelsAndFeaturesStoreInitialized) {
   ModelFileObserver model_file_observer;
   SetResponseType(
@@ -348,7 +310,7 @@
       kSuccessfulModelVersion, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerBrowserTest,
                        PredictionModelFetchFailed) {
   ModelFileObserver model_file_observer;
   SetResponseType(PredictionModelsFetcherRemoteResponseType::kUnsuccessful);
@@ -416,22 +378,30 @@
 
  private:
   void InitializeFeatureList() override {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {features::kOptimizationHints, {}},
-            {features::kRemoteOptimizationGuideFetching, {}},
-            {features::kOptimizationTargetPrediction, {}},
-            {features::kOptimizationGuideModelDownloading,
-             {{"unrestricted_model_downloading", "true"}}},
-        },
-        {});
+    std::vector<base::test::FeatureRefAndParams> enabled_features = {
+        {features::kOptimizationHints, {}},
+        {features::kRemoteOptimizationGuideFetching, {}},
+        {features::kOptimizationTargetPrediction, {}},
+        {features::kOptimizationGuideModelDownloading,
+         {{"unrestricted_model_downloading", "true"}}},
+    };
+    if (ShouldEnableInstallWideModelStore()) {
+      enabled_features.emplace_back(
+          features::kOptimizationGuideInstallWideModelStore,
+          base::FieldTrialParams());
+    }
+    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
   }
 
   std::unique_ptr<ModelFileObserver> model_file_observer_;
 };
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         PredictionManagerModelDownloadingBrowserTest,
+                         /*ShouldEnableInstallWideModelStore=*/testing::Bool());
+
 // Flaky on various bots. See https://crbug.com/1266318
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        DISABLED_TestIncognitoUsesModelFromRegularProfile) {
   SetResponseType(
       PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
@@ -455,8 +425,8 @@
     // Wait until the observer receives the file. We increase the timeout to 60
     // seconds here since the file is on the larger side.
     {
-      base::test::ScopedRunLoopTimeout file_download_timeout(FROM_HERE,
-                                                             base::Seconds(60));
+      base::test::ScopedRunLoopTimeout file_download_timeout(
+          FROM_HERE, kModelFileDownloadTimeout);
       run_loop->Run();
     }
 
@@ -504,7 +474,7 @@
 #else
 #define MAYBE_TestIncognitoDoesntFetchModels TestIncognitoDoesntFetchModels
 #endif
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        MAYBE_TestIncognitoDoesntFetchModels) {
   base::HistogramTester histogram_tester;
 
@@ -531,7 +501,7 @@
       "OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        TestDownloadUrlAcceptedByDownloadServiceButInvalid) {
   base::HistogramTester histogram_tester;
 
@@ -570,7 +540,7 @@
       "OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        TestSuccessfulModelFileFlow) {
   base::HistogramTester histogram_tester;
 
@@ -584,11 +554,13 @@
         EXPECT_EQ(optimization_target,
                   proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 
-        // Regression test for crbug/1327975.
-        // Make sure model file path downloaded into profile dir.
-        base::FilePath profile_dir =
-            g_browser_process->profile_manager()->GetLastUsedProfileDir();
-        EXPECT_TRUE(profile_dir.IsParent(model_info.GetModelFilePath()));
+        if (!GetParam()) {
+          // Regression test for crbug/1327975.
+          // Make sure model file path downloaded into profile dir.
+          base::FilePath profile_dir =
+              g_browser_process->profile_manager()->GetLastUsedProfileDir();
+          EXPECT_TRUE(profile_dir.IsParent(model_info.GetModelFilePath()));
+        }
         run_loop->Quit();
       },
       run_loop.get()));
@@ -600,8 +572,8 @@
   // Wait until the observer receives the file. We increase the timeout to 60
   // seconds here since the file is on the larger side.
   {
-    base::test::ScopedRunLoopTimeout file_download_timeout(FROM_HERE,
-                                                           base::Seconds(60));
+    base::test::ScopedRunLoopTimeout file_download_timeout(
+        FROM_HERE, kModelFileDownloadTimeout);
     run_loop->Run();
   }
 
@@ -639,7 +611,7 @@
                        1)));
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        TestSuccessfulModelFileFlowWithAdditionalFile) {
   base::HistogramTester histogram_tester;
 
@@ -667,8 +639,8 @@
   // Wait until the observer receives the file. We increase the timeout to 60
   // seconds here since the file is on the larger side.
   {
-    base::test::ScopedRunLoopTimeout file_download_timeout(FROM_HERE,
-                                                           base::Seconds(60));
+    base::test::ScopedRunLoopTimeout file_download_timeout(
+        FROM_HERE, kModelFileDownloadTimeout);
     run_loop->Run();
   }
 
@@ -706,7 +678,7 @@
                        1)));
 }
 
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        TestSuccessfulModelFileFlowWithInvalidAdditionalFile) {
   base::HistogramTester histogram_tester;
 
@@ -753,7 +725,7 @@
 #else
 #define MAYBE_TestSwitchProfileDoesntCrash TestSwitchProfileDoesntCrash
 #endif
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        MAYBE_TestSwitchProfileDoesntCrash) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   base::FilePath other_path =
@@ -767,7 +739,7 @@
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
 // CreateGuestBrowser() is not supported for Android or ChromeOS out of the box.
-IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+IN_PROC_BROWSER_TEST_P(PredictionManagerModelDownloadingBrowserTest,
                        GuestProfileReceivesModel) {
   SetResponseType(
       PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc
new file mode 100644
index 0000000..bada62c8
--- /dev/null
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_store_browsertest.cc
@@ -0,0 +1,289 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_run_loop_timeout.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/optimization_guide/browser_test_util.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_key.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profile_test_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/optimization_guide/core/optimization_guide_constants.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "components/optimization_guide/core/prediction_manager.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_switches.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+namespace optimization_guide {
+
+namespace {
+
+constexpr int kSuccessfulModelVersion = 123;
+
+// Test locales.
+constexpr char kTestLocaleFoo[] = "en-CA";
+
+// Timeout to allow the model file to be downloaded, unzipped and sent to the
+// model file observers.
+constexpr base::TimeDelta kModelFileDownloadTimeout = base::Seconds(60);
+
+Profile* CreateProfile() {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  return profiles::testing::CreateProfileSync(
+      profile_manager, profile_manager->GenerateNextProfileDirectoryPath());
+}
+
+proto::ModelCacheKey GetModelCacheKey(const std::string& locale) {
+  proto::ModelCacheKey model_cache_key;
+  model_cache_key.set_locale(locale);
+  return model_cache_key;
+}
+
+}  // namespace
+
+class PredictionModelStoreBrowserTest : public InProcessBrowserTest {
+ public:
+  PredictionModelStoreBrowserTest() = default;
+  ~PredictionModelStoreBrowserTest() override = default;
+
+  PredictionModelStoreBrowserTest(const PredictionModelStoreBrowserTest&) =
+      delete;
+  PredictionModelStoreBrowserTest& operator=(
+      const PredictionModelStoreBrowserTest&) = delete;
+
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        {{features::kOptimizationGuideInstallWideModelStore}}, {});
+    models_server_ = std::make_unique<net::EmbeddedTestServer>(
+        net::EmbeddedTestServer::TYPE_HTTPS);
+    models_server_->ServeFilesFromSourceDirectory(
+        "chrome/test/data/optimization_guide");
+    models_server_->RegisterRequestHandler(base::BindRepeating(
+        &PredictionModelStoreBrowserTest::HandleGetModelsRequest,
+        base::Unretained(this)));
+
+    ASSERT_TRUE(models_server_->Start());
+    InProcessBrowserTest::SetUp();
+  }
+
+  void SetUpOnMainThread() override {
+    download_server_ = std::make_unique<net::EmbeddedTestServer>(
+        net::EmbeddedTestServer::TYPE_HTTPS);
+    download_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+    ASSERT_TRUE(download_server_->Start());
+    model_file_url_ = models_server_->GetURL("/signed_valid_model.crx3");
+
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  void TearDownOnMainThread() override {
+    EXPECT_TRUE(download_server_->ShutdownAndWaitUntilComplete());
+    EXPECT_TRUE(models_server_->ShutdownAndWaitUntilComplete());
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  void SetUpCommandLine(base::CommandLine* cmd) override {
+    cmd->AppendSwitch(switches::kDisableCheckingUserPermissionsForTesting);
+    cmd->AppendSwitchASCII(
+        switches::kOptimizationGuideServiceGetModelsURL,
+        models_server_
+            ->GetURL(GURL(kOptimizationGuideServiceGetModelsDefaultURL).host(),
+                     "/")
+            .spec());
+    cmd->AppendSwitchASCII("host-rules", "MAP * 127.0.0.1");
+    cmd->AppendSwitchASCII("force-variation-ids", "4");
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    cmd->AppendSwitch(ash::switches::kIgnoreUserProfileMappingForTests);
+#endif
+  }
+
+  void RegisterModelFileObserverWithKeyedService(
+      ModelFileObserver* model_file_observer,
+      Profile* profile) {
+    OptimizationGuideKeyedServiceFactory::GetForProfile(profile)
+        ->AddObserverForOptimizationTargetModel(
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+            /*model_metadata=*/absl::nullopt, model_file_observer);
+  }
+
+  // Registers |model_file_observer| for model updates from the optimization
+  // guide service in |profile|. Default profile is used, when |profile| is
+  // null.
+  void RegisterAndWaitForModelUpdate(ModelFileObserver* model_file_observer,
+                                     Profile* profile = nullptr) {
+    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+    model_file_observer->set_model_file_received_callback(
+        base::BindOnce([](base::RunLoop* run_loop,
+                          proto::OptimizationTarget optimization_target,
+                          const ModelInfo& model_info) { run_loop->Quit(); },
+                       run_loop.get()));
+
+    RegisterModelFileObserverWithKeyedService(
+        model_file_observer, profile ? profile : browser()->profile());
+    base::test::ScopedRunLoopTimeout model_file_download_timeout(
+        FROM_HERE, kModelFileDownloadTimeout);
+    run_loop->Run();
+  }
+
+  void SetModelCacheKey(Profile* profile,
+                        const proto::ModelCacheKey& model_cache_key) {
+    OptimizationGuideKeyedServiceFactory::GetForProfile(profile)
+        ->GetPredictionManager()
+        ->SetModelCacheKeyForTesting(model_cache_key);
+  }
+
+ protected:
+  std::unique_ptr<net::test_server::HttpResponse> HandleGetModelsRequest(
+      const net::test_server::HttpRequest& request) {
+    // Returning nullptr will cause the test server to fallback to serving the
+    // file from the test data directory.
+    if (request.GetURL() == model_file_url_)
+      return nullptr;
+    auto get_models_response = BuildGetModelsResponse();
+    get_models_response->mutable_models(0)->mutable_model()->set_download_url(
+        model_file_url_.spec());
+    std::string serialized_response;
+    get_models_response->SerializeToString(&serialized_response);
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_content(serialized_response);
+    response->set_code(net::HTTP_OK);
+    return std::move(response);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  GURL model_file_url_;
+  std::unique_ptr<net::EmbeddedTestServer> download_server_;
+  std::unique_ptr<net::EmbeddedTestServer> models_server_;
+  base::HistogramTester histogram_tester_;
+};
+
+IN_PROC_BROWSER_TEST_F(PredictionModelStoreBrowserTest, TestRegularProfile) {
+  ModelFileObserver model_file_observer;
+  RegisterAndWaitForModelUpdate(&model_file_observer);
+  EXPECT_EQ(model_file_observer.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_TRUE(
+      model_file_observer.model_info()->GetModelFilePath().IsAbsolute());
+
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+      PredictionModelDownloadStatus::kSuccess, 1);
+
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
+      kSuccessfulModelVersion, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
+      kSuccessfulModelVersion, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(PredictionModelStoreBrowserTest, TestIncognitoProfile) {
+  ModelFileObserver model_file_observer;
+  RegisterAndWaitForModelUpdate(&model_file_observer);
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+      PredictionModelDownloadStatus::kSuccess, 1);
+  EXPECT_EQ(model_file_observer.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_TRUE(
+      model_file_observer.model_info()->GetModelFilePath().IsAbsolute());
+
+  base::HistogramTester histogram_tester_otr;
+  ModelFileObserver model_file_observer_otr;
+  Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
+  RegisterAndWaitForModelUpdate(&model_file_observer_otr,
+                                otr_browser->profile());
+
+  // No more downloads should happen.
+  histogram_tester_otr.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
+  EXPECT_EQ(model_file_observer_otr.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_EQ(model_file_observer.model_info()->GetModelFilePath(),
+            model_file_observer_otr.model_info()->GetModelFilePath());
+}
+
+// Tests that two similar profiles share the model, and the model is not
+// redownloaded.
+IN_PROC_BROWSER_TEST_F(PredictionModelStoreBrowserTest,
+                       TestSimilarProfilesShareModel) {
+  ModelFileObserver model_file_observer;
+  RegisterAndWaitForModelUpdate(&model_file_observer);
+
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+      PredictionModelDownloadStatus::kSuccess, 1);
+  EXPECT_EQ(model_file_observer.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_TRUE(
+      model_file_observer.model_info()->GetModelFilePath().IsAbsolute());
+
+  base::HistogramTester histogram_tester_foo;
+  ModelFileObserver model_file_observer_foo;
+  Profile* profile_foo = CreateProfile();
+  RegisterAndWaitForModelUpdate(&model_file_observer_foo, profile_foo);
+
+  // No more downloads should happen.
+  histogram_tester_foo.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
+  EXPECT_EQ(model_file_observer_foo.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_EQ(model_file_observer.model_info()->GetModelFilePath(),
+            model_file_observer_foo.model_info()->GetModelFilePath());
+}
+
+// Tests that two dissimilar profiles do not share the model, and the model will
+// be redownloaded.
+IN_PROC_BROWSER_TEST_F(PredictionModelStoreBrowserTest,
+                       TestDissimilarProfilesNotShareModel) {
+  ModelFileObserver model_file_observer;
+  RegisterAndWaitForModelUpdate(&model_file_observer);
+
+  histogram_tester_.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+      PredictionModelDownloadStatus::kSuccess, 1);
+  EXPECT_EQ(model_file_observer.optimization_target(),
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  EXPECT_TRUE(
+      model_file_observer.model_info()->GetModelFilePath().IsAbsolute());
+
+  {
+    base::HistogramTester histogram_tester_foo;
+    ModelFileObserver model_file_observer_foo;
+    Profile* profile_foo = CreateProfile();
+    SetModelCacheKey(profile_foo, GetModelCacheKey(kTestLocaleFoo));
+
+    RegisterAndWaitForModelUpdate(&model_file_observer_foo, profile_foo);
+    // Same model will be redownloaded.
+    histogram_tester_foo.ExpectUniqueSample(
+        "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+        PredictionModelDownloadStatus::kSuccess, 1);
+    EXPECT_EQ(model_file_observer_foo.optimization_target(),
+              proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+    EXPECT_NE(model_file_observer.model_info()->GetModelFilePath(),
+              model_file_observer_foo.model_info()->GetModelFilePath());
+    EXPECT_TRUE(base::ContentsEqual(
+        model_file_observer.model_info()->GetModelFilePath(),
+        model_file_observer_foo.model_info()->GetModelFilePath()));
+  }
+}
+
+}  // namespace optimization_guide
diff --git a/chrome/browser/pdf/pdf_extension_interactive_uitest.cc b/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
index 7818e16..1f0481052 100644
--- a/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
+++ b/chrome/browser/pdf/pdf_extension_interactive_uitest.cc
@@ -18,6 +18,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/focus_changed_observer.h"
+#include "content/public/test/hit_test_region_observer.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
@@ -244,12 +245,18 @@
 IN_PROC_BROWSER_TEST_F(PDFExtensionInteractiveUITest,
                        ContextMenuOpensFromTouchSelectionMenu) {
   const GURL url = embedded_test_server()->GetURL("/pdf/text_large.pdf");
-  content::WebContents* const guest_contents = LoadPdfGetGuestContents(url);
-  ASSERT_TRUE(guest_contents);
+  extensions::MimeHandlerViewGuest* guest = LoadPdfGetMimeHandlerView(url);
+  ASSERT_TRUE(guest);
 
-  gfx::Point screen_pos =
-      ConvertPageCoordToScreenCoord(guest_contents, {12, 12});
-  views::Widget* widget = TouchSelectText(GetActiveWebContents(), screen_pos);
+  content::RenderFrameHost* guest_mainframe = guest->GetGuestMainFrame();
+  ASSERT_TRUE(guest_mainframe);
+  content::WaitForHitTestData(guest_mainframe);
+
+  const gfx::Point point_in_root_coords =
+      guest_mainframe->GetView()->TransformPointToRootCoordSpace(
+          ConvertPageCoordToScreenCoord(guest_mainframe, {12, 12}));
+  views::Widget* widget =
+      TouchSelectText(GetActiveWebContents(), point_in_root_coords);
   ASSERT_TRUE(widget);
   views::View* menu = widget->GetContentsView();
   ASSERT_TRUE(menu);
@@ -287,14 +294,18 @@
   // Use test.pdf here because it has embedded font metrics. With a fixed zoom,
   // coordinates should be consistent across platforms.
   const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf#zoom=100");
-  content::WebContents* const guest_contents = LoadPdfGetGuestContents(url);
-  ASSERT_TRUE(guest_contents);
+  extensions::MimeHandlerViewGuest* guest = LoadPdfGetMimeHandlerView(url);
+  ASSERT_TRUE(guest);
+
+  content::RenderFrameHost* guest_mainframe = guest->GetGuestMainFrame();
+  ASSERT_TRUE(guest_mainframe);
+  content::WaitForHitTestData(guest_mainframe);
 
   views::Widget* widget = TouchSelectText(GetActiveWebContents(), {473, 166});
   ASSERT_TRUE(widget);
 
   auto* touch_selection_controller =
-      guest_contents->GetRenderWidgetHostView()
+      guest_mainframe->GetView()
           ->GetTouchSelectionControllerClientManager()
           ->GetTouchSelectionController();
 
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 055dd0c..8a825be5 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -2821,38 +2821,30 @@
 
 class PDFExtensionInternalLinkClickTest : public PDFExtensionTest {
  public:
-  PDFExtensionInternalLinkClickTest() : guest_contents_(nullptr) {}
-  ~PDFExtensionInternalLinkClickTest() override {}
+  PDFExtensionInternalLinkClickTest() = default;
+  ~PDFExtensionInternalLinkClickTest() override = default;
 
  protected:
-  void LoadTestLinkPdfGetGuestContents() {
-    guest_contents_ = LoadPdfGetGuestContents(
+  MimeHandlerViewGuest* LoadTestLinkPdfGetMimeHandlerView() {
+    return LoadPdfGetMimeHandlerView(
         embedded_test_server()->GetURL("/pdf/test-internal-link.pdf"));
-    ASSERT_TRUE(guest_contents_);
   }
 
-  gfx::Point GetLinkPosition() {
+  gfx::Point GetLinkPosition(MimeHandlerViewGuest* guest) {
     // The whole first page is a link.
-    return ConvertPageCoordToScreenCoord(guest_contents_, {100, 100});
+    return ConvertPageCoordToScreenCoord(guest->GetGuestMainFrame(),
+                                         {100, 100});
   }
-
-  // TODO(crbug.com/1261928): Tests should transform the point by the guest's
-  // RenderWidgetHostView's TransformPointToRootCoordSpace and send to the
-  // embedding WebContents.
-  WebContents* GetWebContentsForInputRouting() { return guest_contents_; }
-
- private:
-  raw_ptr<WebContents, DanglingUntriaged> guest_contents_;
 };
 
 IN_PROC_BROWSER_TEST_F(PDFExtensionInternalLinkClickTest, CtrlLeft) {
-  LoadTestLinkPdfGetGuestContents();
+  MimeHandlerViewGuest* guest = LoadTestLinkPdfGetMimeHandlerView();
 
   WebContents* web_contents = GetActiveWebContents();
 
-  content::SimulateMouseClickAt(
-      GetWebContentsForInputRouting(), kDefaultKeyModifier,
-      blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
+  SimulateMouseClickAt(guest, kDefaultKeyModifier,
+                       blink::WebMouseEvent::Button::kLeft,
+                       GetLinkPosition(guest));
   ui_test_utils::TabAddedWaiter(browser()).Wait();
 
   int tab_count = browser()->tab_strip_model()->count();
@@ -2872,13 +2864,12 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PDFExtensionInternalLinkClickTest, Middle) {
-  LoadTestLinkPdfGetGuestContents();
+  MimeHandlerViewGuest* guest = LoadTestLinkPdfGetMimeHandlerView();
 
   WebContents* web_contents = GetActiveWebContents();
 
-  content::SimulateMouseClickAt(GetWebContentsForInputRouting(), 0,
-                                blink::WebMouseEvent::Button::kMiddle,
-                                GetLinkPosition());
+  SimulateMouseClickAt(guest, 0, blink::WebMouseEvent::Button::kMiddle,
+                       GetLinkPosition(guest));
   ui_test_utils::TabAddedWaiter(browser()).Wait();
 
   int tab_count = browser()->tab_strip_model()->count();
@@ -2898,15 +2889,15 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PDFExtensionInternalLinkClickTest, ShiftLeft) {
-  LoadTestLinkPdfGetGuestContents();
+  MimeHandlerViewGuest* guest = LoadTestLinkPdfGetMimeHandlerView();
 
   ASSERT_EQ(1U, chrome::GetTotalBrowserCount());
 
   WebContents* web_contents = GetActiveWebContents();
 
-  content::SimulateMouseClickAt(
-      GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
-      blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
+  SimulateMouseClickAt(guest, blink::WebInputEvent::kShiftKey,
+                       blink::WebMouseEvent::Button::kLeft,
+                       GetLinkPosition(guest));
   ui_test_utils::WaitForBrowserToOpen();
 
   ASSERT_EQ(2U, chrome::GetTotalBrowserCount());
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 1de919b8..d9bc559 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1799,6 +1799,11 @@
     prefs::kVirtualKeyboardResizesLayoutByDefault,
     base::Value::Type::BOOLEAN},
 #endif  // BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_WIN)
+  { key::kCloudAPAuthEnabled,
+    prefs::kCloudApAuthEnabled,
+    base::Value::Type::INTEGER },
+#endif  // BUILDFLAG(IS_WIN)
 };
 // clang-format on
 
diff --git a/chrome/browser/policy/messaging_layer/public/report_client.cc b/chrome/browser/policy/messaging_layer/public/report_client.cc
index 9e679ec..713a75d1 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client.cc
@@ -36,7 +36,7 @@
 #include "components/reporting/encryption/encryption_module.h"
 #include "components/reporting/encryption/verification.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_module.h"
 #include "components/reporting/storage/storage_module_interface.h"
diff --git a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.cc b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.cc
index a88c12c..245a614 100644
--- a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.cc
+++ b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.cc
@@ -17,7 +17,7 @@
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
diff --git a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h
index 4caa8c4..aafee93 100644
--- a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h
+++ b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h
@@ -15,7 +15,7 @@
 #include "build/chromeos_buildflags.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
 #include "components/reporting/util/task_runner_context.h"
diff --git a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
index 1f1bb0d..422cef43 100644
--- a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
@@ -18,8 +18,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
@@ -73,7 +72,7 @@
   DmServerTest()
       : sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})),
         handler_(std::make_unique<TestRecordHandler>()),
-        memory_resource_(base::MakeRefCounted<MemoryResourceImpl>(
+        memory_resource_(base::MakeRefCounted<ResourceManager>(
             4u * 1024LLu * 1024LLu))  // 4 MiB
   {}
 
@@ -90,7 +89,7 @@
 
   const base::TimeDelta kMaxDelay_ = base::Seconds(1);
 
-  scoped_refptr<ResourceInterface> memory_resource_;
+  scoped_refptr<ResourceManager> memory_resource_;
 };
 
 class DmServerFailureTest : public DmServerTest,
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
index dac7ff70..34b254b 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
@@ -25,7 +25,7 @@
 #include "chrome/browser/policy/messaging_layer/util/reporting_server_connector.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.h b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.h
index 827ecc1..1d6c050 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.h
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.h
@@ -13,7 +13,7 @@
 #include "base/task/task_runner.h"
 #include "chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
 
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
index 6867190..71f258a 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
@@ -25,8 +25,7 @@
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
@@ -74,8 +73,8 @@
   void SetUp() override {
     mock_client_.SetDMToken(
         policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
-    memory_resource_ = base::MakeRefCounted<MemoryResourceImpl>(
-        4u * 1024LLu * 1024LLu);  // 4 MiB
+    memory_resource_ =
+        base::MakeRefCounted<ResourceManager>(4u * 1024LLu * 1024LLu);  // 4 MiB
   }
 
   void TearDown() override {
@@ -91,13 +90,13 @@
   policy::MockCloudPolicyClient mock_client_;
   ReportingServerConnector::TestEnvironment test_env_{&mock_client_};
 
-  scoped_refptr<ResourceInterface> memory_resource_;
+  scoped_refptr<ResourceManager> memory_resource_;
 };
 
 std::pair<ScopedReservation, std::vector<EncryptedRecord>>
 BuildTestRecordsVector(int64_t number_of_test_records,
                        int64_t generation_id,
-                       scoped_refptr<ResourceInterface> memory_resource) {
+                       scoped_refptr<ResourceManager> memory_resource) {
   ScopedReservation total_reservation;
   std::vector<EncryptedRecord> test_records;
   test_records.reserve(number_of_test_records);
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
index cfd852e5..fc44222 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
@@ -8,7 +8,7 @@
 #include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace reporting {
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
index 165072a..78d5fbfa2 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
@@ -11,10 +11,10 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
+#include "base/test/task_environment.h"
 #include "base/token.h"
 #include "chrome/browser/policy/messaging_layer/util/test_request_payload.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -96,8 +96,8 @@
   }
 
   void SetUp() override {
-    memory_resource_ = base::MakeRefCounted<MemoryResourceImpl>(
-        4u * 1024LLu * 1024LLu);  // 4 MiB
+    memory_resource_ =
+        base::MakeRefCounted<ResourceManager>(4u * 1024LLu * 1024LLu);  // 4 MiB
   }
 
   void TearDown() override {
@@ -106,7 +106,8 @@
 
   bool need_encryption_key() const { return GetParam(); }
 
-  scoped_refptr<ResourceInterface> memory_resource_;
+  base::test::TaskEnvironment task_environment_;
+  scoped_refptr<ResourceManager> memory_resource_;
 };
 
 TEST_P(RecordUploadRequestBuilderTest, AcceptEncryptedRecordsList) {
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_client.cc b/chrome/browser/policy/messaging_layer/upload/upload_client.cc
index c9768b7..e4a880d 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_client.cc
+++ b/chrome/browser/policy/messaging_layer/upload/upload_client.cc
@@ -6,7 +6,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_client.h b/chrome/browser/policy/messaging_layer/upload/upload_client.h
index 345e8ad9..3f3199a 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_client.h
+++ b/chrome/browser/policy/messaging_layer/upload/upload_client.h
@@ -11,7 +11,7 @@
 #include "base/task/task_runner.h"
 #include "chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
 
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
index f62c83ce..238f084 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
@@ -25,7 +25,7 @@
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/memory_resource_impl.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -74,8 +74,8 @@
 
  protected:
   void SetUp() override {
-    memory_resource_ = base::MakeRefCounted<MemoryResourceImpl>(
-        4u * 1024LLu * 1024LLu);  // 4 MiB
+    memory_resource_ =
+        base::MakeRefCounted<ResourceManager>(4u * 1024LLu * 1024LLu);  // 4 MiB
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     // Set up fake primary profile.
@@ -112,7 +112,7 @@
   std::unique_ptr<TestingProfile> profile_;
   std::unique_ptr<user_manager::ScopedUserManager> user_manager_;
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-  scoped_refptr<ResourceInterface> memory_resource_;
+  scoped_refptr<ResourceManager> memory_resource_;
 };
 
 using TestEncryptionKeyAttached = MockFunction<void(SignedEncryptionInfo)>;
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_provider.h b/chrome/browser/policy/messaging_layer/upload/upload_provider.h
index 4ce4a29..a121e33 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_provider.h
+++ b/chrome/browser/policy/messaging_layer/upload/upload_provider.h
@@ -14,7 +14,7 @@
 #include "base/threading/platform_thread.h"
 #include "chrome/browser/policy/messaging_layer/upload/upload_client.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 
 namespace reporting {
 
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_provider_unittest.cc b/chrome/browser/policy/messaging_layer/upload/upload_provider_unittest.cc
index 8cef2bac..ec57c83 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_provider_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/upload_provider_unittest.cc
@@ -18,8 +18,7 @@
 #include "chrome/browser/policy/messaging_layer/util/test_response_payload.h"
 #include "components/policy/core/common/cloud/dm_token.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -72,8 +71,8 @@
 
  protected:
   void SetUp() override {
-    memory_resource_ = base::MakeRefCounted<MemoryResourceImpl>(
-        4u * 1024LLu * 1024LLu);  // 4 MiB
+    memory_resource_ =
+        base::MakeRefCounted<ResourceManager>(4u * 1024LLu * 1024LLu);  // 4 MiB
     cloud_policy_client_.SetDMToken(
         policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
     service_provider_ = std::make_unique<TestEncryptedReportingUploadProvider>(
@@ -114,7 +113,7 @@
   ReportingServerConnector::TestEnvironment test_env_{&cloud_policy_client_};
   EncryptedRecord record_;
 
-  scoped_refptr<ResourceInterface> memory_resource_;
+  scoped_refptr<ResourceManager> memory_resource_;
 
   std::unique_ptr<TestEncryptedReportingUploadProvider> service_provider_;
 };
diff --git a/chrome/browser/policy/test/network_prediction_policy_browsertest.cc b/chrome/browser/policy/test/network_prediction_policy_browsertest.cc
index 8547ff9..4d42730 100644
--- a/chrome/browser/policy/test/network_prediction_policy_browsertest.cc
+++ b/chrome/browser/policy/test/network_prediction_policy_browsertest.cc
@@ -18,7 +18,8 @@
   PrefService* prefs = chrome_test_utils::GetProfile(this)->GetPrefs();
 
   // Enabled by default.
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 
   // Disabled by policy.
   PolicyMap policies;
@@ -28,7 +29,8 @@
                    prefetch::NetworkPredictionOptions::kDisabled)),
                nullptr);
   UpdateProviderPolicy(policies);
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(*prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   // Enabled by policy.
   policies.Set(key::kNetworkPredictionOptions, POLICY_LEVEL_MANDATORY,
@@ -37,7 +39,8 @@
                    prefetch::NetworkPredictionOptions::kStandard)),
                nullptr);
   UpdateProviderPolicy(policies);
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 
   policies.Set(key::kNetworkPredictionOptions, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
@@ -45,7 +48,8 @@
                    prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated)),
                nullptr);
   UpdateProviderPolicy(policies);
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 
   policies.Set(key::kNetworkPredictionOptions, POLICY_LEVEL_MANDATORY,
                POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
@@ -53,7 +57,8 @@
                    prefetch::NetworkPredictionOptions::kExtended)),
                nullptr);
   UpdateProviderPolicy(policies);
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 }
 
 }  // namespace policy
diff --git a/chrome/browser/predictors/loading_predictor_config.cc b/chrome/browser/predictors/loading_predictor_config.cc
index 5873ecc..780490b6 100644
--- a/chrome/browser/predictors/loading_predictor_config.cc
+++ b/chrome/browser/predictors/loading_predictor_config.cc
@@ -36,7 +36,8 @@
 
   // Checks that the preconnect is allowed by user settings.
   return profile && profile->GetPrefs() &&
-         prefetch::IsSomePreloadingEnabled(*profile->GetPrefs());
+         (prefetch::IsSomePreloadingEnabled(*profile->GetPrefs()) ==
+          content::PreloadingEligibility::kEligible);
 }
 
 std::string GetStringNameForHintOrigin(HintOrigin hint_origin) {
diff --git a/chrome/browser/predictors/preconnect_manager.cc b/chrome/browser/predictors/preconnect_manager.cc
index 48b500a..de39505 100644
--- a/chrome/browser/predictors/preconnect_manager.cc
+++ b/chrome/browser/predictors/preconnect_manager.cc
@@ -87,7 +87,8 @@
   if (!profile) {
     return false;
   }
-  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 void PreconnectManager::Start(const GURL& url,
diff --git a/chrome/browser/prefetch/prefetch_prefs.cc b/chrome/browser/prefetch/prefetch_prefs.cc
index 2ee1afc..fad74b38 100644
--- a/chrome/browser/prefetch/prefetch_prefs.cc
+++ b/chrome/browser/prefetch/prefetch_prefs.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/prefetch/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/preloading.h"
 
 namespace prefetch {
 
@@ -60,18 +61,24 @@
   prefs->SetInteger(prefs::kNetworkPredictionOptions, static_cast<int>(value));
 }
 
-bool IsSomePreloadingEnabled(const PrefService& prefs) {
+content::PreloadingEligibility IsSomePreloadingEnabled(
+    const PrefService& prefs) {
   if (base::FeatureList::IsEnabled(kPreloadingHoldback)) {
-    return false;
+    return content::PreloadingEligibility::kPreloadingDisabled;
   }
   return IsSomePreloadingEnabledIgnoringFinch(prefs);
 }
 
-bool IsSomePreloadingEnabledIgnoringFinch(const PrefService& prefs) {
+content::PreloadingEligibility IsSomePreloadingEnabledIgnoringFinch(
+    const PrefService& prefs) {
   if (battery::IsBatterySaverEnabled()) {
-    return false;
+    return content::PreloadingEligibility::kBatterySaverEnabled;
   }
-  return GetPreloadPagesState(prefs) != PreloadPagesState::kNoPreloading;
+  if (GetPreloadPagesState(prefs) == PreloadPagesState::kNoPreloading) {
+    return content::PreloadingEligibility::kPreloadingDisabled;
+  }
+
+  return content::PreloadingEligibility::kEligible;
 }
 
 }  // namespace prefetch
diff --git a/chrome/browser/prefetch/prefetch_prefs.h b/chrome/browser/prefetch/prefetch_prefs.h
index 6c4021a2..dd65950 100644
--- a/chrome/browser/prefetch/prefetch_prefs.h
+++ b/chrome/browser/prefetch/prefetch_prefs.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_PREFETCH_PREFETCH_PREFS_H_
 
 #include "base/feature_list.h"
+#include "content/public/browser/preloading.h"
 
 namespace user_prefs {
 class PrefRegistrySyncable;
@@ -57,13 +58,16 @@
 // persist it in prefs.
 void SetPreloadPagesState(PrefService* prefs, PreloadPagesState state);
 
-// Returns true if preloading is not entirely disabled. Preloading might be
-// disabled by the user via UI settings stored in prefs or by Finch.
-bool IsSomePreloadingEnabled(const PrefService& prefs);
+// Returns PreloadingEligibility:kEligible if preloading is not entirely
+// disabled. Returns the first blocking reason encountered otherwise.
+content::PreloadingEligibility IsSomePreloadingEnabled(
+    const PrefService& prefs);
 
-// Returns true if preloading is not entirely disabled via the UI settings.
+// Returns PreloadingEligibility:kEligible if preloading is not entirely
+// disabled. Returns the first blocking reason encountered otherwise.
 // Ignores the PreloadingHoldback Finch feature.
-bool IsSomePreloadingEnabledIgnoringFinch(const PrefService& prefs);
+content::PreloadingEligibility IsSomePreloadingEnabledIgnoringFinch(
+    const PrefService& prefs);
 
 void RegisterPredictionOptionsProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry);
diff --git a/chrome/browser/prefetch/prefetch_prefs_unittest.cc b/chrome/browser/prefetch/prefetch_prefs_unittest.cc
index d19ca6b..f269ea678 100644
--- a/chrome/browser/prefetch/prefetch_prefs_unittest.cc
+++ b/chrome/browser/prefetch/prefetch_prefs_unittest.cc
@@ -80,23 +80,27 @@
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kDisabled));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kEligible);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(
           prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kEligible);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kEligible);
 }
 
 TEST(PrefetchPrefsTest, IsSomePreloadingEnabled_PreloadingHoldback) {
@@ -110,23 +114,27 @@
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kDisabled));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(
           prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 }
 
 TEST(PrefetchPrefsTest, IsSomePreloadingEnabledIgnoringFinch) {
@@ -140,23 +148,27 @@
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kDisabled));
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kEligible);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(
           prefetch::NetworkPredictionOptions::kWifiOnlyDeprecated));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kEligible);
 
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kExtended));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kEligible);
 }
 
 class PrefetchPrefsWithBatterySaverTest : public ::testing::Test {
@@ -175,10 +187,12 @@
       static_cast<int>(prefetch::NetworkPredictionOptions::kDefault));
 
   battery::OverrideIsBatterySaverEnabledForTesting(false);
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kEligible);
 
   battery::OverrideIsBatterySaverEnabledForTesting(true);
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabledIgnoringFinch(prefs),
+            content::PreloadingEligibility::kBatterySaverEnabled);
 }
 
 TEST_F(PrefetchPrefsWithBatterySaverTest, IsSomePreloadingEnabled) {
@@ -190,11 +204,14 @@
   prefs.SetInteger(
       prefs::kNetworkPredictionOptions,
       static_cast<int>(prefetch::NetworkPredictionOptions::kStandard));
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kEligible);
 
   battery::OverrideIsBatterySaverEnabledForTesting(false);
-  EXPECT_TRUE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kEligible);
 
   battery::OverrideIsBatterySaverEnabledForTesting(true);
-  EXPECT_FALSE(prefetch::IsSomePreloadingEnabled(prefs));
+  EXPECT_EQ(prefetch::IsSomePreloadingEnabled(prefs),
+            content::PreloadingEligibility::kBatterySaverEnabled);
 }
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index e90e6e48..9a0e044 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -436,6 +436,7 @@
 
 #if BUILDFLAG(IS_WIN)
 #include "chrome/browser/component_updater/sw_reporter_installer_win.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h"
 #include "chrome/browser/font_prewarmer_tab_helper.h"
 #include "chrome/browser/media/cdm_pref_service_helper.h"
 #include "chrome/browser/media/media_foundation_service_monitor.h"
@@ -1305,6 +1306,10 @@
   screen_ai::RegisterLocalStatePrefs(registry);
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
+#if BUILDFLAG(IS_WIN)
+  PlatformAuthPolicyObserver::RegisterPrefs(registry);
+#endif  // BUILDFLAG(IS_WIN)
+
   // This is intentionally last.
   RegisterLocalStatePrefsForMigration(registry);
 }
diff --git a/chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_manager_delegate.cc b/chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_manager_delegate.cc
index 919eb7f..aa4dd11 100644
--- a/chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_manager_delegate.cc
+++ b/chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_manager_delegate.cc
@@ -54,7 +54,8 @@
 
 bool ChromeNoStatePrefetchManagerDelegate::
     IsNetworkPredictionPreferenceEnabled() {
-  return prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 std::string
diff --git a/chrome/browser/preloading/prefetch/prefetch_proxy/chrome_prefetch_service_delegate.cc b/chrome/browser/preloading/prefetch/prefetch_proxy/chrome_prefetch_service_delegate.cc
index e498075..84853a6 100644
--- a/chrome/browser/preloading/prefetch/prefetch_proxy/chrome_prefetch_service_delegate.cc
+++ b/chrome/browser/preloading/prefetch/prefetch_proxy/chrome_prefetch_service_delegate.cc
@@ -77,7 +77,8 @@
 }
 
 bool ChromePrefetchServiceDelegate::IsSomePreloadingEnabled() {
-  return prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 bool ChromePrefetchServiceDelegate::IsExtendedPreloadingEnabled() {
diff --git a/chrome/browser/preloading/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc b/chrome/browser/preloading/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
index 54c754b..92abdcf 100644
--- a/chrome/browser/preloading/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
+++ b/chrome/browser/preloading/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
@@ -1405,7 +1405,8 @@
   }
 
   // This checks whether the user has disabled pre* actions in the settings UI.
-  if (!prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs())) {
+  if (prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs()) !=
+      content::PreloadingEligibility::kEligible) {
     return;
   }
 
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.cc
index de6d28c..92b8f3a 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.cc
@@ -255,10 +255,10 @@
     return false;
   }
 
-  if (!prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs())) {
+  auto eligibility = prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs());
+  if (eligibility != content::PreloadingEligibility::kEligible) {
     recorder.reason_ = SearchPrefetchEligibilityReason::kPrefetchDisabled;
-    SetEligibility(attempt,
-                   content::PreloadingEligibility::kPreloadingDisabled);
+    SetEligibility(attempt, eligibility);
     return false;
   }
 
diff --git a/chrome/browser/preloading/prerender/omnibox_prerender_browsertest.cc b/chrome/browser/preloading/prerender/omnibox_prerender_browsertest.cc
index fc70c08f..2a30fd9 100644
--- a/chrome/browser/preloading/prerender/omnibox_prerender_browsertest.cc
+++ b/chrome/browser/preloading/prerender/omnibox_prerender_browsertest.cc
@@ -132,7 +132,8 @@
   PrefService* prefs = GetProfile()->GetPrefs();
   prefetch::SetPreloadPagesState(prefs,
                                  prefetch::PreloadPagesState::kNoPreloading);
-  ASSERT_FALSE(prefetch::IsSomePreloadingEnabled(*prefs));
+  ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   // Navigate to initial URL.
   ASSERT_TRUE(content::NavigateToURL(web_contents, kInitialUrl));
@@ -176,7 +177,8 @@
   // Re-enable the setting.
   prefetch::SetPreloadPagesState(
       prefs, prefetch::PreloadPagesState::kStandardPreloading);
-  ASSERT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 
   content::test::PrerenderHostRegistryObserver registry_observer(*web_contents);
   // Attempt to trigger prerendering again.
diff --git a/chrome/browser/preloading/prerender/prerender_browsertest.cc b/chrome/browser/preloading/prerender/prerender_browsertest.cc
index 6a77a45a..c242fb6 100644
--- a/chrome/browser/preloading/prerender/prerender_browsertest.cc
+++ b/chrome/browser/preloading/prerender/prerender_browsertest.cc
@@ -209,7 +209,8 @@
   PrefService* prefs = chrome_test_utils::GetProfile(this)->GetPrefs();
   prefetch::SetPreloadPagesState(prefs,
                                  prefetch::PreloadPagesState::kNoPreloading);
-  ASSERT_FALSE(prefetch::IsSomePreloadingEnabled(*prefs));
+  ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kPreloadingDisabled);
 
   // Attempt to trigger prerendering.
   GURL prerender_url = embedded_test_server()->GetURL("/simple.html?1");
@@ -225,7 +226,8 @@
   // Re-enable the setting.
   prefetch::SetPreloadPagesState(
       prefs, prefetch::PreloadPagesState::kStandardPreloading);
-  ASSERT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs));
+  ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
+            content::PreloadingEligibility::kEligible);
 
   // Attempt to trigger prerendering again.
   content::test::PrerenderHostRegistryObserver registry_observer(
diff --git a/chrome/browser/privacy/privacy_metrics_service.cc b/chrome/browser/privacy/privacy_metrics_service.cc
index 4395d0c..1e3370d3 100644
--- a/chrome/browser/privacy/privacy_metrics_service.cc
+++ b/chrome/browser/privacy/privacy_metrics_service.cc
@@ -150,7 +150,8 @@
       pref_service_->GetBoolean(prefs::kEnableDoNotTrack));
 
   base::UmaHistogramBoolean("Settings.PreloadStatus.OnStartup2",
-                            prefetch::IsSomePreloadingEnabled(*pref_service_));
+                            prefetch::IsSomePreloadingEnabled(*pref_service_) ==
+                                content::PreloadingEligibility::kEligible);
 
   base::UmaHistogramBoolean(
       "Settings.AutocompleteSearches.OnStartup2",
diff --git a/chrome/browser/resources/chromeos/crostini_installer/BUILD.gn b/chrome/browser/resources/chromeos/crostini_installer/BUILD.gn
index 37b7a400..fc5fd41 100644
--- a/chrome/browser/resources/chromeos/crostini_installer/BUILD.gn
+++ b/chrome/browser/resources/chromeos/crostini_installer/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/html_to_wrapper.gni")
 
 js_type_check("closure_compile") {
   is_polymer3 = true
@@ -35,6 +35,7 @@
   ]
 }
 
-html_to_js("web_components") {
-  js_files = [ "app.js" ]
+html_to_wrapper("html_wrapper_files") {
+  in_files = [ "app.html" ]
+  use_js = true
 }
diff --git a/chrome/browser/resources/chromeos/crostini_installer/app.js b/chrome/browser/resources/chromeos/crostini_installer/app.js
index 1ef12905..8b5eeef5 100644
--- a/chrome/browser/resources/chromeos/crostini_installer/app.js
+++ b/chrome/browser/resources/chromeos/crostini_installer/app.js
@@ -16,7 +16,9 @@
 import {BrowserProxy} from 'chrome://crostini-installer/browser_proxy.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './app.html.js';
 
 /**
  * Enum for the state of `crostini-installer-app`. Not to confused with
@@ -72,7 +74,7 @@
 Polymer({
   is: 'crostini-installer-app',
 
-  _template: html`{__html_template__}`,
+  _template: getTemplate(),
 
   properties: {
     /** @private {!State} */
diff --git a/chrome/browser/resources/chromeos/crostini_upgrader/BUILD.gn b/chrome/browser/resources/chromeos/crostini_upgrader/BUILD.gn
index e9ad9e7..c70c4d0 100644
--- a/chrome/browser/resources/chromeos/crostini_upgrader/BUILD.gn
+++ b/chrome/browser/resources/chromeos/crostini_upgrader/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/html_to_wrapper.gni")
 
 js_type_check("closure_compile") {
   is_polymer3 = true
@@ -34,6 +34,7 @@
   ]
 }
 
-html_to_js("web_components") {
-  js_files = [ "app.js" ]
+html_to_wrapper("html_wrapper_files") {
+  in_files = [ "app.html" ]
+  use_js = true
 }
diff --git a/chrome/browser/resources/chromeos/crostini_upgrader/app.js b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
index 77b11a6..b3d1bc8d 100644
--- a/chrome/browser/resources/chromeos/crostini_upgrader/app.js
+++ b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
@@ -10,8 +10,9 @@
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getTemplate} from './app.html.js';
 import {BrowserProxy} from './browser_proxy.js';
 
 /**
@@ -40,7 +41,7 @@
 Polymer({
   is: 'crostini-upgrader-app',
 
-  _template: html`{__html_template__}`,
+  _template: getTemplate(),
 
   properties: {
     /** @private {State} */
diff --git a/chrome/browser/resources/feed_internals/BUILD.gn b/chrome/browser/resources/feed_internals/BUILD.gn
index 23540308d..2928c07 100644
--- a/chrome/browser/resources/feed_internals/BUILD.gn
+++ b/chrome/browser/resources/feed_internals/BUILD.gn
@@ -17,11 +17,12 @@
   non_web_component_files = [ "feed_internals.ts" ]
 
   mojo_files_deps =
-      [ "//chrome/browser/ui/webui/feed_internals:mojo_bindings_js__generator" ]
-  mojo_files = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/feed_internals/feed_internals.mojom-webui.js" ]
+      [ "//chrome/browser/ui/webui/feed_internals:mojo_bindings_ts__generator" ]
+  mojo_files = [ "$root_gen_dir/chrome/browser/ui/webui/feed_internals/feed_internals.mojom-webui.ts" ]
 
   ts_deps = [
     "//ui/webui/resources:library",
     "//ui/webui/resources/mojo/:library",
   ]
+  ts_use_local_config = false
 }
diff --git a/chrome/browser/resources/feed_internals/tsconfig_base.json b/chrome/browser/resources/feed_internals/tsconfig_base.json
deleted file mode 100644
index 99a81eca..0000000
--- a/chrome/browser/resources/feed_internals/tsconfig_base.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "extends": "../../../../tools/typescript/tsconfig_base.json",
-  "compilerOptions": {
-    "allowJs": true
-  }
-}
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index b45e8bd5..d6bd3db 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -133,11 +133,12 @@
       "shared.rollup.js",
     ]
     excludes = [
-                 "chrome://resources/cr_components/most_visited/most_visited.mojom-webui.js",
-                 "chrome://resources/js/cr.js",
-                 "chrome://resources/mojo/mojo/public/js/bindings.js",
                  "chrome://resources/cr_components/customize_themes/customize_themes.mojom-webui.js",
+                 "chrome://resources/cr_components/most_visited/most_visited.mojom-webui.js",
                  "chrome://resources/js/browser_command/browser_command.mojom-webui.js",
+                 "chrome://resources/js/cr.js",
+                 "chrome://resources/js/metrics_reporter/metrics_reporter.mojom-webui.js",
+                 "chrome://resources/mojo/mojo/public/js/bindings.js",
                  "chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js",
                  "chrome://resources/mojo/mojo/public/mojom/base/text_direction.mojom-webui.js",
                  "chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js",
@@ -165,6 +166,7 @@
     "//ui/webui/resources/cr_components/customize_themes:build_ts",
     "//ui/webui/resources/cr_components/most_visited:build_ts",
     "//ui/webui/resources/js/browser_command:build_ts",
+    "//ui/webui/resources/js/metrics_reporter:build_ts",
   ]
 
   definitions = [
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page.ts b/chrome/browser/resources/new_tab_page/new_tab_page.ts
index 58a6625..7d98f6b 100644
--- a/chrome/browser/resources/new_tab_page/new_tab_page.ts
+++ b/chrome/browser/resources/new_tab_page/new_tab_page.ts
@@ -11,6 +11,8 @@
 
 export {CrAutoImgElement} from 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 export {BrowserCommandProxy} from 'chrome://resources/js/browser_command/browser_command_proxy.js';
+export {BrowserProxyImpl} from 'chrome://resources/js/metrics_reporter/browser_proxy.js';
+export {MetricsReporterImpl} from 'chrome://resources/js/metrics_reporter/metrics_reporter.js';
 export {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 export {DomIf} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 export {AppElement, NtpElement} from './app.js';
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox.ts b/chrome/browser/resources/new_tab_page/realbox/realbox.ts
index 21d3582..b4806695 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox.ts
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox.ts
@@ -6,6 +6,7 @@
 import './realbox_icon.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {MetricsReporterImpl} from 'chrome://resources/js/metrics_reporter/metrics_reporter.js';
 import {hasKeyModifiers} from 'chrome://resources/js/util_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -201,6 +202,7 @@
   realboxLensSearchEnabled: boolean;
   singleColoredIcons: boolean;
   private charTypedTime_: number;
+  private inputAriaLive_: string;
   private isDeletingInput_: boolean;
   private lastIgnoredEnterEvent_: KeyboardEvent|null;
   private lastInput_: Input;
@@ -212,7 +214,6 @@
   private result_: AutocompleteResult|null;
   private selectedMatch_: AutocompleteMatch|null;
   private selectedMatchIndex_: number;
-  private inputAriaLive_: string;
 
   private pageHandler_: PageHandlerInterface;
   private callbackRouter_: PageCallbackRouter;
@@ -254,7 +255,7 @@
 
   private onAutocompleteResultChanged_(result: AutocompleteResult) {
     if (this.lastQueriedInput_ === null ||
-        this.lastQueriedInput_.trimLeft() !== decodeString16(result.input)) {
+        this.lastQueriedInput_.trimStart() !== decodeString16(result.input)) {
       return;  // Stale result; ignore.
     }
 
@@ -347,6 +348,19 @@
     this.charTypedTime_ =
         charTyped ? this.charTypedTime_ || window.performance.now() : 0;
 
+    // If a character has been typed, mark 'CharTyped'. Otherwise clear it. If
+    // 'CharTyped' mark already exists, there's a pending typed character for
+    // which the results have not been painted yet. In that case, keep the
+    // earlier mark.
+    const metricsReporter = MetricsReporterImpl.getInstance();
+    if (charTyped) {
+      if (!metricsReporter.hasLocalMark('CharTyped')) {
+        metricsReporter.mark('CharTyped');
+      }
+    } else {
+      metricsReporter.clearMark('CharTyped');
+    }
+
     if (inputValue.trim()) {
       // TODO(crbug.com/1149769): Rather than disabling inline autocompletion
       // when the input event is fired within a composition session, change the
@@ -389,6 +403,14 @@
       // earlier time.
       this.charTypedTime_ = this.charTypedTime_ || window.performance.now();
 
+      // If 'CharTyped' mark already exists, there's a pending typed character
+      // for which the results have not been painted yet. In that case, keep the
+      // earlier mark.
+      const metricsReporter = MetricsReporterImpl.getInstance();
+      if (!metricsReporter.hasLocalMark('CharTyped')) {
+        metricsReporter.mark('CharTyped');
+      }
+
       this.queryAutocomplete_(this.lastInput_.text);
       e.preventDefault();
     }
@@ -499,7 +521,7 @@
       const array: HTMLElement[] = [this.$.matches, this.$.input];
       if (array.includes(e.target as HTMLElement)) {
         if (this.lastQueriedInput_ !== null &&
-            this.lastQueriedInput_.trimLeft() ===
+            this.lastQueriedInput_.trimStart() ===
                 decodeString16(this.result_.input)) {
           if (this.selectedMatch_) {
             this.navigateToMatch_(this.selectedMatchIndex_, e);
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox_dropdown.ts b/chrome/browser/resources/new_tab_page/realbox/realbox_dropdown.ts
index 2c97a63..54d72dc 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox_dropdown.ts
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox_dropdown.ts
@@ -8,6 +8,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_icons.css.js';
 
+import {MetricsReporterImpl} from 'chrome://resources/js/metrics_reporter/metrics_reporter.js';
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import {DomRepeat, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -18,6 +19,9 @@
 import {RealboxBrowserProxy} from './realbox_browser_proxy.js';
 import {getTemplate} from './realbox_dropdown.html.js';
 
+const CHAR_TYPED_TO_PAINT = 'Realbox.CharTypedToRepaintLatency.ToPaint';
+const RESULT_CHANGED_TO_PAINT = 'Realbox.ResultChangedToRepaintLatency.ToPaint';
+
 export interface RealboxDropdownElement {
   $: {
     groups: DomRepeat,
@@ -160,6 +164,25 @@
       composed: true,
       detail: window.performance.now(),
     }));
+
+    const metricsReporter = MetricsReporterImpl.getInstance();
+    metricsReporter.measure('CharTyped')
+        .then(duration => {
+          metricsReporter.umaReportTime(CHAR_TYPED_TO_PAINT, duration);
+        })
+        .then(() => {
+          metricsReporter.clearMark('CharTyped');
+        })
+        .catch(() => {});  // Fail silently if 'CharTyped' is not marked.
+
+    metricsReporter.measure('ResultChanged')
+        .then(duration => {
+          metricsReporter.umaReportTime(RESULT_CHANGED_TO_PAINT, duration);
+        })
+        .then(() => {
+          metricsReporter.clearMark('ResultChanged');
+        })
+        .catch(() => {});  // Fail silently if 'ResultChanged' is not marked.
   }
 
   //============================================================================
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
index f46b7e4..98ea2bdc 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
@@ -22,9 +22,10 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {getTemplate} from './crostini_arc_adb.html.js';
 import {CrostiniBrowserProxy, CrostiniBrowserProxyImpl} from './crostini_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
index 40c5cd3..3b32c36 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
@@ -16,11 +16,12 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {ContainerInfo, GuestId} from '../guest_os/guest_os_browser_proxy.js';
 import {equalContainerId} from '../guest_os/guest_os_container_select.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {CrostiniBrowserProxy, CrostiniBrowserProxyImpl, DEFAULT_CROSTINI_GUEST_ID, DEFAULT_CROSTINI_VM} from './crostini_browser_proxy.js';
 import {getTemplate} from './crostini_export_import.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
index 5d66db4..06653443 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
@@ -33,9 +33,10 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {CrostiniBrowserProxy, CrostiniBrowserProxyImpl} from './crostini_browser_proxy.js';
 import {getTemplate} from './crostini_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts b/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
index 03a6d14..f347d081 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
@@ -25,9 +25,10 @@
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './date_time_page.html.js';
 import {TimeZoneBrowserProxy, TimeZoneBrowserProxyImpl} from './timezone_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
index b019bd3..65dae1b 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
@@ -20,9 +20,10 @@
 import {SettingsDropdownMenuElement} from '../../controls/settings_dropdown_menu.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {TimeZoneAutoDetectMethod} from './date_time_types.js';
 import {TimeZoneBrowserProxy, TimeZoneBrowserProxyImpl} from './timezone_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.ts b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
index bad2770..3fc04aa1 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
@@ -14,13 +14,14 @@
 import '../../settings_shared.css.js';
 
 import {CrSliderElement} from 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
-import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AudioDevice, AudioSystemPropertiesObserverReceiver, MuteState} from '../../mojom-webui/audio/cros_audio_config.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {getTemplate} from './audio.html.js';
 import {CrosAudioConfigInterface, getCrosAudioConfig} from './cros_audio_config.js';
@@ -28,12 +29,7 @@
 // `cros_audio_config.mojom-webui.js` once mojo updated to handle audio input.
 import {AudioSystemProperties, FakeCrosAudioConfig} from './fake_cros_audio_config.js';
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
-const SettingsAudioElementBase =
-    RouteObserverMixin(I18nMixin(PolymerElement)) as {
-      new (): PolymerElement & I18nMixinInterface & RouteObserverMixinInterface,
-    };
+const SettingsAudioElementBase = RouteObserverMixin(I18nMixin(PolymerElement));
 
 class SettingsAudioElement extends SettingsAudioElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.ts b/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
index 4b01680..bfd6d68 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
@@ -21,13 +21,14 @@
 import '../../settings_shared.css.js';
 
 import {CrLinkRowElement} from 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
-import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
-import {WebUiListenerMixin, WebUiListenerMixinInterface} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Router} from '../router.js';
 
 import {getTemplate} from './device_page.html.js';
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from './device_page_browser_proxy.js';
@@ -38,13 +39,8 @@
   };
 }
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
 const SettingsDevicePageElementBase =
-    RouteObserverMixin(I18nMixin(WebUiListenerMixin(PolymerElement))) as {
-      new (): PolymerElement & I18nMixinInterface &
-          WebUiListenerMixinInterface & RouteObserverMixinInterface,
-    };
+    RouteObserverMixin(I18nMixin(WebUiListenerMixin(PolymerElement)));
 
 class SettingsDevicePageElement extends SettingsDevicePageElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.ts b/chrome/browser/resources/settings/chromeos/device_page/display.ts
index 293758f..ea94610e 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.ts
@@ -36,10 +36,11 @@
 import {SettingsSliderElement} from '../../controls/settings_slider.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {assertExists, cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, getDisplayApi} from './device_page_browser_proxy.js';
 import {getTemplate} from './display.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts b/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
index 06b4d8c..d70eacb 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
@@ -22,10 +22,11 @@
 import {FocusConfig} from '../../focus_config.js';
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from './device_page_browser_proxy.js';
 import {getTemplate} from './keyboard.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
index 07fabaf..4fbb60a 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
@@ -20,9 +20,10 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {getTemplate} from './pointers.html.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/power.ts b/chrome/browser/resources/settings/chromeos/device_page/power.ts
index 1716cabe..d6bfd17 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/power.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/power.ts
@@ -23,10 +23,11 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {SettingChangeValue} from '../../mojom-webui/search/user_action_recorder.mojom-webui.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {BatteryStatus, DevicePageBrowserProxy, DevicePageBrowserProxyImpl, IdleBehavior, LidClosedBehavior, PowerManagementSettings, PowerSource} from './device_page_browser_proxy.js';
 import {getTemplate} from './power.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/stylus.ts b/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
index 0816efc..3ae8f0b 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
@@ -20,11 +20,12 @@
 import {microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {assertExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, NoteAppInfo, NoteAppLockScreenSupport} from './device_page_browser_proxy.js';
 import {getTemplate} from './stylus.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
index 18fab41..b92d4d6 100644
--- a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
+++ b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
@@ -23,11 +23,12 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {GoogleAssistantBrowserProxy, GoogleAssistantBrowserProxyImpl} from './google_assistant_browser_proxy.js';
 import {getTemplate} from './google_assistant_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index abfa029..f40b291 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -12,9 +12,6 @@
   is_polymer3 = true
   deps = [
     ":apn_subpage",
-    ":esim_install_error_dialog",
-    ":esim_remove_profile_dialog",
-    ":esim_rename_dialog",
     ":hotspot_subpage",
     ":hotspot_summary_item",
     ":internet_config",
@@ -169,8 +166,6 @@
 
 js_library("internet_page") {
   deps = [
-    ":esim_remove_profile_dialog",
-    ":esim_rename_dialog",
     ":internet_config",
     ":internet_detail_page",
     ":internet_known_networks_page",
@@ -318,47 +313,6 @@
   ]
 }
 
-js_library("esim_install_error_dialog") {
-  deps = [
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/cellular_setup:esim_manager_utils",
-    "//chromeos/ash/services/cellular_setup/public/mojom:mojom_webui_js",
-    "//third_party/polymer/v3_0/components-chromium/paper-spinner:paper-spinner-lite",
-  ]
-  externs_list = [
-    "//ui/webui/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
-    "//ui/webui/resources/cr_elements/cr_input/cr_input_externs.js",
-  ]
-}
-
-js_library("esim_rename_dialog") {
-  deps = [
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/cellular_setup:esim_manager_utils",
-    "//chromeos/ash/services/cellular_setup/public/mojom:mojom_webui_js",
-  ]
-  externs_list = [
-    "//ui/webui/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
-    "//ui/webui/resources/cr_elements/cr_input/cr_input_externs.js",
-  ]
-}
-
-js_library("esim_remove_profile_dialog") {
-  deps = [
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/cellular_setup:esim_manager_utils",
-    "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings/chromeos:os_route",
-    "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
-    "//chrome/browser/resources/settings/chromeos:router",
-    "//chromeos/ash/services/cellular_setup/public/mojom:mojom_webui_js",
-  ]
-  externs_list = [
-    "//ui/webui/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
-    "//ui/webui/resources/cr_elements/cr_input/cr_input_externs.js",
-  ]
-}
-
 js_library("settings_traffic_counters") {
   deps = [
     ":internet_shared_css",
@@ -377,9 +331,6 @@
 html_to_js("web_components") {
   js_files = [
     "apn_subpage.js",
-    "esim_install_error_dialog.js",
-    "esim_remove_profile_dialog.js",
-    "esim_rename_dialog.js",
     "hotspot_subpage.js",
     "hotspot_summary_item.js",
     "internet_config.js",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.js b/chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.ts
similarity index 66%
rename from chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.js
rename to chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.ts
index a365357..676ddb5 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_install_error_dialog.ts
@@ -11,26 +11,28 @@
 import 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {ESimOperationResult, ESimProfileRemote, ProfileInstallResult} from 'chrome://resources/mojo/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const EsimInstallErrorDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+import {getTemplate} from './esim_install_error_dialog.html.js';
 
-/** @polymer */
+interface EsimInstallErrorDialogElement {
+  $: {
+    installErrorDialog: CrDialogElement,
+  };
+}
+
+const EsimInstallErrorDialogElementBase = I18nMixin(PolymerElement);
+
 class EsimInstallErrorDialogElement extends EsimInstallErrorDialogElementBase {
   static get is() {
-    return 'esim-install-error-dialog';
+    return 'esim-install-error-dialog' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -38,50 +40,46 @@
       /**
        * The error code returned when profile install attempt was made in
        * networks list.
-       * @type {?ProfileInstallResult}
        */
       errorCode: {
         type: Object,
         value: null,
       },
 
-      /** @type {?ESimProfileRemote} */
       profile: {
         type: Object,
         value: null,
       },
 
-      /** @private {string} */
       confirmationCode_: {
         type: String,
         value: '',
         observer: 'onConfirmationCodeChanged_',
       },
 
-      /** @private {boolean} */
-      isInstallInProgress_: {
+      isConfirmationCodeInvalid_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {boolean} */
-      isConfirmationCodeInvalid_: {
+      isInstallInProgress_: {
         type: Boolean,
         value: false,
       },
     };
   }
 
-  /** @private */
-  onConfirmationCodeChanged_() {
+  errorCode: ProfileInstallResult|null;
+  profile: ESimProfileRemote|null;
+  private confirmationCode_: string;
+  private isConfirmationCodeInvalid_: boolean;
+  private isInstallInProgress_: boolean;
+
+  private onConfirmationCodeChanged_(): void {
     this.isConfirmationCodeInvalid_ = false;
   }
 
-  /**
-   * @param {Event} event
-   * @private
-   */
-  onDoneClicked_(event) {
+  private onDoneClicked_(): void {
     if (!this.isConfirmationCodeError_()) {
       this.$.installErrorDialog.close();
       return;
@@ -89,7 +87,7 @@
     this.isInstallInProgress_ = true;
     this.isConfirmationCodeInvalid_ = false;
 
-    this.profile.installProfile(this.confirmationCode_).then((response) => {
+    this.profile!.installProfile(this.confirmationCode_).then((response) => {
       this.isInstallInProgress_ = false;
       if (response.result === ESimOperationResult.kSuccess) {
         this.$.installErrorDialog.close();
@@ -101,32 +99,25 @@
     });
   }
 
-  /**
-   * @param {Event} event
-   * @private
-   */
-  onCancelClicked_(event) {
+  private onCancelClicked_(): void {
     this.$.installErrorDialog.close();
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  /** @private */
-  isConfirmationCodeError_() {
+  private isConfirmationCodeError_(): boolean {
     return this.errorCode === ProfileInstallResult.kErrorNeedsConfirmationCode;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isDoneButtonDisabled_() {
+  private isDoneButtonDisabled_(): boolean {
     return this.isConfirmationCodeError_() &&
         (!this.confirmationCode_ || this.isInstallInProgress_);
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [EsimInstallErrorDialogElement.is]: EsimInstallErrorDialogElement;
+  }
+}
+
 customElements.define(
     EsimInstallErrorDialogElement.is, EsimInstallErrorDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.ts
similarity index 65%
rename from chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
rename to chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.ts
index a1ba84e..61d1a25 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.ts
@@ -8,52 +8,54 @@
 
 import 'chrome://resources/ash/common/cellular_setup/cellular_setup_icons.html.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.js';
 
 import {getESimProfile} from 'chrome://resources/ash/common/cellular_setup/esim_manager_utils.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {ESimOperationResult, ESimProfileRemote} from 'chrome://resources/mojo/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-webui.js';
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../router.js';
 import {routes} from '../os_route.js';
+import {Router} from '../router.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const EsimRemoveProfileDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+import {getTemplate} from './esim_remove_profile_dialog.html.js';
 
-/** @polymer */
+interface EsimRemoveProfileDialogElement {
+  $: {
+    dialog: CrDialogElement,
+    cancel: CrButtonElement,
+  };
+}
+
+const EsimRemoveProfileDialogElementBase = I18nMixin(PolymerElement);
+
 class EsimRemoveProfileDialogElement extends
     EsimRemoveProfileDialogElementBase {
   static get is() {
-    return 'esim-remove-profile-dialog';
+    return 'esim-remove-profile-dialog' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
-      /** @type {?OncMojo.NetworkStateProperties} */
       networkState: {
         type: Object,
         value: null,
       },
 
-      /** @type {boolean} */
       showCellularDisconnectWarning: {
         type: Boolean,
         value: false,
       },
 
-      /** @type {string} */
       esimProfileName_: {
         type: String,
         value: '',
@@ -61,28 +63,30 @@
     };
   }
 
+  networkState: OncMojo.NetworkStateProperties|null;
+  showCellularDisconnectWarning: boolean;
+  private esimProfileName_: string;
+  private esimProfileRemote_: ESimProfileRemote|null;
+
   constructor() {
     super();
 
-    /** @private {?ESimProfileRemote} */
     this.esimProfileRemote_ = null;
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
 
     this.init_();
   }
 
-  /** @private */
-  async init_() {
+  private async init_(): Promise<void> {
     if (!(this.networkState &&
           this.networkState.type === NetworkType.kCellular)) {
       return;
     }
     this.esimProfileRemote_ =
-        await getESimProfile(this.networkState.typeState.cellular.iccid);
+        await getESimProfile(this.networkState.typeState.cellular!.iccid);
     // Fail gracefully if init is incomplete, see crbug/1194729.
     if (!this.esimProfileRemote_) {
       this.fireShowErrorToastEvent_();
@@ -93,33 +97,15 @@
     this.$.cancel.focus();
   }
 
-  /**
-   * Converts a mojoBase.mojom.String16 to a JavaScript String.
-   * @param {?mojoBase.mojom.String16} str
-   * @return {string}
-   * @private
-   */
-  convertString16ToJSString_(str) {
-    return str.data.map(ch => String.fromCodePoint(ch)).join('');
-  }
-
-  /**
-   * @returns {string}
-   * @private
-   */
-  getTitleString_() {
+  private getTitleString_(): string {
     if (!this.esimProfileName_) {
       return '';
     }
     return this.i18n('esimRemoveProfileDialogTitle', this.esimProfileName_);
   }
 
-  /**
-   * @param {Event} event
-   * @private
-   */
-  onRemoveProfileTap_(event) {
-    this.esimProfileRemote_.uninstallProfile().then((response) => {
+  private onRemoveProfileTap_(): void {
+    this.esimProfileRemote_!.uninstallProfile().then((response) => {
       if (response.result === ESimOperationResult.kFailure) {
         this.fireShowErrorToastEvent_();
       }
@@ -131,34 +117,19 @@
         routes.INTERNET_NETWORKS, params, /*isPopState=*/ true);
   }
 
-  /**
-   * @param {Event} event
-   * @private
-   */
-  onCancelTap_(event) {
+  private onCancelTap_(): void {
     this.$.dialog.close();
   }
 
-  /**
-   * @param {string} esimProfileName
-   * @return {string}
-   * @private
-   */
-  getRemoveBtnA11yLabel_(esimProfileName) {
+  private getRemoveBtnA11yLabel_(esimProfileName: string): string {
     return this.i18n('eSimRemoveProfileRemoveA11yLabel', esimProfileName);
   }
 
-  /**
-   * @param {string} esimProfileName
-   * @return {string}
-   * @private
-   */
-  getCancelBtnA11yLabel_(esimProfileName) {
+  private getCancelBtnA11yLabel_(esimProfileName: string): string {
     return this.i18n('eSimRemoveProfileCancelA11yLabel', esimProfileName);
   }
 
-  /** @private */
-  fireShowErrorToastEvent_() {
+  private fireShowErrorToastEvent_(): void {
     const showErrorToastEvent = new CustomEvent('show-error-toast', {
       bubbles: true,
       composed: true,
@@ -168,5 +139,11 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [EsimRemoveProfileDialogElement.is]: EsimRemoveProfileDialogElement;
+  }
+}
+
 customElements.define(
     EsimRemoveProfileDialogElement.is, EsimRemoveProfileDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.html b/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.html
index 3c38bb83..c27c5f62 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.html
@@ -100,9 +100,9 @@
             label="$i18n{eSimRenameProfileInputTitle}"
             aria-label="[[i18n('eSimRenameProfileDialogLabel')]]"
             aria-description="[[i18n('eSimRenameProfileInputA11yLabel',
-                MAX_INPUT_LENGTH)]]"
+                maxInputLength)]]"
             error-message="[[i18n('eSimRenameProfileInputA11yLabel',
-                MAX_INPUT_LENGTH)]]">
+                maxInputLength)]]">
         </cr-input>
         <div id="inputInfo"
             class$="[[getInputInfoClass_(isInputInvalid_)]]"
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.js b/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.ts
similarity index 68%
rename from chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.js
rename to chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.ts
index f3dc16f5..8158f38 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.ts
@@ -13,108 +13,109 @@
 
 import {getESimProfile} from 'chrome://resources/ash/common/cellular_setup/esim_manager_utils.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {ESimOperationResult, ESimProfileRemote} from 'chrome://resources/mojo/chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-webui.js';
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-/** @type {number} */
+import {getTemplate} from './esim_rename_dialog.html.js';
+
 const MAX_INPUT_LENGTH = 20;
 
-/** @type {number} */
 const MIN_INPUT_LENGTH = 1;
 
-/** @type {RegExp} */
 const EMOJI_REGEX_EXP =
     /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi;
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const EsimRenameDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+interface EsimRenameDialogElement {
+  $: {
+    profileRenameDialog: CrDialogElement,
+  };
+}
 
-/** @polymer */
+const EsimRenameDialogElementBase = I18nMixin(PolymerElement);
+
 class EsimRenameDialogElement extends EsimRenameDialogElementBase {
   static get is() {
-    return 'esim-rename-dialog';
+    return 'esim-rename-dialog' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
       /** Used to reference the MAX_INPUT_LENGTH constant in HTML. */
-      MAX_INPUT_LENGTH: {
+      maxInputLength: {
         type: Number,
         value: MAX_INPUT_LENGTH,
+        readonly: true,
       },
 
-      /** @type {?OncMojo.NetworkStateProperties} */
       networkState: {
         type: Object,
         value: null,
       },
 
-      /** @type {boolean} */
       showCellularDisconnectWarning: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {string} */
+      errorMessage_: {
+        type: String,
+        value: '',
+      },
+
       esimProfileName_: {
         type: String,
         value: '',
         observer: 'onEsimProfileNameChanged_',
       },
 
-      /** @private {string} */
-      errorMessage_: {
-        type: String,
-        value: '',
-      },
-
-      /** @private {boolean} */
-      isRenameInProgress_: {
+      isInputInvalid_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {boolean} */
-      isInputInvalid_: {
+      isRenameInProgress_: {
         type: Boolean,
         value: false,
       },
     };
   }
 
+  maxInputLength: number;
+  networkState: OncMojo.NetworkStateProperties|null;
+  showCellularDisconnectWarning: boolean;
+  private errorMessage_: string;
+  private esimProfileName_: string;
+  private esimProfileRemote_: ESimProfileRemote|null;
+  private isInputInvalid_: boolean;
+  private isRenameInProgress_: boolean;
+
   constructor() {
     super();
 
-    /** @private {?ESimProfileRemote} */
     this.esimProfileRemote_ = null;
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
 
     this.init_();
   }
 
-  /** @private */
-  async init_() {
+  private async init_(): Promise<void> {
     if (!(this.networkState &&
           this.networkState.type === NetworkType.kCellular)) {
       return;
     }
     this.esimProfileRemote_ =
-        await getESimProfile(this.networkState.typeState.cellular.iccid);
+        await getESimProfile(this.networkState.typeState.cellular!.iccid);
     // Fail gracefully if init is incomplete, see crbug/1194729.
     if (!this.esimProfileRemote_) {
       this.errorMessage_ = this.i18n('eSimRenameProfileDialogError');
@@ -122,24 +123,16 @@
     this.esimProfileName_ = this.networkState.name;
 
     if (!this.errorMessage_) {
-      this.shadowRoot.querySelector('#eSimprofileName').focus();
+      this.shadowRoot!.querySelector<CrInputElement>(
+                          '#eSimprofileName')!.focus();
     }
   }
 
   /**
-   * Converts a mojoBase.mojom.String16 to a JavaScript String.
-   * @param {?mojoBase.mojom.String16} str
-   * @return {string}
-   */
-  convertString16ToJSString_(str) {
-    return str.data.map(ch => String.fromCodePoint(ch)).join('');
-  }
-
-  /**
    * @param {Event} event
    * @private
    */
-  async onRenameDialogDoneTap_(event) {
+  private async onRenameDialogDoneTap_(): Promise<void> {
     if (this.errorMessage_) {
       this.$.profileRenameDialog.close();
       return;
@@ -150,18 +143,15 @@
     // The C++ layer uses std::u16string, which use 16 bit characters. JS
     // strings support either 8 or 16 bit characters, and must be converted
     // to an array of 16 bit character codes that match std::u16string.
-    const name = {data: Array.from(this.esimProfileName_, c => c.charCodeAt())};
+    const name = {
+      data: Array.from(this.esimProfileName_, c => c.charCodeAt(0)),
+    };
 
-    this.esimProfileRemote_.setProfileNickname(name).then(response => {
-      this.handleSetProfileNicknameResponse_(response.result);
-    });
+    const response = await this.esimProfileRemote_!.setProfileNickname(name);
+    this.handleSetProfileNicknameResponse_(response.result);
   }
 
-  /**
-   * @param {ESimOperationResult} result
-   * @private
-   */
-  handleSetProfileNicknameResponse_(result) {
+  private handleSetProfileNicknameResponse_(result: ESimOperationResult): void {
     this.isRenameInProgress_ = false;
     if (result === ESimOperationResult.kFailure) {
       const showErrorToastEvent = new CustomEvent('show-error-toast', {
@@ -174,11 +164,7 @@
     this.$.profileRenameDialog.close();
   }
 
-  /**
-   * @param {Event} event
-   * @private
-   */
-  onCancelTap_(event) {
+  private onCancelTap_(): void {
     this.$.profileRenameDialog.close();
   }
 
@@ -186,11 +172,8 @@
    * Observer for esimProfileName_ that sanitizes its value by removing any
    * Emojis and truncating it to MAX_INPUT_LENGTH. This method will be
    * recursively called until esimProfileName_ is fully sanitized.
-   * @param {string} newValue
-   * @param {string} oldValue
-   * @private
    */
-  onEsimProfileNameChanged_(newValue, oldValue) {
+  private onEsimProfileNameChanged_(_newValue: string, oldValue: string): void {
     if (oldValue) {
       const sanitizedOldValue = oldValue.replace(EMOJI_REGEX_EXP, '');
       // If sanitizedOldValue.length > MAX_INPUT_LENGTH, the user attempted to
@@ -209,23 +192,15 @@
     this.esimProfileName_ = sanitizedProfileName.substring(0, MAX_INPUT_LENGTH);
   }
 
-  /**
-   * @param {boolean} isInputInvalid
-   * @return {string}
-   * @private
-   */
-  getInputInfoClass_(isInputInvalid) {
+  private getInputInfoClass_(isInputInvalid: boolean): string {
     return isInputInvalid ? 'error' : '';
   }
 
   /**
    * Returns a formatted string containing the current number of characters
    * entered in the input compared to the maximum number of characters allowed.
-   * @param {string} esimProfileName
-   * @return {string}
-   * @private
    */
-  getInputCountString_(esimProfileName) {
+  private getInputCountString_(esimProfileName: string): string {
     // minimumIntegerDigits is 2 because we want to show a leading zero if
     // length is less than 10.
     return this.i18n(
@@ -235,27 +210,23 @@
         MAX_INPUT_LENGTH.toLocaleString());
   }
 
-  /**
-   * @param {boolean} isRenameInProgress
-   * @param {string} esimProfileName
-   * @return {boolean}
-   * @private
-   */
-  isDoneButtonDisabled_(isRenameInProgress, esimProfileName) {
+  private isDoneButtonDisabled_(
+      isRenameInProgress: boolean, esimProfileName: string): boolean {
     if (isRenameInProgress) {
       return true;
     }
     return esimProfileName.length < MIN_INPUT_LENGTH;
   }
 
-  /**
-   * @param {string} esimProfileName
-   * @return {string}
-   * @private
-   */
-  getDoneBtnA11yLabel_(esimProfileName) {
+  private getDoneBtnA11yLabel_(esimProfileName: string): string {
     return this.i18n('eSimRenameProfileDoneBtnA11yLabel', esimProfileName);
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [EsimRenameDialogElement.is]: EsimRenameDialogElement;
+  }
+}
+
 customElements.define(EsimRenameDialogElement.is, EsimRenameDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
index c3910777..9a0ae9bd 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
@@ -25,12 +25,13 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {getImage} from '../icon.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './kerberos_accounts.html.js';
 import {KerberosAccount, KerberosAccountsBrowserProxy, KerberosAccountsBrowserProxyImpl, KerberosErrorType} from './kerberos_accounts_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/main_page_mixin.ts b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
index a41c7487..adcd0ac 100644
--- a/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
+++ b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
@@ -5,11 +5,11 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {beforeNextRender, dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {MinimumRoutes, Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from './router.js';
-
 import {castExists} from './assert_extras.js';
 import {ensureLazyLoaded} from './ensure_lazy_loaded.js';
 import {SettingsIdleLoadElement} from './os_settings_page/settings_idle_load.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from './route_observer_mixin.js';
+import {MinimumRoutes, Route, Router} from './router.js';
 
 /**
  * A categorization of every possible Settings URL, necessary for implementing
@@ -75,7 +75,6 @@
 
 export interface MainPageMixinInterface extends RouteObserverMixinInterface {
   containsRoute(route: Route|undefined): boolean;
-  currentRouteChanged(newRoute: Route, oldRoute?: Route): void;
   querySection(section: string): HTMLElement|null;
   loadAdvancedPage(): Promise<Element>;
 }
@@ -88,8 +87,7 @@
 export const MainPageMixin = dedupingMixin(
     <T extends Constructor<PolymerElement>>(superClass: T): T&
     Constructor<MainPageMixinInterface> => {
-      const superclassBase = RouteObserverMixin(superClass) as T &
-          Constructor<RouteObserverMixinInterface>;
+      const superclassBase = RouteObserverMixin(superClass);
 
       class MainPageMixinInternal extends superclassBase implements
           MainPageMixinInterface {
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
index 8086ed7..1b734db 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
@@ -26,9 +26,10 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {AUTO_SCAN_SPEED_RANGE_MS, SwitchAccessCommand, SwitchAccessDeviceType} from './switch_access_constants.js';
 import {getTemplate} from './switch_access_subpage.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
index b85a88d..5d1ba7c8 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
@@ -21,10 +21,11 @@
 import {DomRepeat, DomRepeatEvent, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {LanguagesBrowserProxy, LanguagesBrowserProxyImpl} from '../os_languages_page/languages_browser_proxy.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {getTemplate} from './tts_subpage.html.js';
 import {TtsSubpageBrowserProxy, TtsSubpageBrowserProxyImpl} from './tts_subpage_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
index c043afe2..044d301 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
@@ -26,10 +26,11 @@
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, browserChannelToI18nId, ChannelInfo, VersionInfo} from './about_page_browser_proxy.js';
 import {getTemplate} from './detailed_build_info.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
index e54968f..c97d26bc 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
@@ -19,10 +19,11 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from './android_apps_browser_proxy.js';
 import {getTemplate} from './android_apps_subpage.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
index 5d13062..ec8a9c8a 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
@@ -16,23 +16,19 @@
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
 import {castExists} from '../../assert_extras.js';
 import {routes} from '../../os_route.js';
+import {RouteObserverMixin} from '../../route_observer_mixin.js';
+import {Route, Router} from '../../router.js';
 
 import {updateSelectedAppId} from './actions.js';
 import {getTemplate} from './app_detail_view.html.js';
 import {AppMap} from './store.js';
-import {AppManagementStoreMixin, AppManagementStoreMixinInterface} from './store_mixin.js';
+import {AppManagementStoreMixin} from './store_mixin.js';
 import {openMainPage} from './util.js';
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
 const AppManagementAppDetailViewElementBase =
-    AppManagementStoreMixin(RouteObserverMixin(PolymerElement)) as {
-      new (): PolymerElement & RouteObserverMixinInterface &
-          AppManagementStoreMixinInterface,
-    };
+    AppManagementStoreMixin(RouteObserverMixin(PolymerElement));
 
 class AppManagementAppDetailViewElement extends
     AppManagementAppDetailViewElementBase {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
index d9a2286..e2c4d94 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
@@ -13,20 +13,16 @@
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
 import {routes} from '../../os_route.js';
+import {RouteObserverMixin} from '../../route_observer_mixin.js';
+import {Route} from '../../router.js';
 
 import {getTemplate} from './main_view.html.js';
 import {AppManagementStore, AppMap} from './store.js';
-import {AppManagementStoreMixin, AppManagementStoreMixinInterface} from './store_mixin.js';
+import {AppManagementStoreMixin} from './store_mixin.js';
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
 const AppManagementMainViewElementBase =
-    AppManagementStoreMixin(RouteObserverMixin(PolymerElement)) as {
-      new (): PolymerElement & RouteObserverMixinInterface &
-          AppManagementStoreMixinInterface,
-    };
+    AppManagementStoreMixin(RouteObserverMixin(PolymerElement));
 
 class AppManagementMainViewElement extends AppManagementMainViewElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
index 63a3a0be..0ccb3d9 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
@@ -18,10 +18,11 @@
 import {App, AppNotificationsHandlerInterface, AppNotificationsObserverReceiver} from '../../../mojom-webui/os_apps_page/app_notification_handler.mojom-webui.js';
 import {SettingChangeValue} from '../../../mojom-webui/search/user_action_recorder.mojom-webui.js';
 import {Setting} from '../../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../../deep_linking_behavior.js';
 import {recordSettingChange} from '../../metrics_recorder.js';
 import {routes} from '../../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../../route_observer_mixin.js';
+import {Route} from '../../router.js';
 import {isAppInstalled} from '../os_apps_page.js';
 
 import {getTemplate} from './app_notifications_subpage.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
index 6a49308..cc35d38 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
@@ -37,9 +37,10 @@
 import {App as AppWithNotifications, AppNotificationsHandlerInterface, AppNotificationsObserverReceiver, Readiness} from '../../mojom-webui/os_apps_page/app_notification_handler.mojom-webui.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from './android_apps_browser_proxy.js';
 import {AppManagementStoreMixin, AppManagementStoreMixinInterface} from './app_management_page/store_mixin.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
index 23c18ca..1d609a5 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
@@ -16,9 +16,10 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './os_files_page.html.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
index 884bbd43..68173222 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
@@ -13,16 +13,13 @@
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './smb_shares_page.html.js';
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
-const SettingsSmbSharesPageElementBase = RouteObserverMixin(PolymerElement) as {
-  new (): PolymerElement & RouteObserverMixinInterface,
-};
+const SettingsSmbSharesPageElementBase = RouteObserverMixin(PolymerElement);
 
 class SettingsSmbSharesPageElement extends SettingsSmbSharesPageElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts
index 035e1fa2..bbc5df68 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts
@@ -27,22 +27,21 @@
 import '../../settings_vars.css.js';
 
 import {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
+import {WebUiListenerMixin} from '//resources/cr_elements/web_ui_listener_mixin.js';
 import {assert, assertNotReached} from '//resources/js/assert_ts.js';
 import {focusWithoutInk} from '//resources/js/focus_without_ink.js';
-import {WebUiListenerMixin, WebUiListenerMixinInterface} from '//resources/cr_elements/web_ui_listener_mixin.js';
 import {IronCollapseElement} from '//resources/polymer/v3_0/iron-collapse/iron-collapse.js';
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 
 import {FocusConfig} from '../../focus_config.js';
 import {loadTimeData} from '../../i18n_setup.js';
-
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
-
 import {PageStatus, StatusAction, SyncBrowserProxy, SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '../../people_page/sync_browser_proxy.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
+
 import {OsSettingsPersonalizationOptionsElement} from './os_personalization_options.js';
 import {OsSettingsSyncEncryptionOptionsElement} from './os_sync_encryption_options.js';
-
 import {getTemplate} from './os_sync_page.html.js';
 
 interface SyncRoutes {
@@ -66,10 +65,7 @@
 }
 
 const OsSettingsSyncPageElementBase =
-    RouteObserverMixin(WebUiListenerMixin(I18nMixin(PolymerElement))) as {
-      new (): PolymerElement & WebUiListenerMixinInterface &
-          I18nMixinInterface & RouteObserverMixinInterface,
-    };
+    RouteObserverMixin(WebUiListenerMixin(I18nMixin(PolymerElement)));
 
 export class OsSettingsSyncPageElement extends OsSettingsSyncPageElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
index 3f870bf..303ff1b 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
@@ -11,10 +11,11 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl} from './cups_printers_browser_proxy.js';
 import {getTemplate} from './os_printing_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
index be67782..f2d9a122 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
@@ -16,9 +16,10 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
 import {getTemplate} from './os_reset_page.html.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
index 6e759d00..7b9eb1c 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
@@ -27,9 +27,10 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './os_search_page.html.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
index 401c07a..9e571f16 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
@@ -26,10 +26,11 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './search_subpage.html.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 0d037a9..7b15c67 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -58,6 +58,9 @@
   "chromeos/internet_page/cellular_networks_list.ts",
   "chromeos/internet_page/cellular_roaming_toggle_button.ts",
   "chromeos/internet_page/cellular_setup_dialog.ts",
+  "chromeos/internet_page/esim_install_error_dialog.ts",
+  "chromeos/internet_page/esim_remove_profile_dialog.ts",
+  "chromeos/internet_page/esim_rename_dialog.ts",
   "chromeos/kerberos_page/kerberos_accounts.ts",
   "chromeos/kerberos_page/kerberos_add_account_dialog.ts",
   "chromeos/kerberos_page/kerberos_page.ts",
@@ -307,6 +310,7 @@
   "chromeos/personalization_page/personalization_hub_browser_proxy.ts",
   "chromeos/prefs_behavior.js",
   "chromeos/route_observer_behavior.js",
+  "chromeos/route_observer_mixin.ts",
   "chromeos/route_origin_behavior.js",
   "chromeos/router.js",
   "chromeos/search/combined_search_handler.ts",
@@ -338,9 +342,6 @@
 # Files that are generated by html_to_js() or other build rule.
 generated_web_component_files = [
   "chromeos/internet_page/apn_subpage.js",
-  "chromeos/internet_page/esim_install_error_dialog.js",
-  "chromeos/internet_page/esim_remove_profile_dialog.js",
-  "chromeos/internet_page/esim_rename_dialog.js",
   "chromeos/internet_page/hotspot_subpage.js",
   "chromeos/internet_page/hotspot_summary_item.js",
   "chromeos/internet_page/internet_config.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
index df9b514..742aec5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.ts
@@ -20,10 +20,11 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {assertExists} from '../assert_extras.js';
 import {OSPageVisibility} from '../os_page_visibility.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './os_settings_main.html.js';
 
@@ -38,11 +39,7 @@
   };
 }
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
-const OsSettingsMainElementBase = RouteObserverMixin(PolymerElement) as {
-  new (): PolymerElement & RouteObserverMixinInterface,
-};
+const OsSettingsMainElementBase = RouteObserverMixin(PolymerElement);
 
 class OsSettingsMainElement extends OsSettingsMainElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
index 93193be..823356aa 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
@@ -19,9 +19,10 @@
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {routes} from '../os_route.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './os_settings_menu.html.js';
 
@@ -33,11 +34,7 @@
   };
 }
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
-const OsSettingsMenuElementBase = RouteObserverMixin(PolymerElement) as {
-  new (): PolymerElement & RouteObserverMixinInterface,
-};
+const OsSettingsMenuElementBase = RouteObserverMixin(PolymerElement);
 
 class OsSettingsMenuElement extends OsSettingsMenuElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
index 2ffc8f72..28c79cb 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
@@ -22,7 +22,8 @@
 import {DomIf, FlattenedNodesObserver, microTask, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {FocusConfig} from '../../focus_config.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 import {getSettingIdParameter} from '../setting_id_param_util.js';
 
 import {getTemplate} from './os_settings_animated_pages.html.js';
@@ -34,8 +35,7 @@
   };
 }
 
-const OsSettingsAnimatedPagesElementBase = RouteObserverMixin(PolymerElement) as
-    {new (): PolymerElement & RouteObserverMixinInterface};
+const OsSettingsAnimatedPagesElementBase = RouteObserverMixin(PolymerElement);
 
 class OsSettingsAnimatedPagesElement extends
     OsSettingsAnimatedPagesElementBase {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
index 88bf000..f43e798 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
@@ -27,7 +27,8 @@
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 import {getSettingIdParameter} from '../setting_id_param_util.js';
 
 import {getTemplate} from './os_settings_subpage.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
index d9e30df..8414780 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.ts
@@ -22,22 +22,23 @@
 import '../../prefs/prefs.js';
 import '../../settings_vars.css.js';
 
-import {CrContainerShadowMixin, CrContainerShadowMixinInterface} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
+import {CrContainerShadowMixin} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
 import {CrDrawerElement} from 'chrome://resources/cr_elements/cr_drawer/cr_drawer.js';
-import {FindShortcutMixin, FindShortcutMixinInterface} from 'chrome://resources/cr_elements/find_shortcut_mixin.js';
+import {FindShortcutMixin} from 'chrome://resources/cr_elements/find_shortcut_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {listenOnce} from 'chrome://resources/js/util_ts.js';
 import {Debouncer, DomIf, microTask, PolymerElement, timeOut} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {SettingsPrefsElement} from '../../prefs/prefs.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {setGlobalScrollTarget} from '../global_scroll_target_behavior.js';
 import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSettingChange} from '../metrics_recorder.js';
 import {convertPrefToSettingMetric} from '../metrics_utils.js';
 import {OSPageVisibility, osPageVisibility} from '../os_page_visibility.js';
 import {OsToolbarElement} from '../os_toolbar/os_toolbar.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './os_settings_ui.html.js';
 
@@ -71,17 +72,12 @@
   };
 }
 
-// TODO(crbug/1315757) Remove need to typecast and intersect mixin interfaces
-// once RouteObserverMixin is converted to TS
 const OsSettingsUiElementBase =
     // RouteObserverMixin calls currentRouteChanged() in
     // connectedCallback(), so ensure other mixins/behaviors run their
     // connectedCallback() first.
     RouteObserverMixin(
-        FindShortcutMixin(CrContainerShadowMixin(PolymerElement))) as {
-      new (): PolymerElement & CrContainerShadowMixinInterface &
-          FindShortcutMixinInterface & RouteObserverMixinInterface,
-    };
+        FindShortcutMixin(CrContainerShadowMixin(PolymerElement)));
 
 class OsSettingsUiElement extends OsSettingsUiElementBase {
   static get is() {
diff --git a/chrome/browser/resources/settings/chromeos/route_observer_mixin.ts b/chrome/browser/resources/settings/chromeos/route_observer_mixin.ts
new file mode 100644
index 0000000..6a892ed8
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/route_observer_mixin.ts
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, Router} from './router.js';
+
+type Constructor<T> = new (...args: any[]) => T;
+
+export interface RouteObserverMixinInterface {
+  currentRouteChanged(newRoute: Route, oldRoute?: Route): void;
+}
+
+export const RouteObserverMixin = dedupingMixin(
+    <T extends Constructor<PolymerElement>>(superClass: T): T&
+    Constructor<RouteObserverMixinInterface> => {
+      class RouteObserverMixin extends superClass implements
+          RouteObserverMixinInterface {
+        private routerInstance_: Router;
+
+        constructor(...args: any[]) {
+          super(...args);
+
+          this.routerInstance_ = Router.getInstance();
+        }
+
+        override connectedCallback(): void {
+          super.connectedCallback();
+
+          this.routerInstance_.addObserver(this);
+
+          // Emulating Polymer data bindings, the observer is called when the
+          // element starts observing the route.
+          this.currentRouteChanged(
+              this.routerInstance_.currentRoute, undefined);
+        }
+
+        override disconnectedCallback(): void {
+          super.disconnectedCallback();
+
+          this.routerInstance_.removeObserver(this);
+        }
+
+        currentRouteChanged(_newRoute: Route, _oldRoute?: Route): void {
+          assertNotReached('Element must implement currentRouteChanged().');
+        }
+      }
+
+      return RouteObserverMixin;
+    });
diff --git a/chrome/browser/resources/settings/chromeos/router.js b/chrome/browser/resources/settings/chromeos/router.js
index c594955..4c7b806 100644
--- a/chrome/browser/resources/settings/chromeos/router.js
+++ b/chrome/browser/resources/settings/chromeos/router.js
@@ -4,9 +4,8 @@
 
 import '../i18n_setup.js';
 
-import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
+import {assert} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {dedupingMixin} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 /**
  * @typedef {{
@@ -399,52 +398,3 @@
     this.currentQueryParameters_ = new URLSearchParams();
   }
 }
-
-/**
- * @polymer
- * @mixinFunction
- */
-export const RouteObserverMixin = dedupingMixin(superClass => {
-  /**
-   * @polymer
-   * @mixinClass
-   */
-  class RouteObserverMixin extends superClass {
-    /** @override */
-    connectedCallback() {
-      super.connectedCallback();
-
-      routerInstance.addObserver(this);
-
-      // Emulating Polymer data bindings, the observer is called when the
-      // element starts observing the route.
-      this.currentRouteChanged(routerInstance.currentRoute, undefined);
-    }
-
-    /** @override */
-    disconnectedCallback() {
-      super.disconnectedCallback();
-
-      routerInstance.removeObserver(this);
-    }
-
-    /**
-     * @param {!Route} newRoute
-     * @param {!Route=} oldRoute
-     */
-    currentRouteChanged(newRoute, oldRoute) {
-      assertNotReached();
-    }
-  }
-
-  return /** @type {?} */ (RouteObserverMixin);
-});
-
-/** @interface */
-export class RouteObserverMixinInterface {
-  /**
-   * @param {!Route} newRoute
-   * @param {!Route=} oldRoute
-   */
-  currentRouteChanged(newRoute, oldRoute) {}
-}
diff --git a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
index 49168664..f84ef8b6 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
+++ b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
@@ -11,6 +11,9 @@
 
   static_files = [ "customize_chrome.html" ]
 
+  extra_grdp_deps = [ "icons:build_grdp" ]
+  extra_grdp_files = [ "$target_gen_dir/icons/resources.grdp" ]
+
   # Files holding a Polymer element definition and have an equivalent .html file.
   web_component_files = [
     "app.ts",
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
new file mode 100644
index 0000000..c6f0ebfd
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//ui/webui/resources/tools/generate_grd.gni")
+
+assert(!is_android)
+
+generate_grd("build_grdp") {
+  grd_prefix = "side_panel_customize_chrome"
+  out_grd = "$target_gen_dir/resources.grdp"
+  input_files = [ "mini_new_tab_page.svg" ]
+  input_files_base_dir = rebase_path(".", "//")
+  resource_path_prefix = "icons"
+}
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/mini_new_tab_page.svg b/chrome/browser/resources/side_panel/customize_chrome/icons/mini_new_tab_page.svg
new file mode 100644
index 0000000..8916d4a
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/mini_new_tab_page.svg
@@ -0,0 +1 @@
+<svg width="256" height="130" viewBox="0 0 256 130" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="256" height="130" rx="8" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M110.164 50.53c-3.181 0-5.854-2.588-5.854-5.765 0-3.176 2.673-5.765 5.854-5.765 1.761 0 3.014.687 3.956 1.592l-1.11 1.108c-.676-.632-1.593-1.127-2.846-1.127-2.326 0-4.143 1.87-4.143 4.192s1.817 4.193 4.143 4.193c1.507 0 2.363-.608 2.915-1.152.452-.452.744-1.096.855-1.982h-3.77v-1.579h5.308c.056.279.087.62.087.985 0 1.183-.322 2.644-1.364 3.685-1.017 1.052-2.307 1.616-4.031 1.616" fill="#4285F4"/><path fill-rule="evenodd" clip-rule="evenodd" d="M122.246 46.884c0-1.347-.958-2.265-2.064-2.265s-2.064.924-2.064 2.265c0 1.33.958 2.265 2.064 2.265s2.064-.93 2.064-2.265m1.61 0c0 2.153-1.647 3.737-3.674 3.737-2.026 0-3.673-1.584-3.673-3.737 0-2.165 1.647-3.737 3.673-3.737 2.027 0 3.674 1.572 3.674 3.737" fill="#E94235"/><path fill-rule="evenodd" clip-rule="evenodd" d="M130.43 46.884c0-1.347-.957-2.265-2.063-2.265-1.107 0-2.064.924-2.064 2.265 0 1.33.957 2.265 2.064 2.265 1.106.007 2.063-.93 2.063-2.265m1.603 0c0 2.153-1.647 3.737-3.673 3.737-2.02 0-3.673-1.584-3.673-3.737 0-2.165 1.647-3.737 3.673-3.737s3.673 1.572 3.673 3.737" fill="#FABB05"/><path fill-rule="evenodd" clip-rule="evenodd" d="M138.507 46.895c0-1.312-.868-2.276-1.969-2.276-1.12 0-2.055.958-2.055 2.276 0 1.299.935 2.244 2.055 2.244 1.101 0 1.969-.945 1.969-2.244Zm1.427-3.519v6.689c0 2.754-1.606 3.879-3.507 3.879-1.79 0-2.86-1.212-3.266-2.2l1.409-.591c.252.61.867 1.324 1.857 1.324 1.219 0 1.969-.765 1.969-2.188v-.535h-.055c-.363.455-1.065.846-1.945.846-1.845 0-3.531-1.623-3.531-3.712 0-2.1 1.692-3.741 3.531-3.741.88 0 1.582.397 1.945.832h.055v-.602h1.538Z" fill="#4285F4"/><path fill-rule="evenodd" clip-rule="evenodd" d="M141.321 50.345h1.663V39.409h-1.663v10.936Z" fill="#34A853"/><path fill-rule="evenodd" clip-rule="evenodd" d="m145.382 46.766 3.319-1.386c-.185-.468-.73-.792-1.381-.792-.83 0-1.981.736-1.938 2.178m3.895 1.347 1.263.849c-.409.611-1.393 1.66-3.096 1.66-2.112 0-3.628-1.642-3.628-3.738 0-2.221 1.535-3.737 3.448-3.737 1.926 0 2.867 1.54 3.177 2.377l.167.424-4.959 2.072c.377.748.971 1.135 1.801 1.135.831 0 1.406-.418 1.827-1.042" fill="#E94235"/><path fill-rule="evenodd" clip-rule="evenodd" d="M82 62.076c0-1.974 1.6-3.575 3.575-3.575h83.85a3.575 3.575 0 0 1 0 7.15h-83.85A3.575 3.575 0 0 1 82 62.076Zm9.102 20.476a3.9 3.9 0 1 0 0-7.8 3.9 3.9 0 0 0 0 7.8Zm40.297-3.9a3.9 3.9 0 1 1-7.8 0 3.9 3.9 0 0 1 7.8 0Zm-22.1 3.9a3.9 3.9 0 1 0 0-7.8 3.9 3.9 0 0 0 0 7.8Zm40.298-3.9a3.9 3.9 0 1 1-7.8 0 3.9 3.9 0 0 1 7.8 0Zm14.3 3.9a3.9 3.9 0 1 0 0-7.8 3.9 3.9 0 0 0 0 7.8Zm-68.895 16.9a3.9 3.9 0 1 1-7.8 0 3.9 3.9 0 0 1 7.8 0Zm32.497 3.9a3.9 3.9 0 1 0 0-7.8 3.9 3.9 0 0 0 0 7.8Zm-14.3-3.9a3.9 3.9 0 1 1-7.8 0 3.9 3.9 0 0 1 7.8 0Zm32.498 3.9a3.9 3.9 0 1 0 0-7.8 3.9 3.9 0 0 0 0 7.8Zm22.1-3.9a3.9 3.9 0 1 1-7.8 0 3.9 3.9 0 0 1 7.8 0Z" fill="#E8EAED"/><path stroke="#000" stroke-opacity=".14" stroke-width=".3" d="M0 25.85h256"/><rect width="256" height="23" rx="8" fill="#DADCE0"/><path fill="#fff" d="M0 12.67h255.97v10.905H0z"/><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="15" width="5" height="6"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.777 19.598 5.45 18.27h2.802a.295.295 0 0 0 0-.59H5.449l1.329-1.328-.026-.025a.294.294 0 0 0-.27-.415.293.293 0 0 0-.12.026l-.025-.026L4.42 17.83l-.021.026a.294.294 0 0 0 0 .24l.02.026 1.918 1.917.026-.025a.295.295 0 0 0 .389-.39l.026-.025Z" fill="#fff"/></mask><g mask="url(#a)"><path fill="#BDC1C6" d="M4.125 15.617H8.84v4.715H4.125z"/></g><mask id="b" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="13" y="15" width="5" height="6"><path fill-rule="evenodd" clip-rule="evenodd" d="m15.62 19.598 1.329-1.328h-2.802a.295.295 0 0 1 0-.59h2.802l-1.328-1.328.025-.025a.295.295 0 0 1 .39-.39l.025-.025 1.918 1.917-.026.026a.294.294 0 0 1 0 .24l.026.026-1.918 1.917-.026-.025a.294.294 0 0 1-.389-.39l-.025-.025Z" fill="#fff"/></mask><g mask="url(#b)"><path fill="#BDC1C6" d="M13.558 15.617h4.715v4.715h-4.715z"/></g><path fill-rule="evenodd" clip-rule="evenodd" d="M27.407 15.912v1.768H25.64l.77-.776c-.28-.276-.639-.402-1.065-.402-.858 0-1.473.619-1.473 1.473 0 .855.615 1.474 1.473 1.474.677 0 1.203-.35 1.4-.934l.005-.012c.048-.103.206-.18.292-.18.114 0 .305.067.305.243a.617.617 0 0 1-.038.166c-.296.755-1.085 1.306-1.955 1.306a2.068 2.068 0 0 1-2.072-2.063c0-1.14.928-2.063 2.072-2.063.569 0 1.083.228 1.457.597l.597-.597Z" fill="#000"/><mask id="c" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="23" y="15" width="5" height="6"><path fill-rule="evenodd" clip-rule="evenodd" d="M27.407 15.912v1.768H25.64l.77-.776c-.28-.276-.639-.402-1.065-.402-.858 0-1.473.619-1.473 1.473 0 .855.615 1.474 1.473 1.474.677 0 1.203-.35 1.4-.934l.005-.012c.048-.103.206-.18.292-.18.114 0 .305.067.305.243a.617.617 0 0 1-.038.166c-.296.755-1.085 1.306-1.955 1.306a2.068 2.068 0 0 1-2.072-2.063c0-1.14.928-2.063 2.072-2.063.569 0 1.083.228 1.457.597l.597-.597Z" fill="#fff"/></mask><g mask="url(#c)"><path fill="#5F6368" d="M22.987 15.617h4.715v4.715h-4.715z"/></g><path fill-rule="evenodd" clip-rule="evenodd" d="M35 15a3 3 0 1 0 0 6h195a3 3 0 1 0 0-6H35Z" fill="#F1F3F4"/><circle cx="239.76" cy="17.975" r="3.242" fill="#1A73E8"/><g clip-path="url(#d)"><circle cx="239.76" cy="17.974" r="3.242" fill="#C4C4C4"/></g><path fill-rule="evenodd" clip-rule="evenodd" d="M248.896 16.647a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Zm0 1.473a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Zm0 1.474a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Z" fill="#000"/><mask id="e" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="248" y="16" width="2" height="5"><path fill-rule="evenodd" clip-rule="evenodd" d="M248.896 16.647a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Zm0 1.473a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Zm0 1.474a.441.441 0 1 1 .442.442.441.441 0 0 1-.442-.442Z" fill="#fff"/></mask><g mask="url(#e)"><path fill="#9AA0A6" d="M246.833 15.615h4.715v4.715h-4.715z"/></g><path fill="#202124" fill-opacity=".28" d="M81.634 4.714h.295v5.894h-.295z"/><path d="M11.2 4.989a2.358 2.358 0 0 1 2.357-2.358h65.98a2.358 2.358 0 0 1 2.357 2.358v7.663H11.2V4.989Z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M11.2 10.293v2.358H8.546v-.018a2.358 2.358 0 0 0 2.652-2.34Zm70.694 0v2.358h2.653v-.018a2.358 2.358 0 0 1-2.652-2.34Z" fill="#DADCE0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M75.953 6.482a.21.21 0 0 1 .172.09l.796.796.793-.79a.21.21 0 1 1 .32.271l.001.003-.818.81.79.791a.21.21 0 1 1-.29.297l-.796-.794-.791.784a.21.21 0 1 1-.316-.27l.813-.809-.804-.803a.21.21 0 0 1 .13-.376Z" fill="#5F6368"/><g clip-path="url(#f)"><path d="M17.13 8.714V6.782h.027l1.352 1.932h.302V6.222h-.307v1.94h-.028l-1.352-1.94h-.302v2.492h.307Zm3.512-.482c-.078.164-.24.252-.48.252-.316 0-.522-.233-.537-.6v-.015h1.338v-.114c0-.578-.306-.935-.808-.935-.512 0-.84.38-.84.965 0 .589.323.962.84.962.407 0 .697-.197.784-.515h-.297Zm-.49-1.15c.294 0 .491.218.498.547h-1.025c.022-.33.23-.547.526-.547Zm3.619-.23h-.3l-.365 1.489h-.028l-.416-1.489h-.285l-.416 1.489h-.028l-.366-1.489h-.3l.521 1.862h.3l.415-1.44h.028l.416 1.44h.302l.522-1.862Zm1.52-.481v.481h-.301v.249h.3v1.13c0 .355.154.497.537.497a.995.995 0 0 0 .175-.018v-.25a1.253 1.253 0 0 1-.139.007c-.193 0-.276-.093-.276-.313V7.101h.415v-.249h-.415v-.481h-.297Zm1.678 2.376a.643.643 0 0 0 .573-.308h.028v.275h.283V7.439c0-.386-.254-.62-.708-.62-.397 0-.69.197-.73.496h.3c.041-.147.197-.231.42-.231.277 0 .42.126.42.355v.17l-.536.033c-.434.025-.679.217-.679.55 0 .34.268.555.629.555Zm.055-.261c-.216 0-.377-.11-.377-.3 0-.187.125-.285.408-.304l.5-.033v.17c0 .267-.226.467-.531.467Zm2.303.26c.472 0 .781-.381.781-.963 0-.585-.307-.963-.78-.963-.256 0-.482.126-.584.326h-.028V6.113h-.297v2.6h.283v-.296h.028c.117.207.338.33.598.33Zm-.069-1.659c.34 0 .543.261.543.696 0 .435-.202.696-.543.696-.338 0-.549-.266-.549-.696 0-.43.21-.696.55-.696Z" fill="#202124"/></g><path fill-rule="evenodd" clip-rule="evenodd" d="M88.358 5.59c.162 0 .294.131.294.294v1.179h1.18a.295.295 0 0 1 0 .59h-1.18V8.83a.295.295 0 1 1-.589 0V7.652h-1.179a.295.295 0 1 1 0-.589h1.179V5.884c0-.163.132-.295.295-.295Z" fill="#000"/><mask id="g" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="86" y="5" width="5" height="5"><path fill-rule="evenodd" clip-rule="evenodd" d="M88.358 5.59c.162 0 .294.131.294.294v1.179h1.18a.295.295 0 0 1 0 .59h-1.18V8.83a.295.295 0 1 1-.589 0V7.652h-1.179a.295.295 0 1 1 0-.589h1.179V5.884c0-.163.132-.295.295-.295Z" fill="#fff"/></mask><g mask="url(#g)"><path fill="#5F6368" d="M90.716 9.716H86V5h4.715z"/></g><path d="m249.487 8.529-1.319-1.316.279-.28 1.04 1.042 1.039-1.041.279.279-1.318 1.316Z" fill="#5F6368"/><defs><clipPath id="d"><rect x="236.519" y="14.733" width="6.484" height="6.484" rx="3.242" fill="#fff"/></clipPath><clipPath id="f"><path fill="#fff" transform="translate(16.504 4.714)" d="M0 0h51.869v5.894H0z"/></clipPath></defs></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
index 81297530..804b196 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
@@ -20,15 +20,23 @@
     object-fit: fill;
   }
 
+  #classicChromeImageContainer {
+    align-items: center;
+    background-color: var(--customize-chrome-color-background-color);
+    display: flex;
+    justify-content: center;
+  }
+
+  #miniNewTabPage {
+    height: 130px;
+    width: 256px;
+  }
+
   #themeTitle {
     margin-bottom: 8px;
     overflow: hidden;
     white-space: nowrap;
   }
-
-  #classicChrome {
-    background-color: var(--customize-chrome-color-background-color);
-  }
 </style>
 <div id="themeSnapshot">
   <template is="dom-if" if="[[showThemeSnapshot_]]">
@@ -38,8 +46,8 @@
     <label id="themeTitle">[[theme_.backgroundImage.title]]</label>
   </template>
   <template is="dom-if" if="[[!showThemeSnapshot_]]">
-    <div class="image" id="classicChrome">
-      <!--TODO(crbug/1384227): Add classic chrome image. -->
+    <div class="image" id="classicChromeImageContainer">
+      <img id="miniNewTabPage" src="icons/mini_new_tab_page.svg"></img>
     </div>
     <label id="themeTitle">$i18n{classicChrome}</label>
   </template>
diff --git a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.h b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.h
index 4f41cd2b..f594257 100644
--- a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.h
+++ b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.h
@@ -19,8 +19,8 @@
   ~ChromeStartModel() override = default;
 
   // Disallow copy/assign.
-  ChromeStartModel(ChromeStartModel&) = delete;
-  ChromeStartModel& operator=(ChromeStartModel&) = delete;
+  ChromeStartModel(const ChromeStartModel&) = delete;
+  ChromeStartModel& operator=(const ChromeStartModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.h b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.h
index 9f0ef0a3..acab5fff 100644
--- a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.h
+++ b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.h
@@ -19,8 +19,8 @@
   ~ChromeStartModelV2() override = default;
 
   // Disallow copy/assign.
-  ChromeStartModelV2(ChromeStartModelV2&) = delete;
-  ChromeStartModelV2& operator=(ChromeStartModelV2&) = delete;
+  ChromeStartModelV2(const ChromeStartModelV2&) = delete;
+  ChromeStartModelV2& operator=(const ChromeStartModelV2&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
index 9398046..98b8307 100644
--- a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
+++ b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
@@ -29,8 +29,8 @@
   explicit UkmDataManagerTestUtils(ukm::TestUkmRecorder* ukm_recorder);
   ~UkmDataManagerTestUtils();
 
-  UkmDataManagerTestUtils(UkmDataManagerTestUtils&) = delete;
-  UkmDataManagerTestUtils& operator=(UkmDataManagerTestUtils&) = delete;
+  UkmDataManagerTestUtils(const UkmDataManagerTestUtils&) = delete;
+  UkmDataManagerTestUtils& operator=(const UkmDataManagerTestUtils&) = delete;
 
   // Must be called before the first profile initialization, sets up default
   // model overrides for the given `default_overrides`
diff --git a/chrome/browser/segmentation_platform/ukm_database_client.h b/chrome/browser/segmentation_platform/ukm_database_client.h
index 5556866..2f0868c 100644
--- a/chrome/browser/segmentation_platform/ukm_database_client.h
+++ b/chrome/browser/segmentation_platform/ukm_database_client.h
@@ -24,8 +24,8 @@
  public:
   static UkmDatabaseClient& GetInstance();
 
-  UkmDatabaseClient(UkmDatabaseClient&) = delete;
-  UkmDatabaseClient& operator=(UkmDatabaseClient&) = delete;
+  UkmDatabaseClient(const UkmDatabaseClient&) = delete;
+  UkmDatabaseClient& operator=(const UkmDatabaseClient&) = delete;
 
   void PreProfileInit();
 
diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
index 0f42e093..8342a4a 100644
--- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
+++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
@@ -67,41 +67,41 @@
   return false;
 }
 
-inline ash::SpeechRecognitionAvailability InstallationErrorToAvailability(
+inline ash::OnDeviceRecognitionAvailability InstallationErrorToAvailability(
     speech::SodaInstaller::ErrorCode error_code) {
   switch (error_code) {
     case speech::SodaInstaller::ErrorCode::kUnspecifiedError:
-      return ash::SpeechRecognitionAvailability::
+      return ash::OnDeviceRecognitionAvailability::
           kSodaInstallationErrorUnspecified;
     case speech::SodaInstaller::ErrorCode::kNeedsReboot:
-      return ash::SpeechRecognitionAvailability::
+      return ash::OnDeviceRecognitionAvailability::
           kSodaInstallationErrorNeedsReboot;
   }
 }
 
 }  // namespace
 
-ash::SpeechRecognitionAvailability
+ash::OnDeviceRecognitionAvailability
 SpeechRecognitionRecognizerClientImpl::GetOnDeviceSpeechRecognitionAvailability(
     const std::string& language) {
   if (!base::FeatureList::IsEnabled(
           ash::features::kOnDeviceSpeechRecognition)) {
-    return ash::SpeechRecognitionAvailability::kSodaNotAvailable;
+    return ash::OnDeviceRecognitionAvailability::kSodaNotAvailable;
   }
 
   const auto language_code = speech::GetLanguageCode(language);
   speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
 
   if (soda_installer->IsSodaInstalled(language_code))
-    return ash::SpeechRecognitionAvailability::kSodaAvailable;
+    return ash::OnDeviceRecognitionAvailability::kAvailable;
 
   if (!IsLanguageSupported(soda_installer, language_code))
-    return ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable;
+    return ash::OnDeviceRecognitionAvailability::kUserLanguageNotAvailable;
 
   // Maybe SODA is currently installing.
   if (soda_installer->IsSodaDownloading(language_code) ||
       soda_installer->IsSodaDownloading(speech::LanguageCode::kNone)) {
-    return ash::SpeechRecognitionAvailability::kSodaInstalling;
+    return ash::OnDeviceRecognitionAvailability::kSodaInstalling;
   }
 
   // It is possible that there was some installation issues for SODA which we
@@ -116,14 +116,14 @@
   if (language_error_code)
     return InstallationErrorToAvailability(language_error_code.value());
 
-  return ash::SpeechRecognitionAvailability::kSodaNotInstalled;
+  return ash::OnDeviceRecognitionAvailability::kSodaNotInstalled;
 }
 
-ash::SpeechRecognitionAvailability
+ash::ServerBasedRecognitionAvailability
 SpeechRecognitionRecognizerClientImpl::GetServerBasedRecognitionAvailability(
     const std::string& language) {
   if (!ash::features::IsInternalServerSideSpeechRecognitionEnabled()) {
-    return ash::SpeechRecognitionAvailability::
+    return ash::ServerBasedRecognitionAvailability::
         kServerBasedRecognitionNotAvailable;
   }
 
@@ -143,10 +143,10 @@
 
   if (kSupportedLocales.contains(base::ToLowerASCII(language)) ||
       kDefaultLanguages.contains(base::ToLowerASCII(language))) {
-    return ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable;
+    return ash::ServerBasedRecognitionAvailability::kAvailable;
   }
 
-  return ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable;
+  return ash::ServerBasedRecognitionAvailability::kUserLanguageNotAvailable;
 }
 
 SpeechRecognitionRecognizerClientImpl::SpeechRecognitionRecognizerClientImpl(
diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl.h b/chrome/browser/speech/speech_recognition_recognizer_client_impl.h
index 335e62e9..865ad4d 100644
--- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.h
+++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.h
@@ -32,12 +32,12 @@
  public:
   // Returns the availability of on-device speech recognition for the given
   // language (BCP-47 format, e.g. "en-US").
-  static ash::SpeechRecognitionAvailability
+  static ash::OnDeviceRecognitionAvailability
   GetOnDeviceSpeechRecognitionAvailability(const std::string& language);
 
   // Returns the availability of server-based speech recognition for the given
   // language.
-  static ash::SpeechRecognitionAvailability
+  static ash::ServerBasedRecognitionAvailability
   GetServerBasedRecognitionAvailability(const std::string& language);
 
   SpeechRecognitionRecognizerClientImpl(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 8820aaa8..0430eee 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2029,8 +2029,8 @@
       "../ash/app_list/search/arc/recommend_apps_fetcher_impl.h",
       "../ash/app_list/search/assistant_text_search_provider.cc",
       "../ash/app_list/search/assistant_text_search_provider.h",
-      "../ash/app_list/search/burnin_controller.cc",
-      "../ash/app_list/search/burnin_controller.h",
+      "../ash/app_list/search/burn_in_controller.cc",
+      "../ash/app_list/search/burn_in_controller.h",
       "../ash/app_list/search/chrome_search_result.cc",
       "../ash/app_list/search/chrome_search_result.h",
       "../ash/app_list/search/common/icon_constants.cc",
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
index d8ca6740..8cd4282 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
@@ -100,10 +100,9 @@
     UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
         int download_progress) {}
 
-void FakeAccessibilityController::
-    ShowSpeechRecognitionDownloadNotificationForDictation(
-        bool succeeded,
-        const std::u16string& display_language) {}
+void FakeAccessibilityController::ShowNotificationForDictation(
+    ash::DictationNotificationType type,
+    const std::u16string& display_language) {}
 
 void FakeAccessibilityController::UpdateDictationBubble(
     bool visible,
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
index 8347af2..dfaf754a 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
@@ -64,8 +64,8 @@
   void DisableSwitchAccessDisableConfirmationDialogTesting() override;
   void UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
       int download_progress) override;
-  void ShowSpeechRecognitionDownloadNotificationForDictation(
-      bool succeeded,
+  void ShowNotificationForDictation(
+      ash::DictationNotificationType type,
       const std::u16string& display_language) override;
   void UpdateDictationBubble(
       bool visible,
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.cc b/chrome/browser/ui/ash/projector/projector_client_impl.cc
index 11d7722..47eb904 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.cc
@@ -88,38 +88,29 @@
 // based speech recognition.
 ash::SpeechRecognitionAvailability
 ProjectorClientImpl::GetSpeechRecognitionAvailability() const {
+  ash::SpeechRecognitionAvailability availability;
+  availability.use_on_device = true;
   const auto& locale = GetLocale();
-  if (ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev()) {
-    return SpeechRecognitionRecognizerClientImpl::
-        GetServerBasedRecognitionAvailability(locale);
-  }
-
-  const auto on_device_availability = SpeechRecognitionRecognizerClientImpl::
+  availability.on_device_availability = SpeechRecognitionRecognizerClientImpl::
       GetOnDeviceSpeechRecognitionAvailability(locale);
-  if (on_device_availability ==
-      ash::SpeechRecognitionAvailability::kSodaAvailable) {
-    return on_device_availability;
+  availability.server_based_availability =
+      SpeechRecognitionRecognizerClientImpl::
+          GetServerBasedRecognitionAvailability(locale);
+
+  if (ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev() ||
+      (availability.on_device_availability !=
+           ash::OnDeviceRecognitionAvailability::kAvailable &&
+       availability.server_based_availability ==
+           ash::ServerBasedRecognitionAvailability::kAvailable)) {
+    availability.use_on_device = false;
   }
 
-  const auto server_based_availability = SpeechRecognitionRecognizerClientImpl::
-      GetServerBasedRecognitionAvailability(locale);
-
-  if (server_based_availability ==
-      ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable) {
-    return server_based_availability;
-  }
-
-  // TODO(b/245613717): Add a kSpeechRecognitionNotSupported message.
-  return on_device_availability;
+  return availability;
 }
 
 void ProjectorClientImpl::StartSpeechRecognition() {
   const auto availability = GetSpeechRecognitionAvailability();
-  DCHECK(ash::ProjectorController::IsRecognitionAvailable(availability));
-  const bool should_use_server_based =
-      availability ==
-      ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable;
-
+  DCHECK(availability.IsAvailable());
   DCHECK_EQ(speech_recognizer_.get(), nullptr);
   recognizer_status_ = SPEECH_RECOGNIZER_OFF;
   speech_recognizer_ = std::make_unique<SpeechRecognitionRecognizerClientImpl>(
@@ -127,7 +118,7 @@
       media::mojom::SpeechRecognitionOptions::New(
           media::mojom::SpeechRecognitionMode::kCaption,
           /*enable_formatting=*/true, GetLocale(),
-          /*is_server_based=*/should_use_server_based,
+          /*is_server_based=*/!availability.use_on_device,
           media::mojom::RecognizerClientType::kProjector));
 }
 
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
index 38a8846d..d8750ec 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
@@ -207,6 +207,17 @@
 const char kFrench[] = "fr";
 const char kUnsupportedLanguage[] = "am";
 
+bool IsEqualAvailability(const SpeechRecognitionAvailability& first,
+                         const SpeechRecognitionAvailability& second) {
+  if (first.use_on_device != second.use_on_device)
+    return false;
+
+  if (first.use_on_device)
+    return first.on_device_availability == second.on_device_availability;
+
+  return first.server_based_availability == second.server_based_availability;
+}
+
 }  // namespace
 
 TEST_P(ProjectorClientImplUnitTest, SpeechRecognitionAvailability) {
@@ -216,28 +227,53 @@
       features::IsInternalServerSideSpeechRecognitionEnabled();
 
   SetLocale(kFrench);
+
+  SpeechRecognitionAvailability availability;
+  availability.use_on_device = false;
+  availability.server_based_availability =
+      ash::ServerBasedRecognitionAvailability::kAvailable;
   if (server_based_available) {
-    EXPECT_EQ(
-        projector_client_->GetSpeechRecognitionAvailability(),
-        ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable);
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
   } else {
-    EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
-              ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable);
+    availability.use_on_device = true;
+    availability.on_device_availability =
+        ash::OnDeviceRecognitionAvailability::kUserLanguageNotAvailable;
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
   }
 
   SetLocale(kEnglishUS);
   if (force_enable_server_based && server_based_available) {
-    EXPECT_EQ(
-        projector_client_->GetSpeechRecognitionAvailability(),
-        ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable);
+    availability.use_on_device = false;
+    availability.server_based_availability =
+        ash::ServerBasedRecognitionAvailability::kAvailable;
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
   } else {
-    EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
-              ash::SpeechRecognitionAvailability::kSodaAvailable);
+    availability.use_on_device = true;
+    availability.on_device_availability =
+        ash::OnDeviceRecognitionAvailability::kAvailable;
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
   }
 
   SetLocale(kUnsupportedLanguage);
-  EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
-            ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable);
+
+  if (force_enable_server_based) {
+    availability.use_on_device = false;
+    availability.server_based_availability =
+        ash::ServerBasedRecognitionAvailability::kUserLanguageNotAvailable;
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
+  } else {
+    availability.use_on_device = true;
+    availability.on_device_availability =
+        ash::OnDeviceRecognitionAvailability::kUserLanguageNotAvailable;
+    SetLocale(kUnsupportedLanguage);
+    EXPECT_TRUE(IsEqualAvailability(
+        projector_client_->GetSpeechRecognitionAvailability(), availability));
+  }
 }
 
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
index c791583..69a627b 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
@@ -95,7 +95,7 @@
 
   if (SpeechRecognitionRecognizerClientImpl::
           GetServerBasedRecognitionAvailability(current_locale) !=
-      ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable) {
+      ash::ServerBasedRecognitionAvailability::kAvailable) {
     app_client_->OnSodaInstallError();
   }
 }
diff --git a/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc b/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
index a4216a80..109d845 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
@@ -89,9 +89,12 @@
     ON_CALL(*mock_client_, GetSpeechRecognitionAvailability)
         .WillByDefault(
             testing::Invoke([&]() -> ash::SpeechRecognitionAvailability {
-              return SpeechRecognitionRecognizerClientImpl::
-                  GetOnDeviceSpeechRecognitionAvailability(
-                      g_browser_process->GetApplicationLocale());
+              SpeechRecognitionAvailability availability;
+              availability.on_device_availability =
+                  SpeechRecognitionRecognizerClientImpl::
+                      GetOnDeviceSpeechRecognitionAvailability(
+                          g_browser_process->GetApplicationLocale());
+              return availability;
             }));
 
     projector_controller().SetClient(mock_client_.get());
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
index 05bd3b5..61e8782 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
@@ -39,7 +39,6 @@
 #include "chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/app_constants/constants.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "content/public/browser/context_menu_params.h"
 #include "extensions/browser/extension_prefs.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -516,15 +515,8 @@
       apps::WindowMode user_window_mode =
           ConvertLaunchTypeCommandToWindowMode(command_id);
       if (user_window_mode != apps::WindowMode::kUnknown) {
-        if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
-          apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
-              ->SetWindowMode(item().id.app_id, user_window_mode);
-        } else {
-          apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
-              ->SetWindowMode(
-                  item().id.app_id,
-                  apps::ConvertWindowModeToMojomWindowMode(user_window_mode));
-        }
+        apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
+            ->SetWindowMode(item().id.app_id, user_window_mode);
       }
       return;
     }
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
index be9f329a..22a5a79 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
@@ -2429,7 +2429,7 @@
                             extensions::LAUNCH_TYPE_WINDOW);
   WebAppProvider* provider = WebAppProvider::GetForTest(browser()->profile());
   DCHECK(provider);
-  provider->sync_bridge().SetAppUserDisplayMode(
+  provider->sync_bridge_unsafe().SetAppUserDisplayMode(
       web_app_id, web_app::UserDisplayMode::kStandalone,
       /*is_user_action=*/false);
 
@@ -2579,7 +2579,7 @@
   web_app::WebAppProvider* provider =
       web_app::WebAppProvider::GetForTest(profile());
   web_app::test::AddInstallUrlData(
-      profile()->GetPrefs(), &provider->sync_bridge(), app_id, app_url,
+      profile()->GetPrefs(), &provider->sync_bridge_unsafe(), app_id, app_url,
       web_app::ExternalInstallSource::kExternalPolicy);
   provider->install_manager().NotifyWebAppInstalledWithOsHooks(app_id);
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 924ab1b..c015e1d3 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1438,7 +1438,8 @@
 bool Browser::IsPrerender2Supported(content::WebContents& web_contents) {
   Profile* profile =
       Profile::FromBrowserContext(web_contents.GetBrowserContext());
-  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs());
+  return prefetch::IsSomePreloadingEnabled(*profile->GetPrefs()) ==
+         content::PreloadingEligibility::kEligible;
 }
 
 std::unique_ptr<content::WebContents> Browser::ActivatePortalWebContents(
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index 87d213f..abe8e2d7 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -322,7 +322,8 @@
 #else   // !IS_CHROMEOS_LACROS && !IS_ANDROID
       // TODO(crbug.com/1320453): Document Picture-in-Picture is turned off in
       // lacros.
-      NOTIMPLEMENTED_LOG_ONCE() << "TYPE_PICTURE_IN_PICTURE";
+      // For TYPE_PICTURE_IN_PICTURE
+      NOTIMPLEMENTED_LOG_ONCE();
       return {nullptr, -1};
 #endif  // !IS_CHROMEOS_LACROS && !IS_ANDROID
     case WindowOpenDisposition::NEW_POPUP: {
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index f0c1394..a0f7a9a5 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -593,7 +593,8 @@
         views::Button::STATE_NORMAL,
         ui::ImageModel::FromVectorIcon(*(action.icon), ui::kColorIcon,
                                        GetLayoutConstant(DOWNLOAD_ICON_SIZE)));
-    action_button->SetAccessibleName(action.hover_text);
+    action_button->SetAccessibleName(
+        GetAccessibleNameForQuickAction(action.command));
     action_button->SetTooltipText(action.hover_text);
     action_button->SetVisible(true);
   }
@@ -771,6 +772,35 @@
   }
 }
 
+std::u16string DownloadBubbleRowView::GetAccessibleNameForQuickAction(
+    DownloadCommands::Command command) {
+  switch (command) {
+    case DownloadCommands::RESUME:
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION_ACCESSIBILITY,
+          model_->GetFileNameToReportUser().LossyDisplayName());
+    case DownloadCommands::PAUSE:
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION_ACCESSIBILITY,
+          model_->GetFileNameToReportUser().LossyDisplayName());
+    case DownloadCommands::OPEN_WHEN_COMPLETE:
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION_ACCESSIBILITY,
+          model_->GetFileNameToReportUser().LossyDisplayName());
+    case DownloadCommands::CANCEL:
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION_ACCESSIBILITY,
+          model_->GetFileNameToReportUser().LossyDisplayName());
+    case DownloadCommands::SHOW_IN_FOLDER:
+      return l10n_util::GetStringFUTF16(
+          IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION_ACCESSIBILITY,
+          model_->GetFileNameToReportUser().LossyDisplayName());
+    default:
+      NOTREACHED();
+      return u"";
+  }
+}
+
 void DownloadBubbleRowView::ShowContextMenuForViewImpl(
     View* source,
     const gfx::Point& point,
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
index 4419907..c137677 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
@@ -98,6 +98,8 @@
   views::ImageButton* AddQuickAction(DownloadCommands::Command command);
   views::ImageButton* GetActionButtonForCommand(
       DownloadCommands::Command command);
+  std::u16string GetAccessibleNameForQuickAction(
+      DownloadCommands::Command command);
 
   // If there is any change in state, update UI info.
   // Returns whether the ui info was changed.
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
index 1c4af376..a8b8e00c 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
@@ -389,7 +389,7 @@
 
   // Change launch container to open in tab.
   web_app::WebAppProvider::GetForTest(browser()->profile())
-      ->sync_bridge()
+      ->sync_bridge_unsafe()
       .SetAppUserDisplayMode(app_id, web_app::UserDisplayMode::kBrowser,
                              /*is_user_action=*/false);
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index 22d09142..e2898958 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -217,9 +217,8 @@
 }
 
 gfx::Size SidePanel::GetMinimumSize() const {
-  const int min_side_panel_contents_width = 320;
   const int min_height = 0;
-  return gfx::Size(min_side_panel_contents_width + kBorderInsets.width(),
+  return gfx::Size(min_side_panel_contents_width_ + kBorderInsets.width(),
                    min_height);
 }
 
@@ -257,8 +256,21 @@
     starting_width_on_resize_ = -1;
   }
   const int minimum_width = GetMinimumSize().width();
-  if (proposed_width < minimum_width) {
+  // The side panel may be resized up to leaving the main contents at
+  // kMainBrowserContentsMinimumWidth.
+  DCHECK_EQ(browser_view_->GetMinimumSize().width(),
+            BrowserViewLayout::kMainBrowserContentsMinimumWidth);
+  const int maximum_width = width() +
+                            browser_view_->contents_container()->width() -
+                            BrowserViewLayout::kMainBrowserContentsMinimumWidth;
+  // Side panel must stay at minimum width if either:
+  // a) The proposed width is less than the minimum.
+  // b) There is not enough room for the side panel to be wider than the
+  //    minimum.
+  if (proposed_width < minimum_width || maximum_width < minimum_width) {
     proposed_width = minimum_width;
+  } else if (proposed_width > maximum_width) {
+    proposed_width = maximum_width;
   }
   if (width() != proposed_width) {
     SetPanelWidth(proposed_width);
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index fc411e9b..0a83a106 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -37,6 +37,9 @@
   HorizontalAlignment GetHorizontalAlignment();
   bool IsRightAligned();
   gfx::Size GetMinimumSize() const override;
+  void SetMinimumSidePanelContentsWidthForTesting(int width) {
+    min_side_panel_contents_width_ = width;
+  }
 
   // views::ResizeAreaDelegate:
   void OnResize(int resize_amount, bool done_resizing) override;
@@ -72,6 +75,8 @@
 
   // Observes and listens to side panel alignment changes.
   PrefChangeRegistrar pref_change_registrar_;
+
+  int min_side_panel_contents_width_ = 320;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_H_
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
index a63d734..d52ee0d6 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
@@ -119,6 +119,19 @@
     return coordinator_->header_combobox_ != nullptr;
   }
 
+  void SetBrowserViewWidth(const int width) {
+    // If browser window is maximized then explicitly restore it, as otherwise
+    // the SetBounds call would be a no-op.
+    // TODO(crbug.com/1393153): Fix this on lacros builds and remove buildflag.
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
+    if (browser_view()->IsMaximized())
+      browser_view()->Restore();
+#endif
+
+    browser_view()->SetBounds(
+        gfx::Rect(width, browser_view()->GetBounds().height()));
+  }
+
  protected:
   raw_ptr<SidePanelCoordinator> coordinator_;
   raw_ptr<SidePanelRegistry> global_registry_;
@@ -142,8 +155,22 @@
   // Set side panel to right-aligned
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
+
+  // The side panel can only be widened when the browser main web contents width
+  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
+  // which is 500. Therefore the side panel can only be widened when the total
+  // browser view width is greater than the minimum side panel contents width +
+  // 500. The browser view maximum width is constrained by the display maximum,
+  // which is 800 on win32 and ChromeOS.
+  // Therefore, we set the browser view width to be 800 and we reduce the side
+  // panel width to 100 so that it can be widened and shrunk.
+  SetBrowserViewWidth(800);
+  browser_view()
+      ->unified_side_panel()
+      ->SetMinimumSidePanelContentsWidthForTesting(100);
+
   coordinator_->Toggle();
-  const int starting_width = 500;
+  const int starting_width = 200;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -168,8 +195,21 @@
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthMaxMin) {
+  // The side panel can only be widened when the browser main web contents width
+  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
+  // which is 500. Therefore the side panel can only be widened when the total
+  // browser view width is greater than the minimum side panel contents width +
+  // 500. The browser view maximum width is constrained by the display maximum,
+  // which is 800 on win32 and ChromeOS.
+  // Therefore, we set the browser view width to be 800 and we reduce the side
+  // panel width to 100 so that it can be widened and shrunk.
+  SetBrowserViewWidth(800);
+  browser_view()
+      ->unified_side_panel()
+      ->SetMinimumSidePanelContentsWidthForTesting(100);
+
   coordinator_->Toggle();
-  const int starting_width = 500;
+  const int starting_width = 200;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -184,22 +224,32 @@
 
   browser_view()->unified_side_panel()->OnResize(-large_increment, true);
   views::test::RunScheduledLayout(browser_view());
-  BrowserViewLayout* layout_manager =
-      static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
-  const int min_web_contents_width =
-      layout_manager->GetMinWebContentsWidthForTesting();
-  EXPECT_EQ(browser_view()->contents_web_view()->width(),
-            min_web_contents_width);
+  EXPECT_GT(browser_view()->unified_side_panel()->width(), starting_width);
+  EXPECT_EQ(browser_view()->contents_container()->width(),
+            BrowserViewLayout::kMainBrowserContentsMinimumWidth);
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthRTL) {
+  // The side panel can only be widened when the browser main web contents width
+  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
+  // which is 500. Therefore the side panel can only be widened when the total
+  // browser view width is greater than the minimum side panel contents width +
+  // 500. The browser view maximum width is constrained by the display maximum,
+  // which is 800 on win32 and ChromeOS.
+  // Therefore, we set the browser view width to be 800 and we reduce the side
+  // panel width to 100 so that it can be widened and shrunk.
+  SetBrowserViewWidth(800);
+  browser_view()
+      ->unified_side_panel()
+      ->SetMinimumSidePanelContentsWidthForTesting(100);
+
   // Set side panel to right-aligned
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
   // Set UI direction to LTR
   base::i18n::SetRTLForTesting(false);
   coordinator_->Toggle();
-  const int starting_width = 500;
+  const int starting_width = 200;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -223,38 +273,79 @@
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthWindowResize) {
+  // The side panel can only be widened when the browser main web contents width
+  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
+  // which is 500. Therefore the side panel can only be widened when the total
+  // browser view width is greater than the minimum side panel contents width +
+  // 500. The browser view maximum width is constrained by the display maximum,
+  // which is 800 on win32 and ChromeOS.
+  // Therefore, we set the browser view width to be 800 and we reduce the side
+  // panel width to 100 so that it can be widened and shrunk.
+  SetBrowserViewWidth(800);
+  browser_view()
+      ->unified_side_panel()
+      ->SetMinimumSidePanelContentsWidthForTesting(100);
+
   coordinator_->Toggle();
-  const int starting_width = 500;
+  const int starting_width = 200;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
 
   // Shrink browser window enough that side panel should also shrink in
   // observance of web contents minimum width.
-  gfx::Rect original_bounds(browser_view()->GetBounds());
-  gfx::Size new_size(starting_width, starting_width);
-  gfx::Rect new_bounds(original_bounds);
-  new_bounds.set_size(new_size);
-  // Explicitly restore the browser window on ChromeOS, as it would otherwise
-  // be maximized and the SetBounds call would be a no-op.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  browser_view()->Restore();
-#endif
-  browser_view()->SetBounds(new_bounds);
-  EXPECT_LT(browser_view()->unified_side_panel()->width(), starting_width);
-  BrowserViewLayout* layout_manager =
+  const int original_width = browser_view()->GetBounds().width();
+  SetBrowserViewWidth(starting_width);
+  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
+            browser_view()->unified_side_panel()->GetMinimumSize().width());
+  BrowserViewLayout* const layout_manager =
       static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
   const int min_web_contents_width =
       layout_manager->GetMinWebContentsWidthForTesting();
-  EXPECT_EQ(browser_view()->contents_web_view()->width(),
+  EXPECT_EQ(browser_view()->contents_container()->width(),
             min_web_contents_width);
 
   // Return browser window to original size, side panel should also return to
   // size prior to window resize.
-  browser_view()->SetBounds(original_bounds);
+  SetBrowserViewWidth(original_width);
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
 }
 
+TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthSmallWindow) {
+  SetBrowserViewWidth(500);
+  browser_view()
+      ->unified_side_panel()
+      ->SetMinimumSidePanelContentsWidthForTesting(100);
+  coordinator_->Toggle();
+  const int starting_width = 200;
+  const int min_side_panel_width =
+      browser_view()->unified_side_panel()->GetMinimumSize().width();
+  browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
+  views::test::RunScheduledLayout(browser_view());
+  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
+            min_side_panel_width);
+
+  // Attempt to resize the side panel, side panel should not be able to resized.
+  const int increment = 50;
+  browser_view()->unified_side_panel()->OnResize(increment, true);
+  views::test::RunScheduledLayout(browser_view());
+  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
+            min_side_panel_width);
+  BrowserViewLayout* const layout_manager =
+      static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
+  const int min_web_contents_width =
+      layout_manager->GetMinWebContentsWidthForTesting();
+  EXPECT_EQ(browser_view()->contents_container()->width(),
+            min_web_contents_width);
+
+  browser_view()->unified_side_panel()->OnResize(-increment, true);
+  views::test::RunScheduledLayout(browser_view());
+  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
+            min_side_panel_width);
+  EXPECT_EQ(browser_view()->contents_container()->width(),
+            min_web_contents_width);
+}
+
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelAlignment) {
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.cc b/chrome/browser/ui/views/tabs/compound_tab_container.cc
index ddda01c..a40daf90 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.cc
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.cc
@@ -8,6 +8,7 @@
 #include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/trace_event/trace_event.h"
+#include "chrome/browser/ui/tabs/tab_style.h"
 #include "chrome/browser/ui/tabs/tab_types.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
@@ -20,7 +21,6 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/list_selection_model.h"
 #include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/layout_types.h"
 #include "ui/views/view.h"
 #include "ui/views/view_utils.h"
@@ -229,18 +229,6 @@
       hover_card_controller_(hover_card_controller),
       scroll_contents_view_(scroll_contents_view),
       bounds_animator_(this) {
-  const views::FlexSpecification tab_container_flex_spec =
-      views::FlexSpecification(views::LayoutOrientation::kHorizontal,
-                               views::MinimumFlexSizeRule::kScaleToMinimum,
-                               views::MaximumFlexSizeRule::kPreferred);
-  pinned_tab_container_->SetProperty(views::kFlexBehaviorKey,
-                                     tab_container_flex_spec);
-  unpinned_tab_container_->SetProperty(views::kFlexBehaviorKey,
-                                       tab_container_flex_spec);
-
-  SetLayoutManager(std::make_unique<views::FlexLayout>())
-      ->SetOrientation(views::LayoutOrientation::kHorizontal);
-
   if (!gfx::Animation::ShouldRenderRichAnimation())
     bounds_animator_.SetAnimationDuration(base::TimeDelta());
 }
@@ -249,15 +237,8 @@
 
 void CompoundTabContainer::SetAvailableWidthCallback(
     base::RepeatingCallback<int()> available_width_callback) {
-  // The pinned container lays out independently of its available width because
-  // it doesn't have variable-width tabs. It doesn't matter what we give it here
-  // - it will call its callback but ultimately end up effectively ignoring the
-  // result deep in TabStripLayoutHelper (because all of its tabs are pinned).
-  pinned_tab_container_->SetAvailableWidthCallback(
-      base::BindRepeating([]() { return 0; }));
-  unpinned_tab_container_->SetAvailableWidthCallback(base::BindRepeating(
-      &CompoundTabContainer::GetAvailableWidthForUnpinnedTabContainer,
-      base::Unretained(this), available_width_callback));
+  // Store this ourselves, and let our child containers fall back to calling
+  // GetAvailableSize.
   available_width_callback_ = available_width_callback;
 }
 
@@ -645,9 +626,49 @@
       unpinned_tab_container_->GetIdealBounds(group));
 }
 
+gfx::Size CompoundTabContainer::GetMinimumSize() const {
+  return GetCombinedSizeForTabContainerSizes(
+      pinned_tab_container_->GetMinimumSize(),
+      unpinned_tab_container_->GetMinimumSize());
+}
+
+views::SizeBounds CompoundTabContainer::GetAvailableSize(
+    const views::View* child) const {
+  if (child == base::to_address(pinned_tab_container_)) {
+    return views::SizeBounds(GetAvailableWidthForTabContainer(),
+                             views::SizeBound());
+  }
+
+  if (child == base::to_address(unpinned_tab_container_)) {
+    return views::SizeBounds(GetAvailableWidthForUnpinnedTabContainer(),
+                             views::SizeBound());
+  }
+
+  NOTREACHED();
+  return views::SizeBounds();
+}
+
+gfx::Size CompoundTabContainer::CalculatePreferredSize() const {
+  return GetCombinedSizeForTabContainerSizes(
+      pinned_tab_container_->GetPreferredSize(),
+      unpinned_tab_container_->GetPreferredSize());
+}
+
 void CompoundTabContainer::Layout() {
-  // TODO(now): What do we do if an animation is running?
-  views::View::Layout();
+  // Pinned container gets however much space it wants.
+  pinned_tab_container_->SetBoundsRect(
+      gfx::Rect(pinned_tab_container_->GetPreferredSize()));
+
+  // Unpinned container can have whatever is left over.
+  const int unpinned_container_leading_x =
+      std::max(0, pinned_tab_container_->width() - TabStyle::GetTabOverlap());
+  const int available_width = width() - unpinned_container_leading_x;
+
+  const gfx::Size pref_size = unpinned_tab_container_->GetPreferredSize();
+
+  unpinned_tab_container_->SetBounds(
+      unpinned_container_leading_x, 0,
+      std::min(available_width, pref_size.width()), pref_size.height());
 }
 
 void CompoundTabContainer::PaintChildren(const views::PaintInfo& paint_info) {
@@ -800,10 +821,6 @@
       AddChildView(from_container.TransferTabOut(from_container_index));
   tab->SetBoundsRect(ToEnclosingRect(initial_tab_bounds));
 
-  // Remove it from layout - `bounds_animator_` will be managing it.
-  static_cast<views::LayoutManagerBase*>(GetLayoutManager())
-      ->SetChildViewIgnoredByLayout(tab, true);
-
   // Let `to_container` update its layout data structures.
   to_container.AddTabToViewModel(
       tab, to_container_index,
@@ -856,17 +873,29 @@
 int CompoundTabContainer::GetUnpinnedContainerIdealLeadingX() const {
   return NumPinnedTabs() > 0
              ? pinned_tab_container_->GetIdealBounds(NumPinnedTabs() - 1)
-                   .right()
+                       .right() -
+                   TabStyle::GetTabOverlap()
              : 0;
 }
 
-int CompoundTabContainer::GetAvailableWidthForUnpinnedTabContainer(
-    base::RepeatingCallback<int()> available_width_callback) {
+int CompoundTabContainer::GetAvailableWidthForUnpinnedTabContainer() const {
   // The unpinned container gets the width the pinned container doesn't want.
   return GetAvailableWidthForTabContainer() -
          GetUnpinnedContainerIdealLeadingX();
 }
 
+gfx::Size CompoundTabContainer::GetCombinedSizeForTabContainerSizes(
+    const gfx::Size pinned_size,
+    const gfx::Size unpinned_size) const {
+  gfx::Size largest_container = pinned_size;
+  largest_container.SetToMax(unpinned_size);
+
+  const int width_with_overlap =
+      pinned_size.width() + unpinned_size.width() - TabStyle::GetTabOverlap();
+  return gfx::Size(std::max(width_with_overlap, largest_container.width()),
+                   largest_container.height());
+}
+
 absl::optional<gfx::Rect> CompoundTabContainer::GetVisibleContentRect() {
   views::ScrollView* scroll_container =
       views::ScrollView::GetScrollViewForContents(scroll_contents_view_);
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.h b/chrome/browser/ui/views/tabs/compound_tab_container.h
index 982c5826..77dd60d 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.h
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.h
@@ -98,6 +98,9 @@
   gfx::Rect GetIdealBounds(tab_groups::TabGroupId group) const override;
 
   // views::View
+  gfx::Size GetMinimumSize() const override;
+  views::SizeBounds GetAvailableSize(const View* child) const override;
+  gfx::Size CalculatePreferredSize() const override;
   void Layout() override;
   void PaintChildren(const views::PaintInfo& paint_info) override;
   void ChildPreferredSizeChanged(views::View* child) override;
@@ -154,8 +157,12 @@
   // any running animations finish.
   int GetUnpinnedContainerIdealLeadingX() const;
 
-  int GetAvailableWidthForUnpinnedTabContainer(
-      base::RepeatingCallback<int()> available_width_callback);
+  int GetAvailableWidthForUnpinnedTabContainer() const;
+
+  // Computes the size of this compound container assuming the pinned and
+  // unpinned containers have the given sizes.
+  gfx::Size GetCombinedSizeForTabContainerSizes(gfx::Size pinned_size,
+                                                gfx::Size unpinned_size) const;
 
   // Private getter to retrieve the visible rect of the scroll container.
   absl::optional<gfx::Rect> GetVisibleContentRect();
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container_unittest.cc b/chrome/browser/ui/views/tabs/compound_tab_container_unittest.cc
index 18b88e4..da6752a 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/compound_tab_container_unittest.cc
@@ -517,3 +517,63 @@
   tab_container_->UpdateAnimationTarget(tab, animation_target,
                                         TabPinned::kUnpinned);
 }
+
+TEST_F(CompoundTabContainerTest, SubContainersOverlap) {
+  // With only pinned tabs, the compound container should match the pinned
+  // container's width.
+  views::View* const pinned_container = AddTab(0, TabPinned::kPinned)->parent();
+  tab_container_->CompleteAnimationAndLayout();
+  EXPECT_EQ(tab_container_->GetPreferredSize().width(),
+            pinned_container->GetPreferredSize().width());
+  EXPECT_EQ(tab_container_->GetMinimumSize().width(),
+            pinned_container->GetMinimumSize().width());
+  EXPECT_EQ(pinned_container->bounds().width(),
+            pinned_container->GetPreferredSize().width());
+
+  // With both subcontainers nonempty, the compound container's width should be
+  // less than the sum of its parts.
+  views::View* const unpinned_container =
+      AddTab(1, TabPinned::kUnpinned)->parent();
+  tab_container_->CompleteAnimationAndLayout();
+  EXPECT_LT(tab_container_->GetPreferredSize().width(),
+            pinned_container->GetPreferredSize().width() +
+                unpinned_container->GetPreferredSize().width());
+  EXPECT_LT(tab_container_->GetMinimumSize().width(),
+            pinned_container->GetMinimumSize().width() +
+                unpinned_container->GetMinimumSize().width());
+  // And the two containers should overlap.
+  EXPECT_LT(unpinned_container->bounds().x(),
+            pinned_container->bounds().right());
+
+  // Same as case 1, but reversed.
+  RemoveTab(0);
+  tab_container_->CompleteAnimationAndLayout();
+  EXPECT_EQ(tab_container_->GetPreferredSize().width(),
+            unpinned_container->GetPreferredSize().width());
+  EXPECT_EQ(tab_container_->GetMinimumSize().width(),
+            unpinned_container->GetMinimumSize().width());
+  EXPECT_EQ(unpinned_container->bounds().width(),
+            unpinned_container->GetPreferredSize().width());
+}
+
+TEST_F(CompoundTabContainerTest, AvailableWidth) {
+  views::View* const pinned_container = AddTab(0, TabPinned::kPinned)->parent();
+  views::View* const unpinned_container =
+      AddTab(1, TabPinned::kUnpinned)->parent();
+
+  // `pinned_container` gets as much space as we can give it - in this test
+  // harness, that's `tab_container_`'s width.
+  EXPECT_EQ(tab_container_->GetAvailableSize(pinned_container).width().value(),
+            tab_container_->width());
+
+  // `unpinned_container` doesn't, because `pinned_container` has some reserved.
+  EXPECT_LT(
+      tab_container_->GetAvailableSize(unpinned_container).width().value(),
+      tab_container_->width());
+
+  // Because of the overlap, `unpinned_container` should have slightly more
+  // available width than `(total available - pinned_container reserved width)`.
+  EXPECT_GT(
+      tab_container_->GetAvailableSize(unpinned_container).width().value(),
+      tab_container_->width() - pinned_container->GetPreferredSize().width());
+}
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 8f613d63..30c3cab 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
@@ -93,11 +93,11 @@
 }
 
 size_t TabStripLayoutHelper::GetPinnedTabCount() const {
-  views::ViewModelT<Tab>* tabs = get_tabs_callback_.Run();
   size_t pinned_count = 0;
-  while (pinned_count < tabs->view_size() &&
-         tabs->view_at(pinned_count)->data().pinned) {
-    pinned_count++;
+  for (const TabSlot& slot : slots_) {
+    if (slot.state.pinned() == TabPinned::kPinned && !slot.state.IsClosed()) {
+      pinned_count++;
+    }
   }
   return pinned_count;
 }
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
index cdf625a..740b7ba 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
@@ -1019,15 +1019,22 @@
     SetTextAlignmentForLocaleTextDirection(model_->GetSourceLanguageCode());
   }
 
-  // Resize the text label to match the width of the bubble. This will depend on
-  // either the preferred width of the tabbed pane, or
-  // |largest_view_state_width_|, which serves as a lower bound.
-  if (tab_view_top_row_->GetPreferredSize().width() <
-      largest_view_state_width_) {
-    partial_text_label_->SizeToFit(largest_view_state_width_);
+  // Use the maximum set width for the bubble for the largest text volumes to
+  // prevent sizing of the bubble that exceeds screen height.
+  if (partial_text_label_->GetText().length() > char_threshold_for_max_width_) {
+    partial_text_label_->SizeToFit(bubble_max_width_);
   } else {
-    partial_text_label_->SizeToFit(
-        tab_view_top_row_->GetPreferredSize().width());
+    // Otherwise, with no risk of overflow, resize the text label to match the
+    // width of the bubble. This will depend on either the preferred width of
+    // the tabbed pane, or |largest_view_state_width_|, which serves as a lower
+    // bound.
+    if (tab_view_top_row_->GetPreferredSize().width() <
+        largest_view_state_width_) {
+      partial_text_label_->SizeToFit(largest_view_state_width_);
+    } else {
+      partial_text_label_->SizeToFit(
+          tab_view_top_row_->GetPreferredSize().width());
+    }
   }
 
   AnnounceForAccessibility(view_state);
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
index 4f6085f..87dcc79 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
@@ -293,8 +293,23 @@
   std::u16string text_selection_;
 
   // The width of the largest non-|translate_view_| child view at
-  // initialization.
-  int largest_view_state_width_ = 0;
+  // initialization. The default minimum width is set to 300dp to provide a more
+  // consistent experience between different UI languages - for high density
+  // languages the width would otherwise be very narrow.
+  int largest_view_state_width_ = 300;
+
+  // The threshold for character volume of |partial_text_label_|. If the volume
+  // is larger than the threshold, use the preset maximum width allowable for
+  // the bubble. Otherwise, resize normally.
+  const size_t char_threshold_for_max_width_ = 1300;
+
+  // The max allowable width for the bubble, used when the character volume of
+  // |partial_text_label_| exceeds |char_threshold_for_max_width_|. This is
+  // necessary to accommodate the size of the label in cases of largest possible
+  // character volume. It follows that this is based off of
+  // translate::kDesktopPartialTranslateTextSelectionMaxCharacters and should be
+  // updated alongside it.
+  const int bubble_max_width_ = 550;
 
   base::OnceClosure on_closing_;
 
diff --git a/chrome/browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc b/chrome/browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc
index 4b15d8d8..72305ed 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc
@@ -100,7 +100,7 @@
   void TearDownOnMainThread() override {
     Profile* const profile = browser()->profile();
     web_app::WebAppRegistrar& registrar =
-        web_app::WebAppProvider::GetForTest(profile)->registrar();
+        web_app::WebAppProvider::GetForTest(profile)->registrar_unsafe();
     for (const auto& app_id : registrar.GetAppIds()) {
       web_app::AppReadinessWaiter app_readiness_waiter(
           profile, app_id, apps::Readiness::kUninstalledByUser);
diff --git a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
index e820851a6..2aa68da 100644
--- a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
@@ -105,7 +105,8 @@
     // `test::InstallWebApp()` forces a kBrowser display mode; see
     // `WebAppInstallFinalizer::FinalizeInstall()`.
     ScopedRegistryUpdate update(
-        &WebAppProvider::GetForTest(browser()->profile())->sync_bridge());
+        &WebAppProvider::GetForTest(browser()->profile())
+             ->sync_bridge_unsafe());
     update->UpdateApp(app_id_)->SetUserDisplayMode(
         UserDisplayMode::kStandalone);
   }
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index a75b3c0..9ca24ee 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -1750,7 +1750,8 @@
   ;
   // Will need to add feature flag based condition for web app settings page
 #if BUILDFLAG(IS_CHROMEOS)
-  auto& sync_bridge = WebAppProvider::GetForTest(profile())->sync_bridge();
+  auto& sync_bridge =
+      WebAppProvider::GetForTest(profile())->sync_bridge_unsafe();
   sync_bridge.SetAppUserDisplayMode(app_id, UserDisplayMode::kBrowser, true);
   AppWindowModeWaiter(profile(), app_id, apps::WindowMode::kBrowser).Await();
 #else
@@ -1769,7 +1770,8 @@
   ;
   // Will need to add feature flag based condition for web app settings page.
 #if BUILDFLAG(IS_CHROMEOS)
-  auto& sync_bridge = WebAppProvider::GetForTest(profile())->sync_bridge();
+  auto& sync_bridge =
+      WebAppProvider::GetForTest(profile())->sync_bridge_unsafe();
   sync_bridge.SetAppUserDisplayMode(app_id, UserDisplayMode::kStandalone, true);
   AppWindowModeWaiter(profile(), app_id, apps::WindowMode::kWindow).Await();
 #else
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
index 33eaa20..e76d567 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
@@ -25,14 +25,12 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/app_constants/constants.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "components/services/app_service/public/cpp/intent_filter.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
 #include "components/services/app_service/public/cpp/permission.h"
 #include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
 #include "components/services/app_service/public/cpp/types_util.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -278,14 +276,8 @@
 void AppManagementPageHandler::SetResizeLocked(const std::string& app_id,
                                                bool locked) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
-    apps::AppServiceProxyFactory::GetForProfile(profile_)->SetResizeLocked(
-        app_id, locked);
-  } else {
-    apps::AppServiceProxyFactory::GetForProfile(profile_)->SetResizeLocked(
-        app_id, locked ? apps::mojom::OptionalBool::kTrue
-                       : apps::mojom::OptionalBool::kFalse);
-  }
+  apps::AppServiceProxyFactory::GetForProfile(profile_)->SetResizeLocked(
+      app_id, locked);
 #else
   NOTREACHED();
 #endif
@@ -342,14 +334,8 @@
   if (provider->registrar_unsafe().IsIsolated(app_id)) {
     NOTREACHED();
   } else {
-    if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
-      apps::AppServiceProxyFactory::GetForProfile(profile_)->SetWindowMode(
-          app_id, window_mode);
-
-    } else {
-      apps::AppServiceProxyFactory::GetForProfile(profile_)->SetWindowMode(
-          app_id, apps::ConvertWindowModeToMojomWindowMode(window_mode));
-    }
+    apps::AppServiceProxyFactory::GetForProfile(profile_)->SetWindowMode(
+        app_id, window_mode);
   }
 #endif
 }
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.cc
index 165d017f..fd9bdb8 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h"
 
 #include "ash/public/cpp/notification_utils.h"
+#include "base/check.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
@@ -17,12 +19,12 @@
 namespace ash::cloud_upload {
 namespace {
 
-// Minimum amount of time, in seconds, for which the notification should be
+// The minimum amount of time for which the "in progress" state should be
 // displayed.
-const base::TimeDelta kMinNotificationTime = base::Seconds(5);
+const base::TimeDelta kMinInProgressTime = base::Seconds(5);
 
-// Time, in seconds, for which the "Complete" notification should display.
-const base::TimeDelta kCompleteNotificationTimeout = base::Seconds(2);
+// Time for which the "Complete" notification should display.
+const base::TimeDelta kCompleteNotificationTime = base::Seconds(5);
 
 // If no other class instance holds a reference to the notification manager, the
 // notification manager goes out of scope.
@@ -137,19 +139,19 @@
                                            *notification,
                                            /*metadata=*/nullptr);
 
-  // Start the "min time" notification timer when the first progress
-  // notification is shown.
-  if (!first_notification_shown) {
-    first_notification_shown = true;
-    notification_timer_.Start(
-        FROM_HERE, kMinNotificationTime,
+  // Make sure we display the "in progress" state for a minimum amount of time.
+  if (state_ == State::kUninitialized) {
+    state_ = State::kInProgress;
+    in_progress_timer_.Start(
+        FROM_HERE, kMinInProgressTime,
         base::BindOnce(
-            &CloudUploadNotificationManager::OnMinNotificationTimeReached,
+            &CloudUploadNotificationManager::OnMinInProgressTimeReached,
             weak_ptr_factory_.GetWeakPtr()));
   }
 }
 
-void CloudUploadNotificationManager::ShowUploadComplete() {
+void CloudUploadNotificationManager::ShowCompleteNotification() {
+  DCHECK_EQ(state_, State::kComplete);
   std::unique_ptr<message_center::Notification> notification =
       CreateUploadCompleteNotification();
   notification->set_never_timeout(true);
@@ -157,26 +159,28 @@
                                            *notification,
                                            /*metadata=*/nullptr);
 
-  // If the complete notification is shown before any progress notifications,
-  // start the `kMinNotificationTime` timer.
-  if (!first_notification_shown) {
-    first_notification_shown = true;
-    notification_timer_.Start(
-        FROM_HERE, kMinNotificationTime,
-        base::BindOnce(
-            &CloudUploadNotificationManager::OnMinNotificationTimeReached,
-            weak_ptr_factory_.GetWeakPtr()));
-  }
-
   // Start the timer to automatically dismiss the "Complete" notification.
   complete_notification_timer_.Start(
-      FROM_HERE, kCompleteNotificationTimeout,
-      base::BindOnce(
-          &CloudUploadNotificationManager::OnCompleteNotificationTimeout,
-          weak_ptr_factory_.GetWeakPtr()));
+      FROM_HERE, kCompleteNotificationTime,
+      base::BindOnce(&CloudUploadNotificationManager::CloseNotification,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
-void CloudUploadNotificationManager::ShowUploadError(std::string message) {
+void CloudUploadNotificationManager::MarkUploadComplete() {
+  // Check if the "in progress" timeout has happened yet or not.
+  if (state_ == State::kInProgress) {
+    state_ = State::kWaitingForInProgressTimeout;
+  } else if (state_ == State::kUninitialized ||
+             state_ == State::kInProgressTimedOut) {
+    // If the complete notification is shown before any progress notifications,
+    // we don't run the kMinInProgressTime timeout.
+    state_ = State::kComplete;
+    ShowCompleteNotification();
+  }
+}
+
+void CloudUploadNotificationManager::ShowUploadError(
+    const std::string& message) {
   std::unique_ptr<message_center::Notification> notification =
       CreateUploadErrorNotification(message);
   notification->set_never_timeout(true);
@@ -185,28 +189,19 @@
                                            /*metadata=*/nullptr);
 }
 
-void CloudUploadNotificationManager::OnMinNotificationTimeReached() {
-  // Close the notification only if the "Complete notification" has timed out.
-  // Error notifications can only be dismissed by users.
-  if (completed_) {
-    CloseNotification();
-  }
-}
-
-void CloudUploadNotificationManager::OnCompleteNotificationTimeout() {
-  completed_ = true;
-
-  // If `kMinNotificationTime` hasn't been reached yet, do not close the
-  // notification.
-  if (!notification_timer_.IsRunning()) {
-    CloseNotification();
+void CloudUploadNotificationManager::OnMinInProgressTimeReached() {
+  if (state_ == State::kInProgress) {
+    state_ = State::kInProgressTimedOut;
+  } else if (state_ == State::kWaitingForInProgressTimeout) {
+    state_ = State::kComplete;
+    ShowCompleteNotification();
   }
 }
 
 void CloudUploadNotificationManager::CloseNotification() {
   GetNotificationDisplayService()->Close(NotificationHandler::Type::TRANSIENT,
                                          notification_id_);
-  notification_timer_.Stop();
+  in_progress_timer_.Stop();
   complete_notification_timer_.Stop();
   if (callback_) {
     std::move(callback_).Run();
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h
index 8c4ab919..d162523 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_UPLOAD_NOTIFICATION_MANAGER_H_
 #define CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_UPLOAD_NOTIFICATION_MANAGER_H_
 
-#include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
@@ -17,7 +16,10 @@
 
 namespace ash::cloud_upload {
 
-// Manages creation/deletion and update of cloud upload system notifications.
+// Creates, updates and deletes cloud upload system notifications. Ensures that
+// notifications stay in the "in progress" state for a minimum of 5 seconds, and
+// a minimum of 5 seconds for the 'complete' state. For the error state,
+// notifications stay open until the user closes them.
 class CloudUploadNotificationManager
     : public base::RefCounted<CloudUploadNotificationManager> {
  public:
@@ -26,14 +28,20 @@
                                  const std::string& cloud_provider_name,
                                  const std::string& target_app_name);
 
-  // Shows the upload progress notification. |progress| within the 0-100 range.
+  // Creates the notification with "in progress" state if it doesn't exist, or
+  // updates the progress bar if it does. |progress| is within the 0-100 range.
+  // The notification will stay in the "in progress" state for a minimum of 5
+  // seconds, even at 100% progress.
   void ShowUploadProgress(int progress);
 
-  // Shows the upload complete notification for 2s if the upload was successful.
-  void ShowUploadComplete();
+  // Shows the upload complete notification for 5 seconds, but only once the
+  // minimum 5 seconds from the "in progress" state has finished.
+  void MarkUploadComplete();
 
-  // Shows the upload error notification.
-  void ShowUploadError(std::string message);
+  // Shows the error state for the notification indefinitely, until closed by
+  // the user. Does not wait for the progress notification to show for a minimum
+  // time.
+  void ShowUploadError(const std::string& message);
 
  private:
   friend base::RefCounted<CloudUploadNotificationManager>;
@@ -54,17 +62,29 @@
   std::unique_ptr<message_center::Notification> CreateUploadErrorNotification(
       std::string message);
 
-  // Called when the minimum amount of time to display the notification is
-  // reached.
-  void OnMinNotificationTimeReached();
+  // Called when the minimum amount of time to display the "in progress"
+  // notification is reached.
+  void OnMinInProgressTimeReached();
 
-  // Called when the "Complete" notification times out.
-  void OnCompleteNotificationTimeout();
+  // Updates the notification immediately to show the complete state.
+  void ShowCompleteNotification();
 
   // Called when the upload flow is complete: Ensures that notifications are
   // closed, timers are interrupted and the completion callback has been called.
   void CloseNotification();
 
+  // A state machine and the possible transitions. The state of showing the
+  // error notification is not explicit because it is never used to determine
+  // later logic.
+  enum class State {
+    kUninitialized,  // --> kInProgress, kComplete
+    kInProgress,     // --> kInProgressTimedOut, kWaitingForInProgressTimeout,
+                     // (error)
+    kInProgressTimedOut,           // --> kComplete, (error)
+    kWaitingForInProgressTimeout,  // --> kComplete
+    kComplete
+  };
+
   // Counts the total number of notification manager instances. This counter is
   // never decremented.
   static inline int notification_manager_counter_ = 0;
@@ -76,10 +96,9 @@
   std::string notification_id_;
   std::string target_app_name_;
   base::OnceClosure callback_;
-  base::OneShotTimer notification_timer_;
+  base::OneShotTimer in_progress_timer_;
   base::OneShotTimer complete_notification_timer_;
-  bool first_notification_shown = false;
-  bool completed_ = false;
+  State state_ = State::kUninitialized;
   base::WeakPtrFactory<CloudUploadNotificationManager> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager_unittest.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager_unittest.cc
new file mode 100644
index 0000000..06b7ef2e
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_notification_manager.h"
+
+#include "base/time/time.h"
+#include "chrome/browser/notifications/notification_display_service_factory.h"
+#include "chrome/browser/notifications/stub_notification_display_service.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash::cloud_upload {
+
+class CloudUploadNotificationManagerTest : public testing::Test {
+ public:
+  CloudUploadNotificationManagerTest() = default;
+
+  CloudUploadNotificationManagerTest(
+      const CloudUploadNotificationManagerTest&) = delete;
+  CloudUploadNotificationManagerTest& operator=(
+      const CloudUploadNotificationManagerTest&) = delete;
+
+  // testing::Test:
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+
+    display_service_ = static_cast<StubNotificationDisplayService*>(
+        NotificationDisplayServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                profile_.get(),
+                base::BindRepeating(
+                    &StubNotificationDisplayService::FactoryForTests)));
+  }
+
+ protected:
+  Profile* profile() { return profile_.get(); }
+
+  absl::optional<message_center::Notification> notification() {
+    auto notifications = display_service_->GetDisplayedNotificationsForType(
+        NotificationHandler::Type::TRANSIENT);
+    if (notifications.size()) {
+      return notifications[0];
+    }
+    return absl::nullopt;
+  }
+
+  bool HaveProgressNotification() {
+    return notification().has_value() &&
+           notification()->type() ==
+               message_center::NotificationType::NOTIFICATION_TYPE_PROGRESS;
+  }
+
+  bool HaveCompleteNotification() {
+    return notification().has_value() &&
+           notification()->type() ==
+               message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE &&
+           notification()->title().starts_with(u"Move completed");
+  }
+
+  bool HaveErrorNotification() {
+    return notification().has_value() &&
+           notification()->type() ==
+               message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE &&
+           notification()->title().starts_with(u"Failed");
+  }
+
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  std::unique_ptr<TestingProfile> profile_;
+  StubNotificationDisplayService* display_service_;
+};
+
+TEST_F(CloudUploadNotificationManagerTest,
+       ShowUploadProgressCreatesNotification) {
+  scoped_refptr<CloudUploadNotificationManager> manager =
+      base::MakeRefCounted<CloudUploadNotificationManager>(
+          profile(), "foo.docx", "Google Drive", "Google Docs");
+
+  ASSERT_EQ(absl::nullopt, notification());
+  manager->ShowUploadProgress(1);
+  ASSERT_TRUE(HaveProgressNotification());
+}
+
+TEST_F(CloudUploadNotificationManagerTest, MinimumTiming) {
+  scoped_refptr<CloudUploadNotificationManager> manager =
+      base::MakeRefCounted<CloudUploadNotificationManager>(
+          profile(), "foo.docx", "Google Drive", "Google Docs");
+
+  manager->ShowUploadProgress(1);
+  manager->ShowUploadProgress(100);
+  manager->MarkUploadComplete();
+  ASSERT_TRUE(HaveProgressNotification());
+
+  // The progress notification should still be showing.
+  task_environment_.FastForwardBy(base::Milliseconds(4900));
+  ASSERT_TRUE(HaveProgressNotification());
+
+  // Now we see the Complete nofication after 5s.
+  task_environment_.FastForwardBy(base::Milliseconds(500));
+  ASSERT_TRUE(HaveCompleteNotification());
+
+  // Now we're at 9900 ms total - we still expect the Complete notification.
+  task_environment_.FastForwardBy(base::Milliseconds(4500));
+  ASSERT_TRUE(HaveCompleteNotification());
+
+  // After > 10s total, the notification should be closed.
+  task_environment_.FastForwardBy(base::Milliseconds(500));
+  ASSERT_EQ(absl::nullopt, notification());
+}
+
+TEST_F(CloudUploadNotificationManagerTest, CompleteWithoutProgress) {
+  scoped_refptr<CloudUploadNotificationManager> manager =
+      base::MakeRefCounted<CloudUploadNotificationManager>(
+          profile(), "foo.docx", "Google Drive", "Google Docs");
+
+  manager->MarkUploadComplete();
+  ASSERT_TRUE(HaveCompleteNotification());
+
+  // The complete notification should still be showing.
+  task_environment_.FastForwardBy(base::Milliseconds(4900));
+  ASSERT_TRUE(HaveCompleteNotification());
+
+  // After > 5s total, the notification should be closed.
+  task_environment_.FastForwardBy(base::Milliseconds(500));
+  ASSERT_EQ(absl::nullopt, notification());
+}
+
+TEST_F(CloudUploadNotificationManagerTest, ErrorStaysOpen) {
+  scoped_refptr<CloudUploadNotificationManager> manager =
+      base::MakeRefCounted<CloudUploadNotificationManager>(
+          profile(), "foo.docx", "Google Drive", "Google Docs");
+
+  manager->ShowUploadProgress(1);
+  manager->ShowUploadProgress(100);
+  manager->ShowUploadError("error");
+  // The error is shown straight away.
+  ASSERT_TRUE(HaveErrorNotification());
+
+  // The error notification should still be showing.
+  task_environment_.FastForwardBy(base::Seconds(60));
+  ASSERT_TRUE(HaveErrorNotification());
+}
+
+TEST_F(CloudUploadNotificationManagerTest, ManagerLifetime) {
+  {
+    scoped_refptr<CloudUploadNotificationManager> manager =
+        base::MakeRefCounted<CloudUploadNotificationManager>(
+            profile(), "foo.docx", "Google Drive", "Google Docs");
+
+    manager->ShowUploadProgress(1);
+    manager->ShowUploadError("error");
+    ASSERT_TRUE(HaveErrorNotification());
+  }
+  // We still have a ref to manager until the notification is dismissed.
+  ASSERT_TRUE(HaveErrorNotification());
+
+  notification()->delegate()->Click(absl::nullopt, absl::nullopt);
+  ASSERT_EQ(absl::nullopt, notification());
+}
+
+}  // namespace ash::cloud_upload
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/drive_upload_handler.cc b/chrome/browser/ui/webui/ash/cloud_upload/drive_upload_handler.cc
index 0e17080..dccbf26 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/drive_upload_handler.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/drive_upload_handler.cc
@@ -76,7 +76,7 @@
           base::MakeRefCounted<CloudUploadNotificationManager>(
               profile,
               source_url.path().BaseName().value(),
-              "OneDrive",
+              "Drive",
               GetTargetAppName(source_url.path()))),
       source_url_(source_url) {
   observed_task_id_ = -1;
@@ -164,7 +164,7 @@
   // Resolve notifications.
   if (notification_manager_) {
     if (hosted_url.is_valid()) {
-      notification_manager_->ShowUploadComplete();
+      notification_manager_->MarkUploadComplete();
     } else if (!error_message.empty()) {
       LOG(ERROR) << "Cloud upload: " << error_message;
       notification_manager_->ShowUploadError(error_message);
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/one_drive_upload_handler.cc b/chrome/browser/ui/webui/ash/cloud_upload/one_drive_upload_handler.cc
index 24ca79b..b27e1a9d 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/one_drive_upload_handler.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/one_drive_upload_handler.cc
@@ -131,7 +131,7 @@
   // Resolve notifications.
   if (notification_manager_) {
     if (uploaded_file_url.is_valid()) {
-      notification_manager_->ShowUploadComplete();
+      notification_manager_->MarkUploadComplete();
     } else if (!error_message.empty()) {
       LOG(ERROR) << "Upload to OneDrive: " << error_message;
       notification_manager_->ShowUploadError(error_message);
diff --git a/chrome/browser/ui/webui/ash/crostini_installer/crostini_installer_ui.cc b/chrome/browser/ui/webui/ash/crostini_installer/crostini_installer_ui.cc
index 584c182a..a6b08ed 100644
--- a/chrome/browser/ui/webui/ash/crostini_installer/crostini_installer_ui.cc
+++ b/chrome/browser/ui/webui/ash/crostini_installer/crostini_installer_ui.cc
@@ -143,6 +143,7 @@
                     crostini::DefaultContainerUserNameForProfile(profile));
 
   source->AddResourcePath("app.js", IDR_CROSTINI_INSTALLER_APP_JS);
+  source->AddResourcePath("app.html.js", IDR_CROSTINI_INSTALLER_APP_HTML_JS);
   source->AddResourcePath("browser_proxy.js",
                           IDR_CROSTINI_INSTALLER_BROWSER_PROXY_JS);
   source->AddResourcePath("crostini_installer.mojom-lite.js",
diff --git a/chrome/browser/ui/webui/ash/crostini_upgrader/crostini_upgrader_ui.cc b/chrome/browser/ui/webui/ash/crostini_upgrader/crostini_upgrader_ui.cc
index 7cc5d1a..ad4691f 100644
--- a/chrome/browser/ui/webui/ash/crostini_upgrader/crostini_upgrader_ui.cc
+++ b/chrome/browser/ui/webui/ash/crostini_upgrader/crostini_upgrader_ui.cc
@@ -101,6 +101,7 @@
   source->AddResourcePath("images/error_illustration.png",
                           IDR_PLUGIN_VM_INSTALLER_ERROR);
   source->AddResourcePath("app.js", IDR_CROSTINI_UPGRADER_APP_JS);
+  source->AddResourcePath("app.html.js", IDR_CROSTINI_UPGRADER_APP_HTML_JS);
   source->AddResourcePath("browser_proxy.js",
                           IDR_CROSTINI_UPGRADER_BROWSER_PROXY_JS);
   source->AddResourcePath("crostini_upgrader.mojom-lite.js",
diff --git a/chrome/browser/ui/webui/feed_internals/BUILD.gn b/chrome/browser/ui/webui/feed_internals/BUILD.gn
index 8a9b06f..60e3578 100644
--- a/chrome/browser/ui/webui/feed_internals/BUILD.gn
+++ b/chrome/browser/ui/webui/feed_internals/BUILD.gn
@@ -8,6 +8,7 @@
 mojom("mojo_bindings") {
   sources = [ "feed_internals.mojom" ]
   webui_module_path = "/"
+  use_typescript_sources = true
 
   public_deps = [
     "//mojo/public/mojom/base",
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 8a75a45..548dc94 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -543,7 +543,7 @@
 }  // namespace
 
 NewTabPageUI::NewTabPageUI(content::WebUI* web_ui)
-    : ui::MojoWebUIController(web_ui, /*enable_chrome_send=*/false),
+    : ui::MojoWebUIController(web_ui, /*enable_chrome_send=*/true),
       content::WebContentsObserver(web_ui->GetWebContents()),
       page_factory_receiver_(this),
       customize_themes_factory_receiver_(this),
@@ -687,7 +687,13 @@
 void NewTabPageUI::BindInterface(
     mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler) {
   realbox_handler_ = std::make_unique<RealboxHandler>(
-      std::move(pending_page_handler), profile_, web_contents());
+      std::move(pending_page_handler), profile_, web_contents(),
+      &metrics_reporter_);
+}
+
+void NewTabPageUI::BindInterface(
+    mojo::PendingReceiver<metrics_reporter::mojom::PageMetricsHost> receiver) {
+  metrics_reporter_.BindInterface(std::move(receiver));
 }
 
 void NewTabPageUI::BindInterface(
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
index 9c8deda..fe09f34 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
@@ -22,6 +22,7 @@
 #include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_observer.h"
+#include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
 #include "components/omnibox/browser/omnibox.mojom-forward.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -36,6 +37,7 @@
 #include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
 #include "ui/webui/resources/cr_components/customize_themes/customize_themes.mojom.h"
 #include "ui/webui/resources/cr_components/most_visited/most_visited.mojom.h"
+#include "ui/webui/resources/js/metrics_reporter/metrics_reporter.mojom.h"
 
 namespace base {
 class RefCountedMemory;
@@ -107,6 +109,11 @@
   void BindInterface(
       mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler);
 
+  // Instantiates the implementor of metrics_reporter::mojom::PageMetricsHost
+  // mojo interface passing the pending receiver that will be internally bound.
+  void BindInterface(
+      mojo::PendingReceiver<metrics_reporter::mojom::PageMetricsHost> receiver);
+
   // Instantiates the implementor of the
   // browser_command::mojom::CommandHandlerFactory mojo interface passing
   // the pending receiver that will be internally bound.
@@ -223,6 +230,7 @@
   mojo::Receiver<browser_command::mojom::CommandHandlerFactory>
       browser_command_factory_receiver_;
   std::unique_ptr<RealboxHandler> realbox_handler_;
+  MetricsReporter metrics_reporter_;
 #if !defined(OFFICIAL_BUILD)
   std::unique_ptr<FooHandler> foo_handler_;
 #endif
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index 4b0a1f9f..78ce2c9 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -962,7 +962,7 @@
         break;
     }
 
-    web_app_provider_->sync_bridge().SetAppUserDisplayMode(
+    web_app_provider_->sync_bridge_unsafe().SetAppUserDisplayMode(
         app_id, user_display_mode, /*is_user_action=*/true);
     return;
   }
@@ -1118,8 +1118,10 @@
 
   InstallOsHooks(app_id);
 
-  web_app_provider_->sync_bridge().SetAppIsLocallyInstalled(app_id, true);
-  web_app_provider_->sync_bridge().SetAppInstallTime(app_id, base::Time::Now());
+  web_app_provider_->sync_bridge_unsafe().SetAppIsLocallyInstalled(app_id,
+                                                                   true);
+  web_app_provider_->sync_bridge_unsafe().SetAppInstallTime(app_id,
+                                                            base::Time::Now());
 }
 
 void AppLauncherHandler::HandleShowAppInfo(const base::Value::List& args) {
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler_unittest.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler_unittest.cc
index 2a5b859..ffd05d9 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler_unittest.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler_unittest.cc
@@ -111,10 +111,10 @@
       return installed_app_id;
 
     auto* web_app_provider = WebAppProvider::GetForTest(profile());
-    web_app_provider->sync_bridge().SetAppIsLocallyInstalled(installed_app_id,
-                                                             false);
-    web_app_provider->sync_bridge().SetAppInstallTime(installed_app_id,
-                                                      base::Time::Min());
+    web_app_provider->sync_bridge_unsafe().SetAppIsLocallyInstalled(
+        installed_app_id, false);
+    web_app_provider->sync_bridge_unsafe().SetAppInstallTime(installed_app_id,
+                                                             base::Time::Min());
     return installed_app_id;
   }
 
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index 2c74534..a797bb3 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/ui/bookmarks/bookmark_stats.h"
 #include "chrome/browser/ui/omnibox/omnibox_pedal_implementations.h"
 #include "chrome/browser/ui/search/omnibox_utils.h"
+#include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/new_tab_page_resources.h"
@@ -499,9 +500,11 @@
 RealboxHandler::RealboxHandler(
     mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler,
     Profile* profile,
-    content::WebContents* web_contents)
+    content::WebContents* web_contents,
+    MetricsReporter* metrics_reporter)
     : profile_(profile),
       web_contents_(web_contents),
+      metrics_reporter_(metrics_reporter),
       page_handler_(this, std::move(pending_page_handler)) {
   controller_emitter_observation_.Observe(
       OmniboxControllerEmitter::GetForBrowserContext(profile_));
@@ -581,6 +584,11 @@
   }
 
   AutocompleteMatch match(autocomplete_controller_->result().match_at(line));
+  if (match.action && match.action->TakesOverMatch()) {
+    return ExecuteAction(line, base::TimeTicks::Now(), mouse_button, alt_key,
+                         ctrl_key, meta_key, shift_key);
+  }
+
   if (match.destination_url != url) {
     // TODO(https://crbug.com/1020025): this could be malice or staleness.
     // Either way: don't navigate.
@@ -797,6 +805,10 @@
     return;
   }
 
+  if (metrics_reporter_ && !metrics_reporter_->HasLocalMark("ResultChanged")) {
+    metrics_reporter_->Mark("ResultChanged");
+  }
+
   page_->AutocompleteResultChanged(CreateAutocompleteResult(
       autocomplete_controller_->input().text(),
       autocomplete_controller_->result(),
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.h b/chrome/browser/ui/webui/realbox/realbox_handler.h
index 4d9086c..3b4c3f8 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.h
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.h
@@ -20,6 +20,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 
 class GURL;
+class MetricsReporter;
 class Profile;
 
 namespace content {
@@ -53,7 +54,8 @@
   RealboxHandler(
       mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler,
       Profile* profile,
-      content::WebContents* web_contents);
+      content::WebContents* web_contents,
+      MetricsReporter* metrics_reporter);
 
   RealboxHandler(const RealboxHandler&) = delete;
   RealboxHandler& operator=(const RealboxHandler&) = delete;
@@ -113,6 +115,7 @@
                           AutocompleteController::Observer>
       controller_emitter_observation_{this};
   base::TimeTicks time_user_first_modified_realbox_;
+  raw_ptr<MetricsReporter> metrics_reporter_;
 
   mojo::Remote<omnibox::mojom::Page> page_;
   mojo::Receiver<omnibox::mojom::PageHandler> page_handler_;
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler_browsertest.cc b/chrome/browser/ui/webui/realbox/realbox_handler_browsertest.cc
index 653ad01..875877c 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler_browsertest.cc
@@ -117,7 +117,7 @@
 
   const scoped_refptr<OmniboxAction> history_clusters_action =
       base::MakeRefCounted<history_clusters::HistoryClustersAction>(
-          "test", history::ClusterKeywordData());
+          "test", history::ClusterKeywordData(), /*takes_over_match=*/false);
   const gfx::VectorIcon& vector_icon = history_clusters_action->GetVectorIcon();
   const std::string& svg_name =
       RealboxHandler::PedalVectorIconToResourceName(vector_icon);
@@ -162,9 +162,9 @@
 IN_PROC_BROWSER_TEST_F(RealboxSearchPreloadBrowserTest, SearchPreloadSuccess) {
   mojo::Remote<omnibox::mojom::PageHandler> remote_page_handler;
   RealboxSearchBrowserTestPage page;
-  RealboxHandler realbox_handler =
-      RealboxHandler(remote_page_handler.BindNewPipeAndPassReceiver(),
-                     browser()->profile(), GetWebContents());
+  RealboxHandler realbox_handler = RealboxHandler(
+      remote_page_handler.BindNewPipeAndPassReceiver(), browser()->profile(),
+      GetWebContents(), /*metrics_reporter=*/nullptr);
   realbox_handler.SetPage(page.GetRemotePage());
   content::test::PrerenderHostRegistryObserver registry_observer(
       *GetWebContents());
diff --git a/chrome/browser/ui/webui/settings/ash/accessibility_handler_browsertest.cc b/chrome/browser/ui/webui/settings/ash/accessibility_handler_browsertest.cc
index 1fe56bfb..7c98b2f 100644
--- a/chrome/browser/ui/webui/settings/ash/accessibility_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/ash/accessibility_handler_browsertest.cc
@@ -290,10 +290,10 @@
       GetWebUIListenerArgumentListValue("dictation-locales-set", argument));
 
   for (auto& it : *argument) {
-    const base::DictionaryValue* dict = &base::Value::AsDictionaryValue(it);
-    const std::string locale = *(dict->FindStringPath("value"));
-    bool works_offline = dict->FindBoolKey("worksOffline").value();
-    bool installed = dict->FindBoolKey("installed").value();
+    const base::Value::Dict& dict = it.GetDict();
+    const std::string locale = *dict.FindString("value");
+    bool works_offline = dict.FindBool("worksOffline").value();
+    bool installed = dict.FindBool("installed").value();
     if (locale == speech::kUsEnglishLocale) {
       ASSERT_TRUE(works_offline);
       ASSERT_TRUE(installed);
diff --git a/chrome/browser/ui/webui/settings/ash/account_manager_ui_handler_browsertest.cc b/chrome/browser/ui/webui/settings/ash/account_manager_ui_handler_browsertest.cc
index c6481c3..68b94dcd 100644
--- a/chrome/browser/ui/webui/settings/ash/account_manager_ui_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/ash/account_manager_ui_handler_browsertest.cc
@@ -306,25 +306,25 @@
   ASSERT_EQ(account_manager_accounts.size(), result.size());
 
   // Check first (device) account.
-  const base::Value& device_account = result[0];
-  EXPECT_TRUE(device_account.FindBoolKey("isDeviceAccount").value());
-  EXPECT_TRUE(device_account.FindBoolKey("isSignedIn").value());
-  EXPECT_FALSE(device_account.FindBoolKey("unmigrated").value());
+  const base::Value::Dict& device_account = result[0].GetDict();
+  EXPECT_TRUE(device_account.FindBool("isDeviceAccount").value());
+  EXPECT_TRUE(device_account.FindBool("isSignedIn").value());
+  EXPECT_FALSE(device_account.FindBool("unmigrated").value());
   EXPECT_EQ(static_cast<int>(GetDeviceAccountInfo().account_type),
-            device_account.FindIntKey("accountType"));
+            device_account.FindInt("accountType"));
   EXPECT_EQ(GetDeviceAccountInfo().email,
-            ValueOrEmpty(device_account.FindStringKey("email")));
+            ValueOrEmpty(device_account.FindString("email")));
   EXPECT_EQ(GetDeviceAccountInfo().id,
-            ValueOrEmpty(device_account.FindStringKey("id")));
+            ValueOrEmpty(device_account.FindString("id")));
   if (GetDeviceAccountInfo().user_type ==
       user_manager::UserType::USER_TYPE_CHILD) {
     std::string organization = GetDeviceAccountInfo().organization;
     base::ReplaceSubstringsAfterOffset(&organization, 0, " ", "&nbsp;");
     EXPECT_EQ(organization,
-              ValueOrEmpty(device_account.FindStringKey("organization")));
+              ValueOrEmpty(device_account.FindString("organization")));
   } else {
     EXPECT_EQ(GetDeviceAccountInfo().organization,
-              ValueOrEmpty(device_account.FindStringKey("organization")));
+              ValueOrEmpty(device_account.FindString("organization")));
   }
 }
 
@@ -354,60 +354,61 @@
   ASSERT_EQ(account_manager_accounts.size(), result.size());
 
   // Check first (device) account.
-  const base::Value& device_account = result[0];
-  EXPECT_TRUE(device_account.FindBoolKey("isDeviceAccount").value());
-  EXPECT_TRUE(device_account.FindBoolKey("isSignedIn").value());
-  EXPECT_FALSE(device_account.FindBoolKey("unmigrated").value());
+  const base::Value::Dict& device_account = result[0].GetDict();
+  EXPECT_TRUE(device_account.FindBool("isDeviceAccount").value());
+  EXPECT_TRUE(device_account.FindBool("isSignedIn").value());
+  EXPECT_FALSE(device_account.FindBool("unmigrated").value());
   EXPECT_EQ(static_cast<int>(GetDeviceAccountInfo().account_type),
-            device_account.FindIntKey("accountType"));
+            device_account.FindInt("accountType"));
   EXPECT_EQ(GetDeviceAccountInfo().email,
-            ValueOrEmpty(device_account.FindStringKey("email")));
+            ValueOrEmpty(device_account.FindString("email")));
   EXPECT_EQ(GetDeviceAccountInfo().id,
-            ValueOrEmpty(device_account.FindStringKey("id")));
+            ValueOrEmpty(device_account.FindString("id")));
   if (GetDeviceAccountInfo().user_type ==
       user_manager::UserType::USER_TYPE_CHILD) {
     std::string organization = GetDeviceAccountInfo().organization;
     base::ReplaceSubstringsAfterOffset(&organization, 0, " ", "&nbsp;");
     EXPECT_EQ(organization,
-              ValueOrEmpty(device_account.FindStringKey("organization")));
+              ValueOrEmpty(device_account.FindString("organization")));
   } else {
     EXPECT_EQ(GetDeviceAccountInfo().organization,
-              ValueOrEmpty(device_account.FindStringKey("organization")));
+              ValueOrEmpty(device_account.FindString("organization")));
   }
 
   // Check secondary accounts.
-  for (const base::Value& account : result) {
-    if (ValueOrEmpty(account.FindStringKey("id")) == GetDeviceAccountInfo().id)
+  for (const base::Value& account_value : result) {
+    const base::Value::Dict& account = account_value.GetDict();
+    if (ValueOrEmpty(account.FindString("id")) == GetDeviceAccountInfo().id)
       continue;
-    EXPECT_FALSE(account.FindBoolKey("isDeviceAccount").value());
+    EXPECT_FALSE(account.FindBool("isDeviceAccount").value());
 
     ::account_manager::Account expected_account =
         GetAccountByKey(account_manager_accounts,
-                        {ValueOrEmpty(account.FindStringKey("id")),
+                        {ValueOrEmpty(account.FindString("id")),
                          account_manager::AccountType::kGaia})
             .value();
     if (GetDeviceAccountInfo().user_type ==
         user_manager::UserType::USER_TYPE_CHILD) {
-      EXPECT_FALSE(account.FindBoolKey("unmigrated").value());
+      EXPECT_FALSE(account.FindBool("unmigrated").value());
     } else {
       EXPECT_EQ(HasDummyGaiaToken(expected_account.key),
-                account.FindBoolKey("unmigrated").value());
+                account.FindBool("unmigrated").value());
     }
     EXPECT_EQ(static_cast<int>(expected_account.key.account_type()),
-              account.FindIntKey("accountType"));
+              account.FindInt("accountType"));
     EXPECT_EQ(expected_account.raw_email,
-              ValueOrEmpty(account.FindStringKey("email")));
+              ValueOrEmpty(account.FindString("email")));
 
     AccountInfo expected_account_info =
         identity_manager()->FindExtendedAccountInfoByGaiaId(
             expected_account.key.id());
     EXPECT_FALSE(expected_account_info.IsEmpty());
     EXPECT_EQ(expected_account_info.full_name,
-              ValueOrEmpty(account.FindStringKey("fullName")));
+              ValueOrEmpty(account.FindString("fullName")));
     EXPECT_EQ(
         !identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
             expected_account_info.account_id),
-        account.FindBoolKey("isSignedIn").value());
+        account.FindBool("isSignedIn").value());
   }
 }
 
@@ -479,7 +480,7 @@
       const base::Value::List& accounts,
       const std::string& email) {
     for (const base::Value& account : accounts) {
-      if (ValueOrEmpty(account.FindStringKey("email")) == email)
+      if (ValueOrEmpty(account.GetDict().FindString("email")) == email)
         return account.Clone();
     }
     return absl::nullopt;
@@ -536,8 +537,8 @@
   ASSERT_EQ(account_manager_accounts.size(), result.size());
 
   // The value for the device account should be always `true`.
-  const base::Value& device_account = result[0];
-  EXPECT_TRUE(device_account.FindBoolKey("isAvailableInArc").value());
+  const base::Value::Dict& device_account = result[0].GetDict();
+  EXPECT_TRUE(device_account.FindBool("isAvailableInArc").value());
 
   // Check secondary accounts.
   absl::optional<const base::Value> secondary_1_dict =
@@ -548,10 +549,10 @@
   ASSERT_TRUE(secondary_2_dict.has_value());
 
   absl::optional<bool> is_available_1 =
-      secondary_1_dict.value().FindBoolKey("isAvailableInArc");
+      secondary_1_dict.value().GetDict().FindBool("isAvailableInArc");
   ASSERT_TRUE(is_available_1.has_value());
   absl::optional<bool> is_available_2 =
-      secondary_2_dict.value().FindBoolKey("isAvailableInArc");
+      secondary_2_dict.value().GetDict().FindBool("isAvailableInArc");
   ASSERT_TRUE(is_available_2.has_value());
 
   // The values should match `SetIsAccountAvailableInArc` calls.
diff --git a/chrome/browser/ui/webui/settings/ash/crostini_handler.cc b/chrome/browser/ui/webui/settings/ash/crostini_handler.cc
index 2de1adb..40f69ab2 100644
--- a/chrome/browser/ui/webui/settings/ash/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/crostini_handler.cc
@@ -821,7 +821,7 @@
   CHECK_EQ(2U, args.size());
 
   guest_os::GuestId container_id(args[0]);
-  SkColor badge_color(args[1].FindDoubleKey("value").value());
+  SkColor badge_color(args[1].GetDict().FindDouble("value").value());
 
   crostini::SetContainerBadgeColor(profile_, container_id, badge_color);
 }
diff --git a/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
index 336268169..58c7cdd 100644
--- a/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
@@ -653,8 +653,8 @@
 
   if (!success) {
     PRINTER_LOG(DEBUG) << "Could not query printer";
-    base::DictionaryValue reject;
-    reject.SetStringKey("message", "Querying printer failed");
+    base::Value::Dict reject;
+    reject.Set("message", "Querying printer failed");
     RejectJavascriptCallback(base::Value(callback_id),
                              base::Value(PrinterSetupResult::kFatalError));
     return;
@@ -1226,10 +1226,11 @@
     RejectJavascriptCallback(base::Value(callback_id), base::Value());
     return;
   }
-  base::DictionaryValue info;
-  info.SetStringKey("ppdManufacturer", manufacturer);
-  info.SetStringKey("ppdModel", model);
-  ResolveJavascriptCallback(base::Value(callback_id), info);
+  base::Value::Dict info;
+  info.Set("ppdManufacturer", manufacturer);
+  info.Set("ppdModel", model);
+  ResolveJavascriptCallback(base::Value(callback_id),
+                            base::Value(std::move(info)));
 }
 
 void CupsPrintersHandler::HandleGetEulaUrl(const base::Value::List& args) {
diff --git a/chrome/browser/ui/webui/settings/ash/device_keyboard_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/device_keyboard_handler_unittest.cc
index 7cf45e1b..abae8e5 100644
--- a/chrome/browser/ui/webui/settings/ash/device_keyboard_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_keyboard_handler_unittest.cc
@@ -63,12 +63,11 @@
         continue;
       }
 
-      if (!data->arg2() ||
-          data->arg2()->type() != base::Value::Type::DICTIONARY) {
+      if (!data->arg2() || !data->arg2()->is_dict()) {
         return false;
       }
 
-      const base::Value* keyboard_params = data->arg2();
+      const base::Value::Dict& keyboard_params = data->arg2()->GetDict();
       const std::vector<std::pair<std::string, bool*>> path_to_out_param = {
           {"showCapsLock", has_caps_lock_out},
           {"showExternalMetaKey", has_external_meta_key_out},
@@ -78,7 +77,7 @@
       };
 
       for (const auto& pair : path_to_out_param) {
-        auto* found = keyboard_params->FindKey(pair.first);
+        auto* found = keyboard_params.Find(pair.first);
         if (!found)
           return false;
 
diff --git a/chrome/browser/ui/webui/settings/ash/device_power_handler.cc b/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
index cbb1bc4..0e7c59d 100644
--- a/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
@@ -414,12 +414,11 @@
   }
 
   base::Value::Dict dict;
-  base::Value* list =
-      dict.Set(kPossibleAcIdleBehaviorsKey, base::Value::List());
+  base::Value::List* list = dict.EnsureList(kPossibleAcIdleBehaviorsKey);
   for (auto idle_behavior : ac_idle_info.possible_behaviors)
     list->Append(static_cast<int>(idle_behavior));
 
-  list = dict.Set(kPossibleBatteryIdleBehaviorsKey, base::Value::List());
+  list = dict.EnsureList(kPossibleBatteryIdleBehaviorsKey);
   for (auto idle_behavior : battery_idle_info.possible_behaviors)
     list->Append(static_cast<int>(idle_behavior));
   dict.Set(kCurrentAcIdleBehaviorKey,
diff --git a/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
index beefa87..f34193d 100644
--- a/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
@@ -143,7 +143,7 @@
     const base::Value* dictionary =
         GetWebUICallbackMessage("storage-size-stat-changed");
     EXPECT_TRUE(dictionary) << "No 'storage-size-stat-changed' callback";
-    int space_state = dictionary->FindKey("spaceState")->GetInt();
+    int space_state = *dictionary->GetDict().FindInt("spaceState");
     return space_state;
   }
 
@@ -259,16 +259,16 @@
   free_disk_space_test_api_->StartCalculation();
   task_environment_.RunUntilIdle();
 
-  const base::Value* dictionary =
+  const base::Value* dictionary_value =
       GetWebUICallbackMessage("storage-size-stat-changed");
-  ASSERT_TRUE(dictionary) << "No 'storage-size-stat-changed' callback";
+  ASSERT_TRUE(dictionary_value) << "No 'storage-size-stat-changed' callback";
+  const base::Value::Dict& dictionary = dictionary_value->GetDict();
 
   const std::string& storage_handler_available_size =
-      dictionary->FindKey("availableSize")->GetString();
+      *dictionary.FindString("availableSize");
   const std::string& storage_handler_used_size =
-      dictionary->FindKey("usedSize")->GetString();
-  double storage_handler_used_ratio =
-      dictionary->FindKey("usedRatio")->GetDouble();
+      *dictionary.FindString("usedSize");
+  double storage_handler_used_ratio = *dictionary.FindDouble("usedRatio");
 
   EXPECT_EQ(ui::FormatBytes(available_size),
             base::ASCIIToUTF16(storage_handler_available_size));
@@ -421,8 +421,8 @@
   const base::Value* callback =
       GetWebUICallbackMessage("storage-size-stat-changed");
   ASSERT_TRUE(callback) << "No 'storage-size-stat-changed' callback";
-  EXPECT_EQ("100 GB", callback->FindKey("availableSize")->GetString());
-  EXPECT_EQ("924 GB", callback->FindKey("usedSize")->GetString());
+  EXPECT_EQ("100 GB", *callback->GetDict().FindString("availableSize"));
+  EXPECT_EQ("924 GB", *callback->GetDict().FindString("usedSize"));
   // Expect no system size callback until every other item has been updated.
   ASSERT_FALSE(GetWebUICallbackMessage("storage-system-size-changed"));
 
diff --git a/chrome/browser/ui/webui/settings/ash/fast_pair_saved_devices_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/fast_pair_saved_devices_handler_unittest.cc
index ca3a948..6f5297e 100644
--- a/chrome/browser/ui/webui/settings/ash/fast_pair_saved_devices_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/fast_pair_saved_devices_handler_unittest.cc
@@ -261,13 +261,13 @@
   }
 
   void LoadPage() {
-    // `HandleReceivedMessages` has to use a ListValue due to the API.
+    // `HandleReceivedMessages` has to use a Value::List due to the API.
     base::Value::List args;
     test_web_ui()->HandleReceivedMessage(kLoadSavedDevicePage, args);
   }
 
   void RemoveDevice(const std::vector<uint8_t>& account_key) {
-    // `HandleReceivedMessages` has to use a ListValue due to the API.
+    // `HandleReceivedMessages` has to use a Value::List due to the API.
     base::Value::List args;
     args.Append(EncodeKey(account_key));
     test_web_ui()->HandleReceivedMessage(kRemoveSavedDevice, args);
diff --git a/chrome/browser/ui/webui/settings/ash/metrics_consent_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/metrics_consent_handler_unittest.cc
index 4e239bb..113263d 100644
--- a/chrome/browser/ui/webui/settings/ash/metrics_consent_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/metrics_consent_handler_unittest.cc
@@ -93,20 +93,20 @@
   ~TestMetricsConsentHandler() override = default;
 
   void GetMetricsConsentState() {
-    base::ListValue args;
-    args.Append(base::Value("callback-id"));
-    HandleGetMetricsConsentState(args.GetList());
+    base::Value::List args;
+    args.Append("callback-id");
+    HandleGetMetricsConsentState(args);
   }
 
   void UpdateMetricsConsent(bool metrics_consent) {
-    base::ListValue args;
-    args.Append(base::Value("callback-id"));
+    base::Value::List args;
+    args.Append("callback-id");
 
     base::Value::Dict dict;
     dict.Set("consent", metrics_consent);
     args.Append(std::move(dict));
 
-    HandleUpdateMetricsConsent(args.GetList());
+    HandleUpdateMetricsConsent(args);
   }
 };
 
@@ -216,15 +216,13 @@
         continue;
       }
 
-      if (!data->arg3() ||
-          data->arg3()->type() != base::Value::Type::DICTIONARY) {
+      if (!data->arg3() || !data->arg3()->is_dict()) {
         return false;
       }
 
-      const base::Value* metrics_consent_state = data->arg3();
-      *pref_name = metrics_consent_state->FindKey("prefName")->GetString();
-      *is_configurable =
-          metrics_consent_state->FindKey("isConfigurable")->GetBool();
+      const base::Value::Dict& metrics_consent_state = data->arg3()->GetDict();
+      *pref_name = *metrics_consent_state.FindString("prefName");
+      *is_configurable = *metrics_consent_state.FindBool("isConfigurable");
 
       return true;
     }
diff --git a/chrome/browser/ui/webui/settings/ash/multidevice_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/multidevice_handler_unittest.cc
index b021a1c..0024182b 100644
--- a/chrome/browser/ui/webui/settings/ash/multidevice_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/multidevice_handler_unittest.cc
@@ -381,9 +381,9 @@
     ASSERT_TRUE(call_data.arg2()->GetBool());
     EXPECT_EQ(
         ContentSettingsPattern::FromURLNoWildcard(expected_url).ToString(),
-        call_data.arg3()->FindKey("origin")->GetString());
+        *call_data.arg3()->GetDict().FindString("origin"));
     EXPECT_EQ(expected_enabled,
-              call_data.arg3()->FindKey("enabled")->GetBool());
+              *call_data.arg3()->GetDict().FindBool("enabled"));
   }
 
   void CallAttemptNotificationSetup(bool has_notification_access_been_granted) {
diff --git a/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc
index 2c8680fd..40a08e4 100644
--- a/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc
@@ -63,9 +63,9 @@
   absl::optional<base::Value> json = base::JSONReader::Read(voice_id);
   std::string name;
   std::string extension_id;
-  if (const std::string* ptr = json->FindStringKey("name"))
+  if (const std::string* ptr = json->GetDict().FindString("name"))
     name = *ptr;
-  if (const std::string* ptr = json->FindStringKey("extension"))
+  if (const std::string* ptr = json->GetDict().FindString("extension"))
     extension_id = *ptr;
 
   std::unique_ptr<content::TtsUtterance> utterance =
diff --git a/chrome/browser/web_applications/app_service/web_apps.cc b/chrome/browser/web_applications/app_service/web_apps.cc
index 4461a34b..129738ec 100644
--- a/chrome/browser/web_applications/app_service/web_apps.cc
+++ b/chrome/browser/web_applications/app_service/web_apps.cc
@@ -257,12 +257,6 @@
   publisher_helper().OpenNativeSettings(app_id);
 }
 
-void WebApps::SetWindowMode(const std::string& app_id,
-                            apps::mojom::WindowMode window_mode) {
-  publisher_helper().SetWindowMode(
-      app_id, apps::ConvertMojomWindowModeToWindowMode(window_mode));
-}
-
 void WebApps::PublishWebApps(std::vector<apps::AppPtr> apps) {
   if (!is_ready_) {
     return;
diff --git a/chrome/browser/web_applications/app_service/web_apps.h b/chrome/browser/web_applications/app_service/web_apps.h
index 2a6f7be..43cddcfa4 100644
--- a/chrome/browser/web_applications/app_service/web_apps.h
+++ b/chrome/browser/web_applications/app_service/web_apps.h
@@ -141,8 +141,6 @@
   void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
                apps::mojom::ConnectOptionsPtr opts) override;
   void OpenNativeSettings(const std::string& app_id) override;
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode) override;
 
   // WebAppPublisherHelper::Delegate overrides.
   void PublishWebApps(std::vector<apps::AppPtr> apps) override;
diff --git a/chrome/browser/web_applications/daily_metrics_helper.cc b/chrome/browser/web_applications/daily_metrics_helper.cc
index 0c09df8..4d1e4e8 100644
--- a/chrome/browser/web_applications/daily_metrics_helper.cc
+++ b/chrome/browser/web_applications/daily_metrics_helper.cc
@@ -63,8 +63,6 @@
 namespace {
 
 using absl::optional;
-using base::DictionaryValue;
-using base::Value;
 
 bool skip_origin_check_for_testing_ = false;
 
@@ -120,19 +118,19 @@
   return record;
 }
 
-std::unique_ptr<DictionaryValue> RecordToDict(DailyInteraction& record) {
-  auto record_dict = std::make_unique<DictionaryValue>();
+base::Value::Dict RecordToDict(DailyInteraction& record) {
+  base::Value::Dict record_dict;
   // Note URL is not set here as it is the key for this dict in its parent.
-  record_dict->SetBoolKey(kInstalled, record.installed);
+  record_dict.Set(kInstalled, record.installed);
   if (record.install_source.has_value())
-    record_dict->SetIntKey(kInstallSource, *record.install_source);
-  record_dict->SetIntKey(kEffectiveDisplayMode, record.effective_display_mode);
-  record_dict->SetBoolKey(kPromotable, record.promotable);
-  record_dict->SetIntKey(kForegroundDurationSec,
-                         record.foreground_duration.InSeconds());
-  record_dict->SetIntKey(kBackgroundDurationSec,
-                         record.background_duration.InSeconds());
-  record_dict->SetIntKey(kNumSessions, record.num_sessions);
+    record_dict.Set(kInstallSource, *record.install_source);
+  record_dict.Set(kEffectiveDisplayMode, record.effective_display_mode);
+  record_dict.Set(kPromotable, record.promotable);
+  record_dict.Set(kForegroundDurationSec,
+                  static_cast<int>(record.foreground_duration.InSeconds()));
+  record_dict.Set(kBackgroundDurationSec,
+                  static_cast<int>(record.background_duration.InSeconds()));
+  record_dict.Set(kNumSessions, record.num_sessions);
   return record_dict;
 }
 
@@ -164,7 +162,7 @@
 
   for (const auto iter : urls_to_features) {
     const std::string& url = iter.first;
-    const Value& val = iter.second;
+    const base::Value& val = iter.second;
     optional<DailyInteraction> record = DictToRecord(url, val.GetDict());
     if (record)
       EmitRecord(*record, profile);
@@ -192,10 +190,10 @@
     }
   }
 
-  std::unique_ptr<DictionaryValue> record_dict = RecordToDict(record);
+  base::Value::Dict record_dict = RecordToDict(record);
   ScopedDictPrefUpdate update(prefs, prefs::kWebAppsDailyMetrics);
 
-  update->Set(url, std::move(*record_dict));
+  update->Set(url, std::move(record_dict));
 }
 
 }  // namespace
diff --git a/chrome/browser/web_applications/externally_installed_web_app_prefs_browsertest.cc b/chrome/browser/web_applications/externally_installed_web_app_prefs_browsertest.cc
index 378ff25..0d3e6e8 100644
--- a/chrome/browser/web_applications/externally_installed_web_app_prefs_browsertest.cc
+++ b/chrome/browser/web_applications/externally_installed_web_app_prefs_browsertest.cc
@@ -69,17 +69,35 @@
 
 class ExternallyInstalledWebAppPrefsBrowserTest_ExternalPrefMigration
     : public ExternallyInstalledWebAppPrefsBrowserTest,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<test::ExternalPrefMigrationTestCases> {
  public:
   ExternallyInstalledWebAppPrefsBrowserTest_ExternalPrefMigration() {
-    bool enable_migration = GetParam();
-    if (enable_migration) {
-      scoped_feature_list_.InitWithFeatures(
-          {features::kUseWebAppDBInsteadOfExternalPrefs}, {});
-    } else {
-      scoped_feature_list_.InitWithFeatures(
-          {}, {features::kUseWebAppDBInsteadOfExternalPrefs});
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    switch (GetParam()) {
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
     }
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
  private:
@@ -708,6 +726,11 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     ExternallyInstalledWebAppPrefsBrowserTest_ExternalPrefMigration,
-    ::testing::Bool());
+    ::testing::Values(
+        test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+        test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+        test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+        test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB),
+    test::GetExternalPrefMigrationTestName);
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/test/web_app_test_utils.cc b/chrome/browser/web_applications/test/web_app_test_utils.cc
index a76bd343..124d749 100644
--- a/chrome/browser/web_applications/test/web_app_test_utils.cc
+++ b/chrome/browser/web_applications/test/web_app_test_utils.cc
@@ -312,6 +312,20 @@
 
 }  // namespace
 
+std::string GetExternalPrefMigrationTestName(
+    const ::testing::TestParamInfo<ExternalPrefMigrationTestCases>& info) {
+  switch (info.param) {
+    case ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+      return "DisableMigration_ReadFromPrefs";
+    case ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+      return "DisableMigration_ReadFromDB";
+    case ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+      return "EnableMigration_ReadFromPrefs";
+    case ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+      return "EnableMigration_ReadFromDB";
+  }
+}
+
 std::unique_ptr<WebApp> CreateWebApp(const GURL& start_url,
                                      WebAppManagement::Type source_type) {
   const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, start_url);
diff --git a/chrome/browser/web_applications/test/web_app_test_utils.h b/chrome/browser/web_applications/test/web_app_test_utils.h
index 5a5edc9..fb222afd 100644
--- a/chrome/browser/web_applications/test/web_app_test_utils.h
+++ b/chrome/browser/web_applications/test/web_app_test_utils.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 #include <memory>
+#include <string>
 
 #include "base/strings/string_piece_forward.h"
 #include "build/chromeos_buildflags.h"
@@ -16,6 +17,7 @@
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/service_worker_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
 class Browser;
@@ -33,6 +35,16 @@
 
 namespace test {
 
+enum class ExternalPrefMigrationTestCases {
+  kDisableMigrationReadPref,
+  kDisableMigrationReadDB,
+  kEnableMigrationReadPref,
+  kEnableMigrationReadDB,
+};
+
+std::string GetExternalPrefMigrationTestName(
+    const ::testing::TestParamInfo<ExternalPrefMigrationTestCases>& info);
+
 // Do not use this for installation! Instead, use the utilities in
 // web_app_install_test_util.h.
 std::unique_ptr<WebApp> CreateWebApp(
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 3489fd2..4553e38 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -357,8 +357,7 @@
 void WebAppProvider::OnSyncBridgeReady() {
   DCHECK(!on_registry_ready_.is_signaled());
 
-  if (base::FeatureList::IsEnabled(
-          features::kUseWebAppDBInsteadOfExternalPrefs)) {
+  if (base::FeatureList::IsEnabled(features::kMigrateExternalPrefsToWebAppDB)) {
     ExternallyInstalledWebAppPrefs::MigrateExternalPrefData(
         profile_->GetPrefs(), sync_bridge_.get());
   }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index b086fa0..ead0e0f 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1670817917-d18ae1e46e627b72b6e0e2b206b3c943b6118f61.profdata
+chrome-linux-main-1670889566-1fec7648febd165c2cc50850d889d9e2c11c2e87.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 9981352f..50a6a4be 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1670817917-c1646207fc155d2a302d0aa7240323d98c6b281c.profdata
+chrome-mac-arm-main-1670867915-f9acec514e2db21e8cb162d5f7eff165da735b7b.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index aa93fd1..cef09c3 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1670867915-974611ceb0aae82f69b3414c56ce0ab9167bb70c.profdata
+chrome-mac-main-1670889566-42ea1512f5afbd78bcbcc3aa2936c8335d141ebf.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index b3d190df..47f9412 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1670817917-2ff239d7f455bf002c1f3af4e1638d37e5e1d2b5.profdata
+chrome-win32-main-1670878747-2b16e54bce8674b59280de7680dd81eeafe1f39a.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b6cfc47e..7598acd 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1670867915-0780691e740e6cd0d08a7857afa68ce8aa0a250d.profdata
+chrome-win64-main-1670889566-c90f50899c0d4f6ffe2d2ba211447e26103d0a1d.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 640eb6f7..24d4f8d 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -806,6 +806,10 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
+BASE_FEATURE(kMigrateExternalPrefsToWebAppDB,
+             "MigrateExternalPrefsToWebAppDB",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kMoveWebApp,
              "MoveWebApp",
              base::FeatureState::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 9cb8249..3fd65d4 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -461,6 +461,9 @@
 BASE_DECLARE_FEATURE(kMicrosoftOfficeWebAppExperiment);
 #endif
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+BASE_DECLARE_FEATURE(kMigrateExternalPrefsToWebAppDB);
+
 COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kMoveWebApp);
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::FeatureParam<std::string> kMoveWebAppUninstallStartUrlPrefix;
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 27ebbc624..314d831b 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2330,6 +2330,11 @@
     "profile.isolated_web_app.install.forcelist";
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_WIN)
+// The integer value of the CloudAPAuthEnabled policy.
+const char kCloudApAuthEnabled[] = "auth.cloud_ap_auth.enabled";
+#endif  // BUILDFLAG(IS_WIN)
+
 // Boolean that specifies whether to enable revocation checking (best effort)
 // by default.
 const char kCertRevocationCheckingEnabled[] = "ssl.rev_checking.enabled";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index b29a12b..e3b12b2 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -875,6 +875,10 @@
 extern const char kIsolatedWebAppInstallForceList[];
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_WIN)
+extern const char kCloudApAuthEnabled[];
+#endif  // BUILDFLAG(IS_WIN)
+
 extern const char kCertRevocationCheckingEnabled[];
 extern const char kCertRevocationCheckingRequiredLocalAnchors[];
 extern const char kSSLVersionMin[];
diff --git a/chrome/services/file_util/single_file_tar_file_extractor_unittest.cc b/chrome/services/file_util/single_file_tar_file_extractor_unittest.cc
index 2d47685..46e68ae 100644
--- a/chrome/services/file_util/single_file_tar_file_extractor_unittest.cc
+++ b/chrome/services/file_util/single_file_tar_file_extractor_unittest.cc
@@ -133,10 +133,10 @@
   EXPECT_EQ(chrome::file_util::mojom::ExtractionResult::kGenericError, result);
 }
 
-// Verify that a .tar file with a 0 byte file works.
 TEST_F(SingleFileTarFileExtractorTest, ZeroByteFile) {
   base::test::TestFuture<chrome::file_util::mojom::ExtractionResult> future;
 
+  // Use a tar.xz containing an empty file.
   base::FilePath path;
   ASSERT_NO_FATAL_FAILURE(path = GetFilePath("empty_file.tar"));
   base::File src_file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
diff --git a/chrome/services/file_util/single_file_tar_xz_file_extractor_unittest.cc b/chrome/services/file_util/single_file_tar_xz_file_extractor_unittest.cc
index f428f1c..5b238644 100644
--- a/chrome/services/file_util/single_file_tar_xz_file_extractor_unittest.cc
+++ b/chrome/services/file_util/single_file_tar_xz_file_extractor_unittest.cc
@@ -112,6 +112,7 @@
 TEST_F(SingleFileTarXzFileExtractorTest, ZeroByteFile) {
   base::test::TestFuture<chrome::file_util::mojom::ExtractionResult> future;
 
+  // Use a tar.xz containing an empty file.
   base::FilePath path;
   ASSERT_NO_FATAL_FAILURE(path = GetFilePath("empty_file.tar.xz"));
   base::File src_file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 34406a6a..2f1a1e2 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1969,6 +1969,7 @@
       "../browser/optimization_guide/page_content_annotations_service_browsertest.cc",
       "../browser/optimization_guide/page_text_observer_browsertest.cc",
       "../browser/optimization_guide/prediction/prediction_manager_browsertest.cc",
+      "../browser/optimization_guide/prediction/prediction_model_store_browsertest.cc",
       "../browser/origin_trials/origin_trials_browsertest.cc",
       "../browser/page_load_metrics/observers/ad_metrics/ad_density_intervention_browsertest.cc",
       "../browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc",
@@ -2565,6 +2566,7 @@
       sources += [
         "../browser/chrome_browser_main_win_browsertest.cc",
         "../browser/chrome_main_process_singleton_browsertest.cc",
+        "../browser/enterprise/platform_auth/platform_auth_policy_observer_browsertest.cc",
         "../browser/enterprise/platform_auth/platform_auth_provider_manager_browsertest.cc",
         "../browser/font_prewarmer_tab_helper_browsertest.cc",
         "../browser/headless/headless_mode_browsertest_win.cc",
@@ -6016,7 +6018,7 @@
     "//components/reporting/encryption:test_support",
     "//components/reporting/encryption:testing_primitives",
     "//components/reporting/encryption:verification",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/storage:storage_uploader_interface",
     "//components/reporting/storage:test_support",
     "//components/reporting/storage_selector:storage_selector",
@@ -7391,10 +7393,13 @@
       "../browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc",
       "../browser/ash/app_list/search/search_controller_impl_unittest.cc",
       "../browser/ash/app_list/search/search_metrics_manager_unittest.cc",
+      "../browser/ash/app_list/search/search_session_metrics_manager_unittest.cc",
       "../browser/ash/app_list/search/test/ranking_test_util.cc",
       "../browser/ash/app_list/search/test/ranking_test_util.h",
       "../browser/ash/app_list/search/test/search_controller_test_util.cc",
       "../browser/ash/app_list/search/test/search_controller_test_util.h",
+      "../browser/ash/app_list/search/test/search_metrics_test_util.cc",
+      "../browser/ash/app_list/search/test/search_metrics_test_util.h",
       "../browser/ash/app_list/search/test/test_ranker_manager.cc",
       "../browser/ash/app_list/search/test/test_ranker_manager.h",
       "../browser/ash/app_list/search/test/test_search_controller.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
index 4706298..867aea73 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeTabbedActivityTestRule.java
@@ -54,8 +54,9 @@
 
     public void resumeMainActivityFromLauncher() throws Exception {
         Assert.assertNotNull(getActivity());
-        Assert.assertEquals(
-                ApplicationStatus.getStateForActivity(getActivity()), ActivityState.STOPPED);
+        Assert.assertTrue(
+                ApplicationStatus.getStateForActivity(getActivity()) == ActivityState.STOPPED
+                || ApplicationStatus.getStateForActivity(getActivity()) == ActivityState.PAUSED);
 
         Intent launchIntent = getActivity().getPackageManager().getLaunchIntentForPackage(
                 getActivity().getPackageName());
diff --git a/chrome/test/base/web_ui_test_data_source.cc b/chrome/test/base/web_ui_test_data_source.cc
index 7da6915f..44777ec 100644
--- a/chrome/test/base/web_ui_test_data_source.cc
+++ b/chrome/test/base/web_ui_test_data_source.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/test/base/web_ui_test_data_source.h"
 
+#include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/data/grit/webui_generated_test_resources.h"
 #include "chrome/test/data/grit/webui_generated_test_resources_map.h"
@@ -17,7 +18,7 @@
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIWebUITestHost);
 
-  source->DisableTrustedTypesCSP();
+  webui::EnableTrustedTypesCSP(source);
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::ScriptSrc,
       "script-src chrome://* 'self';");
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 929d049b..fe193859 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -21269,5 +21269,37 @@
         }
       }
     ]
+  },
+  "CloudAPAuthEnabled": {
+    "os": [ "win" ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "auth.cloud_ap_auth.enabled": {
+            "default_value": 0,
+            "location": "local_state"
+          }
+        }
+      },
+      {
+        "policies": { "CloudAPAuthEnabled": 0 },
+        "prefs": {
+          "auth.cloud_ap_auth.enabled": {
+            "value": 0,
+            "location": "local_state"
+          }
+        }
+      },
+      {
+        "policies": { "CloudAPAuthEnabled": 1 },
+        "prefs": {
+          "auth.cloud_ap_auth.enabled": {
+            "value": 1,
+            "location": "local_state"
+          }
+        }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 600a8051..091cd48 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -381,10 +381,12 @@
   out_dir = "$target_gen_dir/tsc"
   js_files = generate_definitions_js_files
   visibility = [ ":build_ts" ]
-  extra_deps = [ "//ui/webui/resources:generate_definitions" ]
 
   if (is_chromeos_ash) {
-    extra_deps += [ "//ash/webui/common/resources:generate_definitions" ]
+    extra_deps = [
+      "//ash/webui/common/resources:generate_definitions",
+      "//ui/webui/resources:generate_definitions",
+    ]
   }
 }
 
diff --git a/chrome/test/data/webui/chromeos/ash_common/page_toolbar_test.js b/chrome/test/data/webui/chromeos/ash_common/page_toolbar_test.js
index 33164ba..8110206d 100644
--- a/chrome/test/data/webui/chromeos/ash_common/page_toolbar_test.js
+++ b/chrome/test/data/webui/chromeos/ash_common/page_toolbar_test.js
@@ -13,7 +13,7 @@
   let pageToolbarElement = null;
 
   setup(() => {
-    document.body.innerHTML = '';
+    document.body.innerHTML = window.trustedTypes.emptyHTML;
   });
 
   teardown(() => {
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_element_test.ts
index 40c2f5b2..712eeaf3 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_preview_element_test.ts
@@ -134,6 +134,9 @@
   });
 
   test('click ambient collage goes to ambient albums subpage', async () => {
+    // Disables `isAmbientSubpageUIChangeEnabled` to show the previous UI.
+    loadTimeData.overrideValues({['isAmbientSubpageUIChangeEnabled']: false});
+
     personalizationStore.data.ambient = {
       ...personalizationStore.data.ambient,
       albums: ambientProvider.albums,
@@ -184,6 +187,59 @@
         'navigates to google photos topic source');
   });
 
+  test('click ambient thumbnail goes to ambient albums subpage', async () => {
+    loadTimeData.overrideValues({['isAmbientSubpageUIChangeEnabled']: true});
+
+    personalizationStore.data.ambient = {
+      ...personalizationStore.data.ambient,
+      albums: ambientProvider.albums,
+      topicSource: TopicSource.kArtGallery,
+      ambientModeEnabled: true,
+      googlePhotosAlbumsPreviews: ambientProvider.googlePhotosAlbumsPreviews,
+    };
+    ambientPreviewElement = initElement(AmbientPreview, {clickable: true});
+    personalizationStore.notifyObservers();
+    await waitAfterNextRender(ambientPreviewElement);
+
+    function setFakeRouter() {
+      const original = PersonalizationRouter.instance;
+      return new Promise<TopicSource>(resolve => {
+        PersonalizationRouter.instance = () => {
+          return {
+            selectAmbientAlbums(topicSource: TopicSource) {
+              resolve(topicSource);
+              PersonalizationRouter.instance = original;
+            },
+          } as PersonalizationRouter;
+        };
+      });
+    }
+
+    const artGalleryPromise = setFakeRouter();
+
+    ambientPreviewElement.shadowRoot!.getElementById(
+                                         'thumbnailContainer')!.click();
+
+    let topicSource = await artGalleryPromise;
+    assertEquals(
+        topicSource, TopicSource.kArtGallery,
+        'navigates to art gallery topic source');
+
+    // Set the topic source to kGooglePhotos and check that clicking the photo
+    // collage goes to kGooglePhotos subpage.
+    personalizationStore.data.ambient.topicSource = TopicSource.kGooglePhotos;
+    personalizationStore.notifyObservers();
+    const googlePhotosPromise = setFakeRouter();
+
+    ambientPreviewElement.shadowRoot!.getElementById(
+                                         'thumbnailContainer')!.click();
+
+    topicSource = await googlePhotosPromise;
+    assertEquals(
+        topicSource, TopicSource.kGooglePhotos,
+        'navigates to google photos topic source');
+  });
+
   test('displays zero state message before UI change', async () => {
     // Disables `isAmbientSubpageUIChangeEnabled` to show the previous UI.
     loadTimeData.overrideValues({['isAmbientSubpageUIChangeEnabled']: false});
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
index ce6ad90..05c357b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
@@ -115,7 +115,7 @@
 
     const previewItemPlaceholders =
         ambientPreview.shadowRoot!.querySelectorAll('.placeholder');
-    assertEquals(5, previewItemPlaceholders!.length);
+    assertEquals(6, previewItemPlaceholders!.length);
 
     // Should show image placeholders for the 3 theme items.
     const animationThemePlaceholder =
@@ -702,6 +702,10 @@
   test(
       'displays 4 image collage when there are enough photos in Google photos album',
       async () => {
+        // Disables `isAmbientSubpageUIChangeEnabled` to show the previous UI.
+        loadTimeData.overrideValues(
+            {['isAmbientSubpageUIChangeEnabled']: false});
+
         ambientSubpageElement = await displayMainSettings(
             TopicSource.kGooglePhotos, TemperatureUnit.kFahrenheit,
             /*ambientModeEnabled=*/ true);
@@ -724,6 +728,10 @@
   test(
       'displays 1 image collage when there are not enough photos in Google photos album',
       async () => {
+        // Disables `isAmbientSubpageUIChangeEnabled` to show the previous UI.
+        loadTimeData.overrideValues(
+            {['isAmbientSubpageUIChangeEnabled']: false});
+
         ambientSubpageElement = await displayMainSettings(
             TopicSource.kGooglePhotos, TemperatureUnit.kFahrenheit,
             /*ambientModeEnabled=*/ true);
@@ -748,6 +756,10 @@
   test(
       'displays preview urls from selected albums when there are zero preview photos in Google photos album',
       async () => {
+        // Disables `isAmbientSubpageUIChangeEnabled` to show the previous UI.
+        loadTimeData.overrideValues(
+            {['isAmbientSubpageUIChangeEnabled']: false});
+
         ambientSubpageElement = await displayMainSettings(
             TopicSource.kGooglePhotos, TemperatureUnit.kFahrenheit,
             /*ambientModeEnabled=*/ true);
diff --git a/chrome/test/data/webui/cr_elements/cr_menu_selector_focus_test.ts b/chrome/test/data/webui/cr_elements/cr_menu_selector_focus_test.ts
index 41a9296d..2605ed8 100644
--- a/chrome/test/data/webui/cr_elements/cr_menu_selector_focus_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_menu_selector_focus_test.ts
@@ -141,10 +141,13 @@
     const firstItem = getChild(0);
     element.selected = firstItem.href;
     assertTrue(firstItem.hasAttribute('selected'));
+    assertEquals('page', firstItem.getAttribute('aria-current'));
     const secondItem = getChild(1);
     element.selected = secondItem.href;
     assertFalse(firstItem.hasAttribute('selected'));
+    assertFalse(firstItem.hasAttribute('aria-current'));
     assertTrue(secondItem.hasAttribute('selected'));
+    assertEquals('page', secondItem.getAttribute('aria-current'));
   });
 
   test('DoesNotSelectUnselectableItems', () => {
diff --git a/chrome/test/data/webui/cr_elements/cr_searchable_drop_down_tests.ts b/chrome/test/data/webui/cr_elements/cr_searchable_drop_down_tests.ts
index 53ee256b..a94391e 100644
--- a/chrome/test/data/webui/cr_elements/cr_searchable_drop_down_tests.ts
+++ b/chrome/test/data/webui/cr_elements/cr_searchable_drop_down_tests.ts
@@ -5,6 +5,7 @@
 // clang-format off
 import 'chrome://resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js';
 
+import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import {CrSearchableDropDownElement} from 'chrome://resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js';
 import {keyDownOn, move} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
@@ -80,7 +81,7 @@
   }
 
   setup(function() {
-    document.body.innerHTML = `
+    document.body.innerHTML = getTrustedHTML`
       <p id="outside">Nothing to see here</p>
       <cr-searchable-drop-down label="test drop down">
       </cr-searchable-drop-down>
diff --git a/chrome/test/data/webui/new_tab_page/BUILD.gn b/chrome/test/data/webui/new_tab_page/BUILD.gn
index e2a15992..4857a1f 100644
--- a/chrome/test/data/webui/new_tab_page/BUILD.gn
+++ b/chrome/test/data/webui/new_tab_page/BUILD.gn
@@ -39,6 +39,10 @@
     "chrome://webui-test/*|" +
         rebase_path("$root_gen_dir/chrome/test/data/webui/tsc/*",
                     target_gen_dir),
+    "chrome://webui-test/metrics_reporter/*|" +
+        rebase_path(
+            "$root_gen_dir/chrome/test/data/webui/metrics_reporter/tsc/*",
+            target_gen_dir),
   ]
   in_files = non_preprocessed_files
 
@@ -49,6 +53,7 @@
 
   deps = [
     "..:build_ts",
+    "../metrics_reporter:build_ts",
     "//chrome/browser/resources/new_tab_page:build_ts",
   ]
 }
diff --git a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
index 927b76b3..2784ef13 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
+++ b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
@@ -5,12 +5,14 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 import 'chrome://new-tab-page/new_tab_page.js';
 
-import {$$, decodeString16, mojoString16, RealboxBrowserProxy, RealboxElement, RealboxIconElement, RealboxMatchElement} from 'chrome://new-tab-page/new_tab_page.js';
+import {$$, BrowserProxyImpl, decodeString16, MetricsReporterImpl, mojoString16, RealboxBrowserProxy, RealboxElement, RealboxIconElement, RealboxMatchElement} from 'chrome://new-tab-page/new_tab_page.js';
 import {AutocompleteMatch, NavigationPredictor} from 'chrome://new-tab-page/omnibox.mojom-webui.js';
 import {getFaviconForPageURL} from 'chrome://resources/js/icon.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {PageMetricsCallbackRouter} from 'chrome://resources/js/metrics_reporter/metrics_reporter.mojom-webui.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
 import {assertStyle} from '../test_support.js';
@@ -125,6 +127,8 @@
 
   let testProxy: TestRealboxBrowserProxy;
 
+  const testMetricsReporterProxy = TestBrowserProxy.fromClass(BrowserProxyImpl);
+
   suiteSetup(() => {
     loadTimeData.overrideValues({
       realboxMatchOmniboxTheme: true,
@@ -135,9 +139,19 @@
   setup(async () => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
 
+    // Set up Realbox's browser proxy.
     testProxy = new TestRealboxBrowserProxy();
     RealboxBrowserProxy.setInstance(testProxy);
 
+    // Set up MetricsReporter's browser proxy.
+    testMetricsReporterProxy.reset();
+    const metricsReporterCallbackRouter = new PageMetricsCallbackRouter();
+    testMetricsReporterProxy.setResultFor(
+        'getCallbackRouter', metricsReporterCallbackRouter);
+    testMetricsReporterProxy.setResultFor('getMark', Promise.resolve(null));
+    BrowserProxyImpl.setInstance(testMetricsReporterProxy);
+    MetricsReporterImpl.setInstanceForTest(new MetricsReporterImpl());
+
     realbox = document.createElement('ntp-realbox');
     document.body.appendChild(realbox);
   });
@@ -1864,7 +1878,7 @@
   });
 
   //============================================================================
-  // Test Metrics
+  // Test Responsiveness Metrics
   //============================================================================
 
   test('responsiveness metric is being recorded', async () => {
@@ -1953,6 +1967,86 @@
         1, testProxy.handler.getCallCount('logCharTypedToRepaintLatency'));
   });
 
+  test('new responsiveness metrics are being recorded', async () => {
+    realbox.$.input.value = 'he';
+    realbox.$.input.dispatchEvent(new InputEvent('input'));
+
+    // The responsiveness metrics are not recorded until the results are
+    // painted.
+    assertEquals(0, testMetricsReporterProxy.getCallCount('umaReportTime'));
+
+    let matches = [createSearchMatch()];
+    MetricsReporterImpl.getInstance().mark('ResultChanged');  // Marked in C++.
+    testProxy.callbackRouterRemote.autocompleteResultChanged({
+      input: mojoString16(realbox.$.input.value.trimStart()),
+      matches,
+      suggestionGroupsMap: {},
+    });
+    await testProxy.callbackRouterRemote.$.flushForTesting();
+    assertTrue(areMatchesShowing());
+
+    // The responsiveness metrics are recorded once the results are painted.
+    await testMetricsReporterProxy.whenCalled('umaReportTime');
+    assertEquals(2, testMetricsReporterProxy.getCallCount('umaReportTime'));
+    await testMetricsReporterProxy.whenCalled('clearMark');
+
+    // Delete the last character.
+    realbox.$.input.value = 'h';
+    realbox.$.input.dispatchEvent(new InputEvent('input'));
+
+    matches = [createSearchMatch({
+      allowedToBeDefaultMatch: true,
+      inlineAutocompletion: mojoString16('ello'),
+    })];
+    MetricsReporterImpl.getInstance().mark('ResultChanged');  // Marked in C++.
+    testProxy.callbackRouterRemote.autocompleteResultChanged({
+      input: mojoString16(realbox.$.input.value.trimStart()),
+      matches,
+      suggestionGroupsMap: {},
+    });
+    await testProxy.callbackRouterRemote.$.flushForTesting();
+    assertTrue(areMatchesShowing());
+
+    // Only one responsiveness metric is recorded when characters are deleted.
+    await testMetricsReporterProxy.whenCalled('umaReportTime');
+    assertEquals(3, testMetricsReporterProxy.getCallCount('umaReportTime'));
+    await testMetricsReporterProxy.whenCalled('clearMark');
+
+    assertEquals('hello', realbox.$.input.value);
+    const start = realbox.$.input.selectionStart!;
+    const end = realbox.$.input.selectionEnd!;
+    assertEquals('ello', realbox.$.input.value.substring(start, end));
+
+    // Type the next character of the inline autocompletion.
+    const keyEvent = new KeyboardEvent('keydown', {
+      bubbles: true,
+      cancelable: true,
+      composed: true,  // So it propagates across shadow DOM boundary.
+      key: 'e',
+    });
+    realbox.$.input.dispatchEvent(keyEvent);
+    assertTrue(keyEvent.defaultPrevented);
+
+    matches = [createSearchMatch({
+      allowedToBeDefaultMatch: true,
+      inlineAutocompletion: mojoString16('llo'),
+    })];
+    MetricsReporterImpl.getInstance().mark('ResultChanged');  // Marked in C++.
+    testProxy.callbackRouterRemote.autocompleteResultChanged({
+      input: mojoString16('he'),
+      matches,
+      suggestionGroupsMap: {},
+    });
+    await testProxy.callbackRouterRemote.$.flushForTesting();
+    assertTrue(areMatchesShowing());
+
+    // The responsiveness metrics are recorded when the default match has
+    // inline autocompletion.
+    await testMetricsReporterProxy.whenCalled('umaReportTime');
+    assertEquals(5, testMetricsReporterProxy.getCallCount('umaReportTime'));
+    await testMetricsReporterProxy.whenCalled('clearMark');
+  });
+
   //============================================================================
   // Test favicons / entity images
   //============================================================================
diff --git a/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc b/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
index d123968..257e75b 100644
--- a/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
+++ b/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
@@ -447,7 +447,6 @@
     CastDeviceCountMetrics::SinkSource sink_source,
     ChannelOpenedCallback callback,
     cast_channel::CastSocketOpenParams open_params) {
-  std::move(callback).Run(open_channel_response_);
   if (!open_channel_response_)
     return;
 
@@ -466,6 +465,11 @@
                      base::Unretained(mock_cast_media_sink_service_impl()),
                      cast_sink));
 
+  // The open channel callback needs to run after the AddSinkForTest is posted
+  // to ensure that no race conditions occur and we mimic an actual access code
+  // casting situation.
+  std::move(callback).Run(open_channel_response_);
+
   // A delay is added to the QRM notification since this
   // simulates the non-instant time it takes for a sink to be added
   // to the QRM.
diff --git a/chromeos/ash/components/audio/cras_audio_handler.cc b/chromeos/ash/components/audio/cras_audio_handler.cc
index 7e584a8..2112c627 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler.cc
@@ -733,6 +733,7 @@
 
 void CrasAudioHandler::IncreaseOutputVolumeByOneStep(int one_step_percent) {
   // Set all active devices to the same volume.
+  int new_output_volume = 0;
   for (const auto& item : audio_devices_) {
     const AudioDevice& device = item.second;
     if (!device.is_input && device.active) {
@@ -751,19 +752,20 @@
           volume_level = 1;
         }
         // increase one level and convert to volume
-        output_volume_ = std::min(
+        new_output_volume = std::min(
             100, static_cast<int>(std::floor(((double)(volume_level + 1)) /
                                              number_of_volume_steps * 100)));
       } else {
-        output_volume_ = std::min(100, output_volume_ + one_step_percent);
+        new_output_volume = std::min(100, output_volume_ + one_step_percent);
       }
-      SetOutputNodeVolumePercent(device.id, output_volume_);
+      SetOutputNodeVolumePercent(device.id, new_output_volume);
     }
   }
 }
 
 void CrasAudioHandler::DecreaseOutputVolumeByOneStep(int one_step_percent) {
   // Set all active devices to the same volume.
+  int new_output_volume = 0;
   for (const auto& item : audio_devices_) {
     const AudioDevice& device = item.second;
     if (!device.is_input && device.active) {
@@ -779,13 +781,13 @@
             (double)output_volume_ * (double)number_of_volume_steps * 0.01);
 
         // decrease one level and convert to volume
-        output_volume_ = std::max(
+        new_output_volume = std::max(
             0, static_cast<int>(std::floor(((double)(volume_level - 1)) /
                                            number_of_volume_steps * 100)));
       } else {
-        output_volume_ = std::max(0, output_volume_ - one_step_percent);
+        new_output_volume = std::max(0, output_volume_ - one_step_percent);
       }
-      SetOutputNodeVolumePercent(device.id, output_volume_);
+      SetOutputNodeVolumePercent(device.id, new_output_volume);
     }
   }
 }
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
index 3f78f55..7b40eca 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
@@ -138,10 +138,10 @@
 // TODO(b/259454320): Pass through a `base::RepeatingCallback` here to enable
 // the callsite to receive progress updates.
 void DriveFsPinManager::Start(
-    base::OnceCallback<void(PinError)> complete_callback) {
+    base::OnceCallback<void(SetupError)> complete_callback) {
   if (!enabled_) {
     LOG(ERROR) << "The pin manager is not enabled";
-    std::move(complete_callback).Run(PinError::kManagerDisabled);
+    std::move(complete_callback).Run(SetupError::kManagerDisabled);
     return;
   }
 
@@ -158,13 +158,14 @@
 }
 
 void DriveFsPinManager::Stop() {
-  Complete(PinError::kErrorManagerStopped);
+  Complete(SetupError::kErrorManagerStopped);
 }
 
 void DriveFsPinManager::OnFreeDiskSpaceRetrieved(int64_t free_space) {
   if (free_space == -1) {
     LOG(ERROR) << "Error calculating free disk space";
-    std::move(complete_callback_).Run(PinError::kErrorCalculatingFreeDiskSpace);
+    std::move(complete_callback_)
+        .Run(SetupError::kErrorCalculatingFreeDiskSpace);
     return;
   }
 
@@ -186,13 +187,13 @@
   if (error != drive::FILE_ERROR_OK) {
     LOG(ERROR) << "Error retrieving search results for size calculation: "
                << error;
-    Complete(PinError::kErrorRetrievingSearchResults);
+    Complete(SetupError::kErrorRetrievingSearchResults);
     return;
   }
 
   if (!items.has_value()) {
     LOG(ERROR) << "Items returned are invalid";
-    Complete(PinError::kErrorResultsReturnedInvalid);
+    Complete(SetupError::kErrorResultsReturnedInvalid);
     return;
   }
 
@@ -221,12 +222,12 @@
     LOG(ERROR) << "The required size (" << size_required_
                << " bytes) exceeds the available free space (" << free_space_
                << "bytes)";
-    Complete(PinError::kErrorNotEnoughFreeSpace);
+    Complete(SetupError::kErrorNotEnoughFreeSpace);
     return;
   }
 
   if (!search_query_.is_bound()) {
-    Complete(PinError::kErrorSearchQueryNotBound);
+    Complete(SetupError::kErrorSearchQueryNotBound);
     return;
   }
 
@@ -235,7 +236,7 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void DriveFsPinManager::Complete(PinError status) {
+void DriveFsPinManager::Complete(SetupError status) {
   weak_ptr_factory_.InvalidateWeakPtrs();
   search_query_.reset();
   free_space_ = 0;
@@ -270,13 +271,13 @@
     absl::optional<std::vector<drivefs::mojom::QueryItemPtr>> items) {
   if (error != drive::FILE_ERROR_OK) {
     LOG(ERROR) << "Error retrieving search results to pin: " << error;
-    Complete(PinError::kErrorRetrievingSearchResultsForPinning);
+    Complete(SetupError::kErrorRetrievingSearchResultsForPinning);
     return;
   }
 
   if (!items.has_value()) {
     LOG(ERROR) << "Items returned are invalid";
-    Complete(PinError::kErrorResultsReturnedInvalidForPinning);
+    Complete(SetupError::kErrorResultsReturnedInvalidForPinning);
     return;
   }
 
@@ -284,7 +285,7 @@
     VLOG(1) << "Finished pinning all files in "
             << timer_.Elapsed().InMilliseconds() << "ms";
     setup_complete_ = true;
-    Complete(PinError::kSuccess);
+    Complete(SetupError::kSuccess);
     return;
   }
 
@@ -300,7 +301,7 @@
 
   if (unpinned_items == 0) {
     if (!search_query_.is_bound()) {
-      Complete(PinError::kErrorSearchQueryNotBound);
+      Complete(SetupError::kErrorSearchQueryNotBound);
       return;
     }
     VLOG(1) << "All items in current batch are already pinned";
@@ -329,7 +330,7 @@
     LOG(ERROR) << "Failed pinning an item: " << status;
     VLOG(1) << "Path that failed to pin: " << path << " with error "
             << drive::FileErrorToString(status);
-    Complete(PinError::kErrorFailedToPinItem);
+    Complete(SetupError::kErrorFailedToPinItem);
     return;
   }
 
@@ -365,7 +366,7 @@
 
 void DriveFsPinManager::MaybeStartSearch(size_t remaining_items) {
   if (!search_query_.is_bound()) {
-    Complete(PinError::kErrorSearchQueryNotBound);
+    Complete(SetupError::kErrorSearchQueryNotBound);
     return;
   }
 
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.h b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
index 69621ee..a26c5891 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.h
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
@@ -34,7 +34,7 @@
 
 // Errors that are returned via the completion callback that indicate either
 // which stage the failure was at or whether the initial setup was a success.
-enum class PinError {
+enum class SetupError {
   kSuccess = 0,
   kManagerDisabled = 1,
   kErrorCalculatingFreeDiskSpace = 2,
@@ -96,7 +96,7 @@
   // which will ensure any new files created and switched to pinned state
   // automatically. The complete callback will be called once the initial
   // pinning has completed.
-  void Start(base::OnceCallback<void(PinError)> complete_callback);
+  void Start(base::OnceCallback<void(SetupError)> complete_callback);
 
   // Stop the syncing setup.
   void Stop();
@@ -158,7 +158,7 @@
 
   // When the pinning has finished, this ensures appropriate cleanup happens on
   // the underlying search query mojo connection.
-  void Complete(PinError status);
+  void Complete(SetupError status);
 
   // Once the verification that the files to pin will not exceed available disk
   // space, the files to pin can be batch pinned.
@@ -200,7 +200,7 @@
   bool setup_complete_ = false;
   int64_t size_required_ = 0;
   int64_t free_space_ = 0;
-  base::OnceCallback<void(PinError)> complete_callback_;
+  base::OnceCallback<void(SetupError)> complete_callback_;
   std::unique_ptr<FreeDiskSpaceDelegate> free_disk_space_;
 
   base::FilePath profile_path_;
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
index 2c326b5c..d0f1c93 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
@@ -190,14 +190,14 @@
 };
 
 TEST_F(DriveFsPinManagerTest, DisabledPinManagerShouldNotStartSearching) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
 
   EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_)).Times(0);
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_)).Times(0);
-  EXPECT_CALL(mock_callback, Run(PinError::kManagerDisabled))
+  EXPECT_CALL(mock_callback, Run(SetupError::kManagerDisabled))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(_, _)).Times(0);
 
@@ -209,14 +209,14 @@
 }
 
 TEST_F(DriveFsPinManagerTest, OnFreeDiskSpaceFailingShouldNotSearchDrive) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
 
   EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_)).Times(0);
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_)).Times(0);
-  EXPECT_CALL(mock_callback, Run(PinError::kErrorCalculatingFreeDiskSpace))
+  EXPECT_CALL(mock_callback, Run(SetupError::kErrorCalculatingFreeDiskSpace))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
       .WillOnce(RunOnceCallback<1>(-1));
@@ -229,7 +229,7 @@
 }
 
 TEST_F(DriveFsPinManagerTest, DriveReturningAnErrorShouldFail) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -238,7 +238,7 @@
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       .WillOnce(DoAll(PopulateNoSearchItems(),
                       Return(drive::FileError::FILE_ERROR_FAILED)));
-  EXPECT_CALL(mock_callback, Run(PinError::kErrorRetrievingSearchResults))
+  EXPECT_CALL(mock_callback, Run(SetupError::kErrorRetrievingSearchResults))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
       .WillOnce(RunOnceCallback<1>(1024));  // 1 MB.
@@ -251,7 +251,7 @@
 }
 
 TEST_F(DriveFsPinManagerTest, DriveReturnedSuccessButInvalidResultsShouldFail) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -259,7 +259,7 @@
   EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_)).Times(1);
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       .WillOnce(Return(drive::FileError::FILE_ERROR_OK));
-  EXPECT_CALL(mock_callback, Run(PinError::kErrorResultsReturnedInvalid))
+  EXPECT_CALL(mock_callback, Run(SetupError::kErrorResultsReturnedInvalid))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
       .WillOnce(RunOnceCallback<1>(1024));  // 1 MB.
@@ -272,7 +272,7 @@
 }
 
 TEST_F(DriveFsPinManagerTest, IfPinnedItemSizeExceedsFreeDiskSpaceShouldFail) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -285,7 +285,7 @@
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       .WillOnce(DoAll(PopulateSearchItems(expected_drive_items),
                       Return(drive::FileError::FILE_ERROR_OK)));
-  EXPECT_CALL(mock_callback, Run(PinError::kErrorNotEnoughFreeSpace))
+  EXPECT_CALL(mock_callback, Run(SetupError::kErrorNotEnoughFreeSpace))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
       .WillOnce(RunOnceCallback<1>(1024));  // 1 MB.
@@ -298,7 +298,7 @@
 }
 
 TEST_F(DriveFsPinManagerTest, FailingToPinOneItemShouldFailCompletely) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -317,7 +317,7 @@
       // operations being mock failed.
       .WillOnce(DoAll(PopulateSearchItems(expected_drive_items),
                       Return(drive::FileError::FILE_ERROR_OK)));
-  EXPECT_CALL(mock_callback, Run(PinError::kErrorFailedToPinItem))
+  EXPECT_CALL(mock_callback, Run(SetupError::kErrorFailedToPinItem))
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
       .WillOnce(RunOnceCallback<1>(1024));  // 1 MB.
@@ -335,7 +335,7 @@
 }
 
 TEST_F(DriveFsPinManagerTest, OnlyUnpinnedItemsShouldGetPinned) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -403,7 +403,7 @@
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       .WillOnce(DoAll(PopulateNoSearchItems(),
                       Return(drive::FileError::FILE_ERROR_OK)));
-  EXPECT_CALL(mock_callback, Run(PinError::kSuccess))
+  EXPECT_CALL(mock_callback, Run(SetupError::kSuccess))
       .WillOnce(RunClosure(new_run_loop.QuitClosure()));
   ChangeAllItemEventsToState(status->item_events,
                              mojom::ItemEvent::State::kCompleted);
@@ -414,7 +414,7 @@
 TEST_F(
     DriveFsPinManagerTest,
     ZeroByteItemsAndHostedItemsShouldBePeriodicallyCleanedFromTheInProgressMap) {
-  base::MockOnceCallback<void(PinError)> mock_callback;
+  base::MockOnceCallback<void(SetupError)> mock_callback;
   auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
 
   base::RunLoop run_loop;
@@ -477,7 +477,7 @@
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       .WillOnce(DoAll(PopulateNoSearchItems(),
                       Return(drive::FileError::FILE_ERROR_OK)));
-  EXPECT_CALL(mock_callback, Run(PinError::kSuccess))
+  EXPECT_CALL(mock_callback, Run(SetupError::kSuccess))
       .WillOnce(RunClosure(new_run_loop.QuitClosure()));
   ChangeAllItemEventsToState(status->item_events,
                              mojom::ItemEvent::State::kCompleted);
diff --git a/chromeos/process_proxy/process_output_watcher_unittest.cc b/chromeos/process_proxy/process_output_watcher_unittest.cc
index d9e62331..18f7102 100644
--- a/chromeos/process_proxy/process_output_watcher_unittest.cc
+++ b/chromeos/process_proxy/process_output_watcher_unittest.cc
@@ -200,7 +200,7 @@
   std::vector<TestCase> exp;
 };
 
-TEST_F(ProcessOutputWatcherTest, OutputWatcher) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_OutputWatcher) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("t", false));
   test_cases.push_back(TestCase("testing output\n", false));
@@ -215,7 +215,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, SplitUTF8Character) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_SplitUTF8Character) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("test1\xc2", false, "test1"));
   test_cases.push_back(TestCase("\xb5test1", false, "\xc2\xb5test1"));
@@ -223,7 +223,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, SplitSoleUTF8Character) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_SplitSoleUTF8Character) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("\xc2", false, ""));
   test_cases.push_back(TestCase("\xb5", false, "\xc2\xb5"));
@@ -231,7 +231,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, SplitUTF8CharacterLength3) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_SplitUTF8CharacterLength3) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("test3\xe2\x82", false, "test3"));
   test_cases.push_back(TestCase("\xac", false, "\xe2\x82\xac"));
@@ -239,7 +239,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, SplitSoleUTF8CharacterThreeWays) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_SplitSoleUTF8CharacterThreeWays) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("\xe2", false, ""));
   test_cases.push_back(TestCase("\x82", false, ""));
@@ -256,14 +256,14 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, SoleThreeByteUTF8Character) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_SoleThreeByteUTF8Character) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("\xe2\x82\xac", false, "\xe2\x82\xac"));
 
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, HasThreeByteUTF8Character) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_HasThreeByteUTF8Character) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(
       TestCase("test\xe2\x82\xac_", false, "test\xe2\x82\xac_"));
@@ -279,7 +279,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, MultipleMultiByteUTF8Characters) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_MultipleMultiByteUTF8Characters) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(
       TestCase("test\xe2\x82\xac\xc2", false, "test\xe2\x82\xac"));
@@ -288,7 +288,7 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, ContainsInvalidUTF8) {
+TEST_F(ProcessOutputWatcherTest, DISABLED_ContainsInvalidUTF8) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("\xc2_", false, "\xc2_"));
 
diff --git a/components/access_code_cast/common/access_code_cast_metrics.cc b/components/access_code_cast/common/access_code_cast_metrics.cc
index 818dd83..4b7a45c 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.cc
+++ b/components/access_code_cast/common/access_code_cast_metrics.cc
@@ -28,6 +28,10 @@
     "AccessCodeCast.Ui.DialogOpenLocation";
 const char AccessCodeCastMetrics::kHistogramRememberedDevicesCount[] =
     "AccessCodeCast.Discovery.RememberedDevicesCount";
+const char AccessCodeCastMetrics::kHistogramUiTabSwitcherUsageType[] =
+    "AccessCodeCast.Ui.TabSwitcherUsageType";
+const char AccessCodeCastMetrics::kHistogramUiTabSwitchingCount[] =
+    "AccessCodeCast.Ui.TabSwitchingCount";
 
 // static
 void AccessCodeCastMetrics::OnCastSessionResult(int route_request_result_code,
@@ -87,3 +91,14 @@
 void AccessCodeCastMetrics::RecordRememberedDevicesCount(int count) {
   base::UmaHistogramCounts100(kHistogramRememberedDevicesCount, count);
 }
+
+// static
+void AccessCodeCastMetrics::RecordTabSwitchesCountInTabSession(int count) {
+  base::UmaHistogramCounts100(kHistogramUiTabSwitchingCount, count);
+}
+
+// static
+void AccessCodeCastMetrics::RecordTabSwitcherUsageCase(
+    AccessCodeCastUiTabSwitcherUsage usage) {
+  base::UmaHistogramEnumeration(kHistogramUiTabSwitcherUsageType, usage);
+}
diff --git a/components/access_code_cast/common/access_code_cast_metrics.h b/components/access_code_cast/common/access_code_cast_metrics.h
index 47c18d0a..17df49e 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.h
+++ b/components/access_code_cast/common/access_code_cast_metrics.h
@@ -72,6 +72,17 @@
   kMaxValue = kSystemTrayCastMenu
 };
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class AccessCodeCastUiTabSwitcherUsage {
+  kTabSwitcherUiShownAndNotUsed = 0,
+  kTabSwitcherUiShownAndUsedToSwitchTabs = 1,
+
+  // NOTE: Do not reorder existing entries, and add entries only immediately
+  // above this line.
+  kMaxValue = kTabSwitcherUiShownAndUsedToSwitchTabs,
+};
+
 class AccessCodeCastMetrics {
  public:
   AccessCodeCastMetrics();
@@ -87,6 +98,8 @@
   static const char kHistogramDialogLoadTime[];
   static const char kHistogramDialogOpenLocation[];
   static const char kHistogramRememberedDevicesCount[];
+  static const char kHistogramUiTabSwitcherUsageType[];
+  static const char kHistogramUiTabSwitchingCount[];
 
   // Records metrics relating to starting a cast session (route). Mode is
   // media_router::MediaCastMode.
@@ -117,6 +130,15 @@
   // Records the count of cast devices which are currently being remembered
   // being the AccessCodeCastSinkService.
   static void RecordRememberedDevicesCount(int count);
+
+  // Records the count of tabs a user switches to during a tab mirroring
+  // session.
+  static void RecordTabSwitchesCountInTabSession(int count);
+
+  // Records the usage type of tab switcher UI, i.e. shown only and not used or
+  // shown and actually used to switch tabs.
+  static void RecordTabSwitcherUsageCase(
+      AccessCodeCastUiTabSwitcherUsage usage);
 };
 
 #endif  // COMPONENTS_ACCESS_CODE_CAST_COMMON_ACCESS_CODE_CAST_METRICS_H_
diff --git a/components/attribution_reporting/BUILD.gn b/components/attribution_reporting/BUILD.gn
index fd97004..56eb465 100644
--- a/components/attribution_reporting/BUILD.gn
+++ b/components/attribution_reporting/BUILD.gn
@@ -94,26 +94,16 @@
   deps = [ "//base" ]
 }
 
-fuzzer_test("os_source_parse_fuzzer") {
-  sources = [ "os_source_parse_fuzzer.cc" ]
+fuzzer_test("parse_os_header_fuzzer") {
+  sources = [ "parse_os_header_fuzzer.cc" ]
   deps = [
     ":attribution_reporting",
     "//base",
     "//base:i18n",
   ]
 
-  seed_corpus = "//components/attribution_reporting/os_source_fuzzer_corpus/"
-}
-
-fuzzer_test("os_trigger_parse_fuzzer") {
-  sources = [ "os_trigger_parse_fuzzer.cc" ]
-  deps = [
-    ":attribution_reporting",
-    "//base",
-    "//base:i18n",
-  ]
-
-  seed_corpus = "//components/attribution_reporting/os_trigger_fuzzer_corpus/"
+  seed_corpus =
+      "//components/attribution_reporting/parse_os_header_fuzzer_corpus/"
 }
 
 fuzzer_test("source_registration_fuzzer") {
diff --git a/components/attribution_reporting/os_registration.cc b/components/attribution_reporting/os_registration.cc
index ae39fed..bf7bdd34 100644
--- a/components/attribution_reporting/os_registration.cc
+++ b/components/attribution_reporting/os_registration.cc
@@ -4,100 +4,18 @@
 
 #include "components/attribution_reporting/os_registration.h"
 
-#include <utility>
-
-#include "base/check.h"
 #include "base/strings/string_piece.h"
 #include "net/http/structured_headers.h"
-#include "services/network/public/cpp/is_potentially_trustworthy.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
-#include "url/origin.h"
 
 namespace attribution_reporting {
 
-namespace {
-
-bool IsValidUrl(const GURL& url) {
-  return url.SchemeIsHTTPOrHTTPS() &&
-         network::IsOriginPotentiallyTrustworthy(url::Origin::Create(url));
-}
-
-GURL ParseURLFromStructuredHeaderItem(base::StringPiece header) {
+GURL ParseOsSourceOrTriggerHeader(base::StringPiece header) {
   const auto item = net::structured_headers::ParseItem(header);
   if (!item || !item->item.is_string())
     return GURL();
 
-  GURL url(item->item.GetString());
-  return IsValidUrl(url) ? url : GURL();
+  return GURL(item->item.GetString());
 }
 
-}  // namespace
-
-// static
-absl::optional<OsSource> OsSource::Parse(base::StringPiece header) {
-  GURL url = ParseURLFromStructuredHeaderItem(header);
-  if (!url.is_valid())
-    return absl::nullopt;
-
-  return OsSource(std::move(url));
-}
-
-// static
-absl::optional<OsSource> OsSource::Create(GURL url) {
-  if (!IsValidUrl(url))
-    return absl::nullopt;
-
-  return OsSource(std::move(url));
-}
-
-// static
-absl::optional<OsTrigger> OsTrigger::Parse(base::StringPiece header) {
-  GURL url = ParseURLFromStructuredHeaderItem(header);
-  if (!url.is_valid())
-    return absl::nullopt;
-
-  return OsTrigger(std::move(url));
-}
-
-// static
-absl::optional<OsTrigger> OsTrigger::Create(GURL url) {
-  if (!IsValidUrl(url))
-    return absl::nullopt;
-
-  return OsTrigger(std::move(url));
-}
-
-OsTrigger::OsTrigger() = default;
-
-OsTrigger::OsTrigger(GURL url) : url_(std::move(url)) {
-  DCHECK(IsValidUrl(url_));
-}
-
-OsTrigger::~OsTrigger() = default;
-
-OsTrigger::OsTrigger(const OsTrigger&) = default;
-
-OsTrigger& OsTrigger::operator=(const OsTrigger&) = default;
-
-OsTrigger::OsTrigger(OsTrigger&&) = default;
-
-OsTrigger& OsTrigger::operator=(OsTrigger&&) = default;
-
-OsSource::OsSource() = default;
-
-OsSource::OsSource(GURL url) : url_(std::move(url)) {
-  DCHECK(IsValidUrl(url_));
-}
-
-OsSource::~OsSource() = default;
-
-OsSource::OsSource(const OsSource&) = default;
-
-OsSource& OsSource::operator=(const OsSource&) = default;
-
-OsSource::OsSource(OsSource&&) = default;
-
-OsSource& OsSource::operator=(OsSource&&) = default;
-
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/os_registration.h b/components/attribution_reporting/os_registration.h
index f7eeb1b9..51975eec 100644
--- a/components/attribution_reporting/os_registration.h
+++ b/components/attribution_reporting/os_registration.h
@@ -7,90 +7,23 @@
 
 #include "base/component_export.h"
 #include "base/strings/string_piece_forward.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
-namespace mojo {
-struct DefaultConstructTraits;
-}  // namespace mojo
-
 namespace attribution_reporting {
 
-class COMPONENT_EXPORT(ATTRIBUTION_REPORTING) OsTrigger {
- public:
-  // Parses Attribution-Reporting-Register-OS-Trigger header.
-  //
-  // The structured-header item may have parameters, but they are ignored.
-  //
-  // Returns `absl::nullopt` if `header` is not parsable as a structured-header
-  // item, if the item is not a string, if the string is not a valid URL, or if
-  // the URL is not potentially trustworthy.
-  //
-  // Example:
-  //
-  // "https://x.test/abc"
-  static absl::optional<OsTrigger> Parse(base::StringPiece);
-
-  static absl::optional<OsTrigger> Create(GURL url);
-
-  ~OsTrigger();
-
-  OsTrigger(const OsTrigger&);
-  OsTrigger& operator=(const OsTrigger&);
-
-  OsTrigger(OsTrigger&&);
-  OsTrigger& operator=(OsTrigger&&);
-
-  const GURL& url() const { return url_; }
-
- private:
-  friend mojo::DefaultConstructTraits;
-
-  // Exposed for Mojo type-mapping.
-  OsTrigger();
-
-  explicit OsTrigger(GURL url);
-
-  GURL url_;
-};
-
-class COMPONENT_EXPORT(ATTRIBUTION_REPORTING) OsSource {
- public:
-  // Parses Attribution-Reporting-Register-OS-Source header.
-  //
-  // The structured-header item may have parameters, but they are ignored.
-  //
-  // Returns `absl::nullopt` if `header` is not parsable as a structured-header
-  // item, if the item is not a string, if the string is not a valid URL, or if
-  // the URL is not potentially trustworthy.
-  //
-  // Example:
-  //
-  // "https://x.test/abc"
-  static absl::optional<OsSource> Parse(base::StringPiece);
-
-  static absl::optional<OsSource> Create(GURL url);
-
-  ~OsSource();
-
-  OsSource(const OsSource&);
-  OsSource& operator=(const OsSource&);
-
-  OsSource(OsSource&&);
-  OsSource& operator=(OsSource&&);
-
-  const GURL& url() const { return url_; }
-
- private:
-  friend mojo::DefaultConstructTraits;
-
-  // Exposed for Mojo type-mapping.
-  OsSource();
-
-  OsSource(GURL url);
-
-  GURL url_;
-};
+// Parses an Attribution-Reporting-OS-Source or
+// Attribution-Reporting-Register-OS-Trigger header.
+//
+// The structured-header item may have parameters, but they are ignored.
+//
+// Returns an invalid `GURL` if `header` is not parsable as a structured-header
+// item, if the item is not a string, or if the string is not a valid URL.
+//
+// Example:
+//
+// "https://x.test/abc"
+COMPONENT_EXPORT(ATTRIBUTION_REPORTING)
+GURL ParseOsSourceOrTriggerHeader(base::StringPiece);
 
 }  // namespace attribution_reporting
 
diff --git a/components/attribution_reporting/os_registration_unittest.cc b/components/attribution_reporting/os_registration_unittest.cc
index c92656c..763dddb 100644
--- a/components/attribution_reporting/os_registration_unittest.cc
+++ b/components/attribution_reporting/os_registration_unittest.cc
@@ -6,14 +6,12 @@
 
 #include "base/strings/string_piece.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace attribution_reporting {
 namespace {
 
-template <typename T>
-void TestParse() {
+TEST(OsRegistration, ParseOsSourceOrTriggerHeader) {
   const struct {
     const char* description;
     base::StringPiece header;
@@ -40,16 +38,6 @@
           GURL("https://d.test"),
       },
       {
-          "url_not_in_http_family",
-          R"("wss://d.test")",
-          GURL(),
-      },
-      {
-          "url_not_potentially_trustworthy",
-          R"("http://d.test")",
-          GURL(),
-      },
-      {
           "extra_params_ignored",
           R"("https://d.test"; y=1)",
           GURL("https://d.test"),
@@ -57,23 +45,11 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    absl::optional<T> actual = T::Parse(test_case.header);
-
-    EXPECT_EQ(test_case.expected.is_valid(), actual.has_value())
+    EXPECT_EQ(ParseOsSourceOrTriggerHeader(test_case.header),
+              test_case.expected)
         << test_case.description;
-
-    if (test_case.expected.is_valid())
-      EXPECT_EQ(test_case.expected, actual->url()) << test_case.description;
   }
 }
 
-TEST(OsSource, Parse) {
-  TestParse<OsSource>();
-}
-
-TEST(OsTrigger, Parse) {
-  TestParse<OsTrigger>();
-}
-
 }  // namespace
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/os_source_fuzzer_corpus/invalid_not_string.txt b/components/attribution_reporting/os_source_fuzzer_corpus/invalid_not_string.txt
deleted file mode 100644
index d800886..0000000
--- a/components/attribution_reporting/os_source_fuzzer_corpus/invalid_not_string.txt
+++ /dev/null
@@ -1 +0,0 @@
-123
\ No newline at end of file
diff --git a/components/attribution_reporting/os_source_fuzzer_corpus/invalid_wrong_syntax.txt b/components/attribution_reporting/os_source_fuzzer_corpus/invalid_wrong_syntax.txt
deleted file mode 100644
index 74e0f12e..0000000
--- a/components/attribution_reporting/os_source_fuzzer_corpus/invalid_wrong_syntax.txt
+++ /dev/null
@@ -1 +0,0 @@
-!
\ No newline at end of file
diff --git a/components/attribution_reporting/os_source_fuzzer_corpus/valid_extra_params.txt b/components/attribution_reporting/os_source_fuzzer_corpus/valid_extra_params.txt
deleted file mode 100644
index 2227b62..0000000
--- a/components/attribution_reporting/os_source_fuzzer_corpus/valid_extra_params.txt
+++ /dev/null
@@ -1 +0,0 @@
-"https://adtech.example/register-android-trigger"; foo=1
\ No newline at end of file
diff --git a/components/attribution_reporting/os_source_fuzzer_corpus/valid_minimum.txt b/components/attribution_reporting/os_source_fuzzer_corpus/valid_minimum.txt
deleted file mode 100644
index 9388391..0000000
--- a/components/attribution_reporting/os_source_fuzzer_corpus/valid_minimum.txt
+++ /dev/null
@@ -1 +0,0 @@
-"https://adtech.example/register-android-trigger"
\ No newline at end of file
diff --git a/components/attribution_reporting/os_source_parse_fuzzer.cc b/components/attribution_reporting/os_source_parse_fuzzer.cc
deleted file mode 100644
index 35078aaa..0000000
--- a/components/attribution_reporting/os_source_parse_fuzzer.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include "base/command_line.h"
-#include "base/i18n/icu_util.h"
-#include "base/strings/string_piece.h"
-#include "components/attribution_reporting/os_registration.h"
-
-namespace {
-
-struct Environment {
-  Environment() {
-    base::CommandLine::Init(0, nullptr);
-    base::i18n::InitializeICU();
-  }
-};
-
-}  // namespace
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  static Environment env;
-  attribution_reporting::OsSource::Parse(
-      base::StringPiece(reinterpret_cast<const char*>(data), size));
-  return 0;
-}
diff --git a/components/attribution_reporting/os_trigger_parse_fuzzer.cc b/components/attribution_reporting/parse_os_header_fuzzer.cc
similarity index 92%
rename from components/attribution_reporting/os_trigger_parse_fuzzer.cc
rename to components/attribution_reporting/parse_os_header_fuzzer.cc
index fe82112..db8141d 100644
--- a/components/attribution_reporting/os_trigger_parse_fuzzer.cc
+++ b/components/attribution_reporting/parse_os_header_fuzzer.cc
@@ -23,7 +23,7 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   static Environment env;
-  attribution_reporting::OsTrigger::Parse(
+  attribution_reporting::ParseOsSourceOrTriggerHeader(
       base::StringPiece(reinterpret_cast<const char*>(data), size));
   return 0;
 }
diff --git a/components/attribution_reporting/os_trigger_fuzzer_corpus/invalid_not_string.txt b/components/attribution_reporting/parse_os_header_fuzzer_corpus/invalid_not_string.txt
similarity index 100%
rename from components/attribution_reporting/os_trigger_fuzzer_corpus/invalid_not_string.txt
rename to components/attribution_reporting/parse_os_header_fuzzer_corpus/invalid_not_string.txt
diff --git a/components/attribution_reporting/os_trigger_fuzzer_corpus/invalid_wrong_syntax.txt b/components/attribution_reporting/parse_os_header_fuzzer_corpus/invalid_wrong_syntax.txt
similarity index 100%
rename from components/attribution_reporting/os_trigger_fuzzer_corpus/invalid_wrong_syntax.txt
rename to components/attribution_reporting/parse_os_header_fuzzer_corpus/invalid_wrong_syntax.txt
diff --git a/components/attribution_reporting/os_trigger_fuzzer_corpus/valid_extra_params.txt b/components/attribution_reporting/parse_os_header_fuzzer_corpus/valid_extra_params.txt
similarity index 100%
rename from components/attribution_reporting/os_trigger_fuzzer_corpus/valid_extra_params.txt
rename to components/attribution_reporting/parse_os_header_fuzzer_corpus/valid_extra_params.txt
diff --git a/components/attribution_reporting/os_trigger_fuzzer_corpus/valid_minimum.txt b/components/attribution_reporting/parse_os_header_fuzzer_corpus/valid_minimum.txt
similarity index 100%
rename from components/attribution_reporting/os_trigger_fuzzer_corpus/valid_minimum.txt
rename to components/attribution_reporting/parse_os_header_fuzzer_corpus/valid_minimum.txt
diff --git a/components/breadcrumbs/core/breadcrumb_manager.cc b/components/breadcrumbs/core/breadcrumb_manager.cc
index 42706a06..787dc86 100644
--- a/components/breadcrumbs/core/breadcrumb_manager.cc
+++ b/components/breadcrumbs/core/breadcrumb_manager.cc
@@ -51,6 +51,14 @@
   }
 }
 
+void BreadcrumbManager::SetPreviousSessionEvents(
+    const std::vector<std::string>& events) {
+  breadcrumbs_.insert(breadcrumbs_.begin(), events.begin(), events.end());
+  for (auto& observer : observers_) {
+    observer.PreviousSessionEventsAdded();
+  }
+}
+
 BreadcrumbManager::BreadcrumbManager() = default;
 BreadcrumbManager::~BreadcrumbManager() = default;
 
diff --git a/components/breadcrumbs/core/breadcrumb_manager.h b/components/breadcrumbs/core/breadcrumb_manager.h
index 1cc5928..d78f1f5 100644
--- a/components/breadcrumbs/core/breadcrumb_manager.h
+++ b/components/breadcrumbs/core/breadcrumb_manager.h
@@ -41,6 +41,11 @@
   // BreadcrumbPersistentStorageManager as a delimiter.
   void AddEvent(const std::string& event);
 
+  // Adds breadcrumb events associated with the previous application session.
+  // Note: this behaves the same as `AddEvent()`, but takes multiple events and
+  // adds them to the start of the breadcrumbs log.
+  void SetPreviousSessionEvents(const std::vector<std::string>& events);
+
   // Adds and removes observers.
   void AddObserver(BreadcrumbManagerObserver* observer);
   void RemoveObserver(BreadcrumbManagerObserver* observer);
diff --git a/components/breadcrumbs/core/breadcrumb_manager_observer.h b/components/breadcrumbs/core/breadcrumb_manager_observer.h
index 5495f98..bba0c1e8 100644
--- a/components/breadcrumbs/core/breadcrumb_manager_observer.h
+++ b/components/breadcrumbs/core/breadcrumb_manager_observer.h
@@ -24,6 +24,10 @@
   // logged prepended to the string passed to `BreadcrumbManager::AddEvent()`.
   virtual void EventAdded(const std::string& event) {}
 
+  // Called when the previous session's events have been retrieved from file and
+  // added to the BreadcrumbManager.
+  virtual void PreviousSessionEventsAdded() {}
+
  protected:
   BreadcrumbManagerObserver();
   ~BreadcrumbManagerObserver() override;
diff --git a/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.cc b/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.cc
index 16f52ab..512b3816 100644
--- a/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.cc
+++ b/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.cc
@@ -32,31 +32,22 @@
   return *instance;
 }
 
-void CrashReporterBreadcrumbObserver::SetPreviousSessionEvents(
-    const std::vector<std::string>& events) {
-  breadcrumbs_.insert(breadcrumbs_.begin(), events.begin(), events.end());
+void CrashReporterBreadcrumbObserver::PreviousSessionEventsAdded() {
   UpdateBreadcrumbEventsCrashKey();
 }
 
-void CrashReporterBreadcrumbObserver::ResetForTesting() {
-  breadcrumbs_.clear();
-}
-
 void CrashReporterBreadcrumbObserver::EventAdded(const std::string& event) {
-  breadcrumbs_.push_back(event);
   UpdateBreadcrumbEventsCrashKey();
 }
 
 void CrashReporterBreadcrumbObserver::UpdateBreadcrumbEventsCrashKey() {
-  // Remove the oldest events to remain below the maximum number of breadcrumbs.
-  while (breadcrumbs_.size() > kMaxBreadcrumbs)
-    breadcrumbs_.pop_front();
+  const auto& breadcrumbs = BreadcrumbManager::GetInstance().GetEvents();
 
   // Get the length of all breadcrumbs combined and preallocate the space needed
   // for the combined string. This saves repeated allocations in the next loop.
   const size_t event_separator_length = strlen(kEventSeparator);
   const size_t breadcrumbs_string_length = std::accumulate(
-      breadcrumbs_.begin(), breadcrumbs_.end(), 0,
+      breadcrumbs.begin(), breadcrumbs.end(), 0,
       [event_separator_length](const size_t sum,
                                const std::string& breadcrumb) {
         return sum + breadcrumb.length() + event_separator_length;
@@ -66,14 +57,14 @@
 
   // Concatenate breadcrumbs backwards, putting new breadcrumbs at the front, so
   // that the most relevant (i.e., newest) breadcrumbs are at the top in Crash.
-  for (const std::string& breadcrumb : base::Reversed(breadcrumbs_)) {
+  for (const std::string& breadcrumb : base::Reversed(breadcrumbs)) {
     breadcrumbs_string += breadcrumb;
     breadcrumbs_string += kEventSeparator;
   }
   DCHECK(breadcrumbs_string.length() == breadcrumbs_string_length);
 
   // Enforce a maximum length to ensure the string fits in the crash report;
-  // this is unlikely to be needed due to the limit of |kMaxBreadcrumbs| events.
+  // this is unlikely to be needed due to the limit of `kMaxBreadcrumbs` events.
   if (breadcrumbs_string.length() > kMaxDataLength)
     breadcrumbs_string.resize(kMaxDataLength);
 
diff --git a/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.h b/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.h
index 6b611a0b..958cb2c 100644
--- a/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.h
+++ b/components/breadcrumbs/core/crash_reporter_breadcrumb_observer.h
@@ -26,14 +26,6 @@
   // Creates a singleton instance that observes the BreadcrumbManager.
   static CrashReporterBreadcrumbObserver& GetInstance();
 
-  // Sets breadcrumb events associated with the previous application session.
-  // Note: this behaves the same as EventAdded(), but takes multiple events and
-  // adds them to the start of the breadcrumbs log.
-  void SetPreviousSessionEvents(const std::vector<std::string>& events);
-
-  // Removes all events.
-  void ResetForTesting();
-
  private:
   friend base::NoDestructor<CrashReporterBreadcrumbObserver>;
 
@@ -42,14 +34,10 @@
 
   // BreadcrumbObserver:
   void EventAdded(const std::string& event) override;
+  void PreviousSessionEventsAdded() override;
 
   // Updates the breadcrumbs stored in the crash log.
   void UpdateBreadcrumbEventsCrashKey();
-
-  // The full list of received breadcrumbs that will be sent to the crash
-  // report. Older events are at the front. A maximum size is enforced for
-  // privacy purposes, so old events may be removed when new events are added.
-  base::circular_deque<std::string> breadcrumbs_;
 };
 
 }  // namespace breadcrumbs
diff --git a/components/download/internal/background_service/proto/entry.proto b/components/download/internal/background_service/proto/entry.proto
index 9553232..629efd97 100644
--- a/components/download/internal/background_service/proto/entry.proto
+++ b/components/download/internal/background_service/proto/entry.proto
@@ -24,9 +24,10 @@
   MOUNTAIN_INTERNAL = 7;
   PLUGIN_VM_IMAGE = 8;
   OPTIMIZATION_GUIDE_PREDICTION_MODELS = 9;
+  BRUSCHETTA = 10;
 
   // New clients should be added above here.
-  BOUNDARY = 10;
+  BOUNDARY = 11;
 }
 
 // Custom key value pair provided by the client and will be sent back to client
diff --git a/components/download/internal/background_service/proto_conversions.cc b/components/download/internal/background_service/proto_conversions.cc
index be74ae46..c776acb 100644
--- a/components/download/internal/background_service/proto_conversions.cc
+++ b/components/download/internal/background_service/proto_conversions.cc
@@ -75,6 +75,8 @@
       return protodb::DownloadClient::PLUGIN_VM_IMAGE;
     case DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS:
       return protodb::DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS;
+    case DownloadClient::BRUSCHETTA:
+      return protodb::DownloadClient::BRUSCHETTA;
     case DownloadClient::BOUNDARY:
       return protodb::DownloadClient::BOUNDARY;
   }
@@ -106,6 +108,8 @@
       return DownloadClient::PLUGIN_VM_IMAGE;
     case protodb::DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS:
       return DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS;
+    case protodb::DownloadClient::BRUSCHETTA:
+      return DownloadClient::BRUSCHETTA;
     case protodb::DownloadClient::BOUNDARY:
       return DownloadClient::BOUNDARY;
   }
diff --git a/components/download/public/background_service/clients.cc b/components/download/public/background_service/clients.cc
index ad9780ad..ca9151e 100644
--- a/components/download/public/background_service/clients.cc
+++ b/components/download/public/background_service/clients.cc
@@ -26,6 +26,8 @@
       return "PluginVmImage";
     case DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS:
       return "OptimizationGuidePredictionModels";
+    case DownloadClient::BRUSCHETTA:
+      return "Bruschetta";
     case DownloadClient::BOUNDARY:
       break;
   }
diff --git a/components/download/public/background_service/clients.h b/components/download/public/background_service/clients.h
index 9943c8c..d254705 100644
--- a/components/download/public/background_service/clients.h
+++ b/components/download/public/background_service/clients.h
@@ -44,8 +44,10 @@
 
   OPTIMIZATION_GUIDE_PREDICTION_MODELS = 6,
 
+  BRUSCHETTA = 7,
+
   // New clients should be added above here.
-  BOUNDARY = 7,
+  BOUNDARY = 8,
 };
 
 // Get a string that represents a particular client. Used in histograms and
diff --git a/components/download/public/background_service/test/test_download_service.cc b/components/download/public/background_service/test/test_download_service.cc
index 62e33559..b5f646a 100644
--- a/components/download/public/background_service/test/test_download_service.cc
+++ b/components/download/public/background_service/test/test_download_service.cc
@@ -46,9 +46,6 @@
 TestDownloadService::TestDownloadService()
     : service_config_(std::make_unique<TestServiceConfig>()),
       logger_(std::make_unique<EmptyLogger>()),
-      is_ready_(false),
-      fail_at_start_(false),
-      file_size_(123456789u),
       client_(nullptr) {}
 
 TestDownloadService::~TestDownloadService() = default;
@@ -97,6 +94,14 @@
   for (auto iter = downloads_.begin(); iter != downloads_.end(); ++iter) {
     if (iter->value().guid == guid) {
       downloads_.erase(iter);
+
+      CompletionInfo completion_info(base::FilePath(), 0u);
+
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::BindOnce(&TestDownloadService::OnDownloadFailed,
+                         base::Unretained(this), guid, completion_info,
+                         Client::FailureReason::CANCELLED));
       return;
     }
   }
@@ -137,6 +142,10 @@
   hash256_ = hash256;
 }
 
+void TestDownloadService::SetFilePath(base::FilePath path) {
+  path_ = std::move(path);
+}
+
 void TestDownloadService::ProcessDownload() {
   DCHECK(!fail_at_start_);
   if (!is_ready_ || downloads_.empty())
@@ -150,7 +159,7 @@
     OnDownloadFailed(params.guid, completion_info,
                      Client::FailureReason::ABORTED);
   } else {
-    CompletionInfo completion_info(base::FilePath(), file_size_,
+    CompletionInfo completion_info(path_, file_size_,
                                    {params.request_params.url}, nullptr);
     completion_info.hash256 = hash256_;
     OnDownloadSucceeded(params.guid, completion_info);
diff --git a/components/download/public/background_service/test/test_download_service.h b/components/download/public/background_service/test/test_download_service.h
index 46109bb0..b22c4c0 100644
--- a/components/download/public/background_service/test/test_download_service.h
+++ b/components/download/public/background_service/test/test_download_service.h
@@ -56,6 +56,8 @@
 
   void SetHash256(const std::string& hash256);
 
+  void SetFilePath(base::FilePath path);
+
   void set_client(Client* client) { client_ = client; }
 
  private:
@@ -74,13 +76,14 @@
   std::unique_ptr<ServiceConfig> service_config_;
   std::unique_ptr<Logger> logger_;
 
-  bool is_ready_;
+  bool is_ready_ = false;
   std::string hash256_;
   std::string failed_download_id_;
-  bool fail_at_start_;
-  uint64_t file_size_;
+  bool fail_at_start_ = false;
+  uint64_t file_size_ = 123456789u;
+  base::FilePath path_;
 
-  raw_ptr<Client> client_;
+  raw_ptr<Client> client_ = nullptr;
 
   std::list<absl::optional<DownloadParams>> downloads_;
 };
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 9ccf590..332bc7123 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -786,12 +786,12 @@
   }
 }
 
-void ClientControlledShellSurface::SetSnappedToPrimary() {
+void ClientControlledShellSurface::SetSnapPrimary(float snap_ratio) {
   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetSnappedToPrimary");
   pending_window_state_ = chromeos::WindowStateType::kPrimarySnapped;
 }
 
-void ClientControlledShellSurface::SetSnappedToSecondary() {
+void ClientControlledShellSurface::SetSnapSecondary(float snap_ratio) {
   TRACE_EVENT0("exo", "ClientControlledShellSurface::SetSnappedToSecondary");
   pending_window_state_ = chromeos::WindowStateType::kSecondarySnapped;
 }
diff --git a/components/exo/client_controlled_shell_surface.h b/components/exo/client_controlled_shell_surface.h
index e704e66..c2f4b52 100644
--- a/components/exo/client_controlled_shell_surface.h
+++ b/components/exo/client_controlled_shell_surface.h
@@ -186,8 +186,8 @@
   bool IsInputEnabled(Surface* surface) const override;
   void OnSetFrame(SurfaceFrameType type) override;
   void OnSetFrameColors(SkColor active_color, SkColor inactive_color) override;
-  void SetSnappedToPrimary() override;
-  void SetSnappedToSecondary() override;
+  void SetSnapPrimary(float snap_ratio) override;
+  void SetSnapSecondary(float snap_ratio) override;
   void SetPip() override;
   void UnsetPip() override;
 
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index 9173fb6..4543a7c2 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -2522,7 +2522,7 @@
 
   ash::WindowState::Get(window)->OnWMEvent(&event);
   EXPECT_EQ(gfx::Rect(0, 32, 400, 568), delegate->requested_bounds().back());
-  shell_surface->SetSnappedToPrimary();
+  shell_surface->SetSnapPrimary(chromeos::kDefaultSnapRatio);
   shell_surface->SetGeometry(gfx::Rect(0, 0, 400, 568));
   surface->Commit();
 
@@ -2538,7 +2538,7 @@
   EXPECT_EQ(gfx::Rect(0, 32, 400, 568), delegate->requested_bounds().back());
 
   // Clean up state.
-  shell_surface->SetSnappedToPrimary();
+  shell_surface->SetSnapPrimary(chromeos::kDefaultSnapRatio);
   surface->Commit();
 }
 
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index d5640cd..0b6ca09 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -283,9 +283,11 @@
       /*allow_haptic_feedback=*/false);
 }
 
-void CommitSnap(aura::Window* window, chromeos::SnapDirection snap_direction) {
+void CommitSnap(aura::Window* window,
+                chromeos::SnapDirection snap_direction,
+                float snap_ratio) {
   chromeos::SnapController::Get()->CommitSnap(window, snap_direction,
-                                              chromeos::kDefaultSnapRatio);
+                                              snap_ratio);
 }
 
 }  // namespace
@@ -494,16 +496,19 @@
   ShowSnapPreview(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone);
 }
 
-void ShellSurfaceBase::SetSnappedToPrimary() {
-  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kPrimary);
+void ShellSurfaceBase::SetSnapPrimary(float snap_ratio) {
+  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kPrimary,
+             snap_ratio);
 }
 
-void ShellSurfaceBase::SetSnappedToSecondary() {
-  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kSecondary);
+void ShellSurfaceBase::SetSnapSecondary(float snap_ratio) {
+  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kSecondary,
+             snap_ratio);
 }
 
 void ShellSurfaceBase::UnsetSnap() {
-  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone);
+  CommitSnap(widget_->GetNativeWindow(), chromeos::SnapDirection::kNone,
+             chromeos::kDefaultSnapRatio);
 }
 
 void ShellSurfaceBase::SetCanGoBack() {
diff --git a/components/exo/shell_surface_base.h b/components/exo/shell_surface_base.h
index 383a938..606cf56e 100644
--- a/components/exo/shell_surface_base.h
+++ b/components/exo/shell_surface_base.h
@@ -13,6 +13,7 @@
 #include "base/gtest_prod_util.h"
 #include "chromeos/ui/base/display_util.h"
 #include "chromeos/ui/base/window_pin_type.h"
+#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "components/exo/surface_observer.h"
 #include "components/exo/surface_tree_host.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -231,8 +232,8 @@
   void ShowSnapPreviewToPrimary() override;
   void ShowSnapPreviewToSecondary() override;
   void HideSnapPreview() override;
-  void SetSnappedToPrimary() override;
-  void SetSnappedToSecondary() override;
+  void SetSnapPrimary(float snap_ratio) override;
+  void SetSnapSecondary(float snap_ratio) override;
   void UnsetSnap() override;
   void OnActivationRequested() override;
   void OnSetServerStartResize() override;
diff --git a/components/exo/sub_surface.h b/components/exo/sub_surface.h
index defafbc..2b6feca 100644
--- a/components/exo/sub_surface.h
+++ b/components/exo/sub_surface.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/observer_list.h"
+#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "components/exo/surface_delegate.h"
 #include "components/exo/surface_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -85,8 +86,8 @@
   void ShowSnapPreviewToPrimary() override {}
   void ShowSnapPreviewToSecondary() override {}
   void HideSnapPreview() override {}
-  void SetSnappedToPrimary() override {}
-  void SetSnappedToSecondary() override {}
+  void SetSnapPrimary(float snap_ratio) override {}
+  void SetSnapSecondary(float snap_ratio) override {}
   void UnsetSnap() override {}
   void SetCanGoBack() override {}
   void UnsetCanGoBack() override {}
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index d03295f0..7e20f4da 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -698,14 +698,14 @@
     delegate_->HideSnapPreview();
 }
 
-void Surface::SetSnappedToSecondary() {
+void Surface::SetSnapPrimary(float snap_ratio) {
   if (delegate_)
-    delegate_->SetSnappedToSecondary();
+    delegate_->SetSnapPrimary(snap_ratio);
 }
 
-void Surface::SetSnappedToPrimary() {
+void Surface::SetSnapSecondary(float snap_ratio) {
   if (delegate_)
-    delegate_->SetSnappedToPrimary();
+    delegate_->SetSnapSecondary(snap_ratio);
 }
 
 void Surface::UnsetSnap() {
@@ -1630,10 +1630,8 @@
                           : SkColors::kBlack;
     viz::SolidColorDrawQuad* solid_quad =
         render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
-    // TODO(crbug.com/1339335): Support AA quads coming from exo.
-    constexpr bool kForceAntiAliasingOff = true;
     solid_quad->SetNew(quad_state, quad_rect, quad_rect, color,
-                       kForceAntiAliasingOff);
+                       false /* force_anti_aliasing_off */);
   }
 
   render_pass->damage_rect.Union(gfx::ToEnclosedRect(damage_rect));
diff --git a/components/exo/surface.h b/components/exo/surface.h
index 4589efec..ee5cc75b 100644
--- a/components/exo/surface.h
+++ b/components/exo/surface.h
@@ -14,6 +14,7 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "cc/base/region.h"
+#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "components/exo/buffer.h"
 #include "components/exo/layer_tree_frame_sink_holder.h"
 #include "components/exo/surface_delegate.h"
@@ -233,8 +234,8 @@
 
   // Called when the client was snapped to primary or secondary position, or
   // reset.
-  void SetSnappedToSecondary();
-  void SetSnappedToPrimary();
+  void SetSnapPrimary(float snap_ratio);
+  void SetSnapSecondary(float snap_ratio);
   void UnsetSnap();
 
   // Whether the current client window can go back, as per its navigation list.
diff --git a/components/exo/surface_delegate.h b/components/exo/surface_delegate.h
index 14c3eb7..6192f82b6 100644
--- a/components/exo/surface_delegate.h
+++ b/components/exo/surface_delegate.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_EXO_SURFACE_DELEGATE_H_
 #define COMPONENTS_EXO_SURFACE_DELEGATE_H_
 
+#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -68,8 +69,8 @@
 
   // Called when the client was snapped to primary or secondary position, and
   // reset.
-  virtual void SetSnappedToPrimary() = 0;
-  virtual void SetSnappedToSecondary() = 0;
+  virtual void SetSnapPrimary(float snap_ratio) = 0;
+  virtual void SetSnapSecondary(float snap_ratio) = 0;
   virtual void UnsetSnap() = 0;
 
   // Whether the current client window can go back, as per its navigation list.
diff --git a/components/exo/surface_tree_host.h b/components/exo/surface_tree_host.h
index 63ad7f0..2b469db 100644
--- a/components/exo/surface_tree_host.h
+++ b/components/exo/surface_tree_host.h
@@ -105,8 +105,8 @@
   void ShowSnapPreviewToPrimary() override {}
   void ShowSnapPreviewToSecondary() override {}
   void HideSnapPreview() override {}
-  void SetSnappedToPrimary() override {}
-  void SetSnappedToSecondary() override {}
+  void SetSnapPrimary(float snap_ratio) override {}
+  void SetSnapSecondary(float snap_ratio) override {}
   void UnsetSnap() override {}
   void SetCanGoBack() override {}
   void UnsetCanGoBack() override {}
diff --git a/components/exo/test/exo_test_helper.cc b/components/exo/test/exo_test_helper.cc
index 8c250d5..05554d6e 100644
--- a/components/exo/test/exo_test_helper.cc
+++ b/components/exo/test/exo_test_helper.cc
@@ -104,9 +104,9 @@
            requested_state == chromeos::WindowStateType::kSecondarySnapped);
 
     if (requested_state == chromeos::WindowStateType::kPrimarySnapped)
-      shell_surface_->SetSnappedToPrimary();
+      shell_surface_->SetSnapPrimary(chromeos::kDefaultSnapRatio);
     else
-      shell_surface_->SetSnappedToSecondary();
+      shell_surface_->SetSnapSecondary(chromeos::kDefaultSnapRatio);
   }
 
   Commit();
diff --git a/components/exo/test/shell_surface_builder.cc b/components/exo/test/shell_surface_builder.cc
index 0fd22e5..bb80c96 100644
--- a/components/exo/test/shell_surface_builder.cc
+++ b/components/exo/test/shell_surface_builder.cc
@@ -345,10 +345,10 @@
         shell_surface->SetFullscreen(/*fullscreen=*/true);
         break;
       case chromeos::WindowStateType::kPrimarySnapped:
-        shell_surface->SetSnappedToPrimary();
+        shell_surface->SetSnapPrimary(chromeos::kDefaultSnapRatio);
         break;
       case chromeos::WindowStateType::kSecondarySnapped:
-        shell_surface->SetSnappedToSecondary();
+        shell_surface->SetSnapSecondary(chromeos::kDefaultSnapRatio);
         break;
       case chromeos::WindowStateType::kPip:
         shell_surface->SetPip();
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index 14b2fde5..d45ed6b 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -24,7 +24,7 @@
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="zaura_shell" version="47">
+  <interface name="zaura_shell" version="48">
     <description summary="aura_shell">
       The global interface exposing aura shell capabilities is used to
       instantiate an interface extension for a wl_surface object.
@@ -361,12 +361,14 @@
 
     <request name="set_snap_left" since="16">
       <description summary="snap the surface to the left.">
+        [Deprecated] Use set_snap_primary on zaura_toplevel.
         Request that surface is snapped to the left.
       </description>
     </request>
 
     <request name="set_snap_right" since="16">
       <description summary="snap the surface to the right.">
+        [Deprecated] Use set_snap_secondary on zaura_toplevel.
         Request that surface is snapped to the right.
       </description>
     </request>
@@ -751,7 +753,7 @@
     </event>
   </interface>
 
-  <interface name="zaura_toplevel" version="46">
+  <interface name="zaura_toplevel" version="48">
     <description summary="aura shell interface to the toplevel shell">
       An interface to the toplevel shell, which allows the
       client to access shell specific functionality.
@@ -1051,6 +1053,23 @@
       </description>
       <arg name="scale_factor_as_uint" type="uint"/>
     </request>
+
+      <!-- Version 48 additions-->
+    <request name="set_snap_primary" since="48">
+      <description summary="snap the surface to the primary snap position.">
+        Request that surface is snapped to the left or top if primary layout,
+        right or bottom otherwise.
+      </description>/>
+      <arg name="snap_ratio_as_uint" type="uint"/>
+    </request>
+
+    <request name="set_snap_secondary" since="48">
+      <description summary="snap the surface to the secondary snap position.">
+        Request that surface is snapped to the right or bottom if primary
+        layout, left or top otherwise.
+      </description>
+      <arg name="snap_ratio_as_uint" type="uint"/>
+    </request>
   </interface>
 
   <interface name="zaura_popup" version="46">
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index 2dab565..9d57224 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -64,6 +64,8 @@
 constexpr int kAuraShellSeatObserverPriority = 1;
 static_assert(Seat::IsValidObserverPriority(kAuraShellSeatObserverPriority),
               "kAuraShellSeatObserverPriority is not in the valid range.");
+static_assert(sizeof(uint32_t) == sizeof(float),
+              "Sizes much match for reinterpret cast to be meaningful");
 
 // A property key containing a boolean set to true if na aura surface object is
 // associated with surface object.
@@ -207,11 +209,13 @@
   GetUserDataAs<AuraSurface>(resource)->IntentToSnap(snap_direction);
 }
 
-void aura_surface_set_snap_left(wl_client* client, wl_resource* resource) {
+void aura_surface_set_snap_left_deprecated(wl_client* client,
+                                           wl_resource* resource) {
   GetUserDataAs<AuraSurface>(resource)->SetSnapPrimary();
 }
 
-void aura_surface_set_snap_right(wl_client* client, wl_resource* resource) {
+void aura_surface_set_snap_right_deprecated(wl_client* client,
+                                            wl_resource* resource) {
   GetUserDataAs<AuraSurface>(resource)->SetSnapSecondary();
 }
 
@@ -307,8 +311,8 @@
     aura_surface_set_client_surface_str_id,
     aura_surface_set_server_start_resize,
     aura_surface_intent_to_snap,
-    aura_surface_set_snap_left,
-    aura_surface_set_snap_right,
+    aura_surface_set_snap_left_deprecated,
+    aura_surface_set_snap_right_deprecated,
     aura_surface_unset_snap,
     aura_surface_set_window_session_id,
     aura_surface_set_can_go_back,
@@ -431,11 +435,11 @@
 }
 
 void AuraSurface::SetSnapPrimary() {
-  surface_->SetSnappedToPrimary();
+  surface_->SetSnapPrimary(chromeos::kDefaultSnapRatio);
 }
 
 void AuraSurface::SetSnapSecondary() {
-  surface_->SetSnappedToSecondary();
+  surface_->SetSnapSecondary(chromeos::kDefaultSnapRatio);
 }
 
 void AuraSurface::UnsetSnap() {
@@ -836,6 +840,14 @@
   shell_surface_->UnsetFloat();
 }
 
+void AuraToplevel::SetSnapPrimary(float snap_ratio) {
+  shell_surface_->SetSnapPrimary(snap_ratio);
+}
+
+void AuraToplevel::SetSnapSecondary(float snap_ratio) {
+  shell_surface_->SetSnapSecondary(snap_ratio);
+}
+
 template <class T>
 void AddState(wl_array* states, T state) {
   T* value = static_cast<T*>(wl_array_add(states, sizeof(T)));
@@ -1272,6 +1284,20 @@
   GetUserDataAs<AuraToplevel>(resource)->UnsetFloat();
 }
 
+void aura_toplevel_set_snap_primary(wl_client* client,
+                                    wl_resource* resource,
+                                    uint32_t snap_ratio_as_uint) {
+  float snap_ratio = *reinterpret_cast<float*>(&snap_ratio_as_uint);
+  GetUserDataAs<AuraToplevel>(resource)->SetSnapPrimary(snap_ratio);
+}
+
+void aura_toplevel_set_snap_secondary(wl_client* client,
+                                      wl_resource* resource,
+                                      uint32_t snap_ratio_as_uint) {
+  float snap_ratio = *reinterpret_cast<float*>(&snap_ratio_as_uint);
+  GetUserDataAs<AuraToplevel>(resource)->SetSnapSecondary(snap_ratio);
+}
+
 void aura_toplevel_set_restore_info_with_window_id_source(
     wl_client* client,
     wl_resource* resource,
@@ -1371,6 +1397,8 @@
     aura_toplevel_deactivate,
     aura_toplevel_set_fullscreen_mode,
     aura_toplevel_set_scale_factor,
+    aura_toplevel_set_snap_primary,
+    aura_toplevel_set_snap_secondary,
 };
 
 void aura_popup_surface_submission_in_pixel_coordinates(wl_client* client,
@@ -1411,8 +1439,6 @@
 void aura_popup_set_scale_factor(wl_client* client,
                                  wl_resource* resource,
                                  uint32_t scale_factor_as_uint) {
-  static_assert(sizeof(uint32_t) == sizeof(float),
-                "Sizes much match for reinterpret cast to be meaningful");
   float scale_factor = *reinterpret_cast<float*>(&scale_factor_as_uint);
   GetUserDataAs<AuraPopup>(resource)->SetScaleFactor(scale_factor);
 }
diff --git a/components/exo/wayland/zaura_shell.h b/components/exo/wayland/zaura_shell.h
index 54236fad..85f95d1 100644
--- a/components/exo/wayland/zaura_shell.h
+++ b/components/exo/wayland/zaura_shell.h
@@ -31,7 +31,7 @@
 namespace wayland {
 class SerialTracker;
 
-constexpr uint32_t kZAuraShellVersion = 47;
+constexpr uint32_t kZAuraShellVersion = 48;
 
 // Adds bindings to the Aura Shell. Normally this implies Ash on ChromeOS
 // builds. On non-ChromeOS builds the protocol provides access to Aura windowing
@@ -145,6 +145,8 @@
   void SetSystemModal(bool modal);
   void SetFloat();
   void UnsetFloat();
+  void SetSnapPrimary(float snap_ratio);
+  void SetSnapSecondary(float snap_ratio);
 
   void OnConfigure(const gfx::Rect& bounds,
                    chromeos::WindowStateType state_type,
diff --git a/components/exo/wayland/zaura_shell_unittest.cc b/components/exo/wayland/zaura_shell_unittest.cc
index 249bf74..6bb6a39 100644
--- a/components/exo/wayland/zaura_shell_unittest.cc
+++ b/components/exo/wayland/zaura_shell_unittest.cc
@@ -107,8 +107,8 @@
   MOCK_METHOD(void, ShowSnapPreviewToPrimary, (), (override));
   MOCK_METHOD(void, ShowSnapPreviewToSecondary, (), (override));
   MOCK_METHOD(void, HideSnapPreview, (), (override));
-  MOCK_METHOD(void, SetSnappedToSecondary, (), (override));
-  MOCK_METHOD(void, SetSnappedToPrimary, (), (override));
+  MOCK_METHOD(void, SetSnapPrimary, (float snap_ratio), (override));
+  MOCK_METHOD(void, SetSnapSecondary, (float snap_ratio), (override));
   MOCK_METHOD(void, UnsetSnap, (), (override));
   MOCK_METHOD(void, SetCanGoBack, (), (override));
   MOCK_METHOD(void, UnsetCanGoBack, (), (override));
diff --git a/components/exo/wayland/zcr_remote_shell_impl.cc b/components/exo/wayland/zcr_remote_shell_impl.cc
index b137ed9..a745919 100644
--- a/components/exo/wayland/zcr_remote_shell_impl.cc
+++ b/components/exo/wayland/zcr_remote_shell_impl.cc
@@ -1284,13 +1284,14 @@
 
 void remote_surface_set_snapped_to_left(wl_client* client,
                                         wl_resource* resource) {
-  GetUserDataAs<ClientControlledShellSurface>(resource)->SetSnappedToPrimary();
+  GetUserDataAs<ClientControlledShellSurface>(resource)->SetSnapPrimary(
+      chromeos::kDefaultSnapRatio);
 }
 
 void remote_surface_set_snapped_to_right(wl_client* client,
                                          wl_resource* resource) {
-  GetUserDataAs<ClientControlledShellSurface>(resource)
-      ->SetSnappedToSecondary();
+  GetUserDataAs<ClientControlledShellSurface>(resource)->SetSnapSecondary(
+      chromeos::kDefaultSnapRatio);
 }
 
 void remote_surface_start_resize(wl_client* client,
diff --git a/components/exo/wayland/zcr_remote_shell_impl_unittest.cc b/components/exo/wayland/zcr_remote_shell_impl_unittest.cc
index b777b7c..59106a84 100644
--- a/components/exo/wayland/zcr_remote_shell_impl_unittest.cc
+++ b/components/exo/wayland/zcr_remote_shell_impl_unittest.cc
@@ -235,7 +235,7 @@
   // Snap window.
   ash::WindowSnapWMEvent event(ash::WM_EVENT_SNAP_PRIMARY);
   ash::WindowState::Get(window)->OnWMEvent(&event);
-  shell_surface->SetSnappedToPrimary();
+  shell_surface->SetSnapPrimary(chromeos::kDefaultSnapRatio);
   shell_surface->SetGeometry(gfx::Rect(0, 0, 400, 520));
   surface->Commit();
 
diff --git a/components/feedback/redaction_tool.cc b/components/feedback/redaction_tool.cc
index 60cd1b5f..a5c31816 100644
--- a/components/feedback/redaction_tool.cc
+++ b/components/feedback/redaction_tool.cc
@@ -17,7 +17,11 @@
 #include "build/chromeos_buildflags.h"
 #include "components/feedback/pii_types.h"
 #include "net/base/ip_address.h"
+#ifdef USE_SYSTEM_RE2
+#include <re2/re2.h>
+#else
 #include "third_party/re2/src/re2/re2.h"
+#endif //USE_SYSTEM_RE2
 
 using re2::RE2;
 
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinator.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinator.java
index ca02317..b79e225 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinator.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinator.java
@@ -215,6 +215,12 @@
                     candidates.set(1, null);
                 }
             } else {
+                // TODO(crbug.com/1382275): simplify this into one step.
+                // Split the transition: [m1, null] -> [m2, null] into two steps:
+                // [m1, null] -> [null, null] -> [m2, null]
+                nextFront = nextBack = null;
+                candidates.set(0, null);
+                candidates.set(1, null);
                 recordStackingAnimationType(StackingAnimationType.REMOVE_FRONT_ONLY);
             }
         } else if (currentFront == nextFront) {
@@ -253,8 +259,12 @@
         if (currentFront == null) {
             // No message is being displayed now: trigger #onStartShowing.
             mCurrentDisplayedMessages = new ArrayList<>(candidates);
-            mMessageQueueDelegate.onStartShowing(
-                    () -> { triggerStackingAnimation(candidates, onFinished); });
+            mMessageQueueDelegate.onStartShowing(() -> {
+                if (candidates.get(0) == mCurrentDisplayedMessages.get(0)
+                        && candidates.get(1) == mCurrentDisplayedMessages.get(1)) {
+                    triggerStackingAnimation(candidates, onFinished);
+                }
+            });
         } else if (nextFront == null) {
             // All messages will be hidden: trigger #onFinishHiding.
             Runnable runnable = () -> {
@@ -280,8 +290,8 @@
                 mMessageQueueDelegate.onAnimationEnd();
                 onFinished.run();
             }));
-            mAnimatorStartCallback.onResult(mAnimatorSet);
             mMessageQueueDelegate.onAnimationStart();
+            mAnimatorStartCallback.onResult(mAnimatorSet);
         };
         if (candidates.get(0) == null) {
             runnable.run();
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinatorUnitTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinatorUnitTest.java
index 2b1f6d0..36196d09 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinatorUnitTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageAnimationCoordinatorUnitTest.java
@@ -7,6 +7,7 @@
 import static android.os.Looper.getMainLooper;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doNothing;
@@ -345,6 +346,38 @@
         Assert.assertEquals(1, d2.getDelta());
     }
 
+    // Test pushing front message to back.
+    // [m1, null] -> [m2, null]
+    // TODO(crbug.com/1382275): simplify this into one step.
+    // This should be done in two steps:  [m1, null] -> [null, null] -> [m2, null]
+    @Test
+    @SmallTest
+    public void testUpdateFrontMessageOnly() {
+        MessageState m1 = buildMessageState();
+        setMessageIdentifier(m1, 1);
+        MessageState m2 = buildMessageState();
+        setMessageIdentifier(m2, 2);
+        mAnimationCoordinator.updateWithStacking(Arrays.asList(m1, null), false, () -> {});
+
+        verify(m1.handler).show(Position.INVISIBLE, Position.FRONT);
+        verify(m2.handler, never()).show(Position.FRONT, Position.BACK);
+
+        mAnimationCoordinator.updateWithStacking(Arrays.asList(m2, null), false, () -> {});
+        verify(m2.handler, never()).show(anyInt(), anyInt());
+        verify(m1.handler).hide(eq(Position.FRONT), eq(Position.INVISIBLE), anyBoolean());
+
+        var currentMessages = mAnimationCoordinator.getCurrentDisplayedMessages();
+        Assert.assertArrayEquals(new MessageState[] {null, null}, currentMessages.toArray());
+
+        mAnimationCoordinator.updateWithStacking(Arrays.asList(m2, null), false, () -> {});
+        verify(m2.handler).show(Position.INVISIBLE, Position.FRONT);
+        // Do not trigger #hide again.
+        verify(m1.handler, times(1)).hide(eq(Position.FRONT), eq(Position.INVISIBLE), anyBoolean());
+
+        currentMessages = mAnimationCoordinator.getCurrentDisplayedMessages();
+        Assert.assertArrayEquals(new MessageState[] {m2, null}, currentMessages.toArray());
+    }
+
     /**
      * Test messages are hidden before #onStartShow is done.
      */
diff --git a/components/omnibox/browser/actions/history_clusters_action.cc b/components/omnibox/browser/actions/history_clusters_action.cc
index 6e08b5f6..81f7fa8 100644
--- a/components/omnibox/browser/actions/history_clusters_action.cc
+++ b/components/omnibox/browser/actions/history_clusters_action.cc
@@ -98,7 +98,8 @@
 
 HistoryClustersAction::HistoryClustersAction(
     const std::string& query,
-    const history::ClusterKeywordData& matched_keyword_data)
+    const history::ClusterKeywordData& matched_keyword_data,
+    bool takes_over_match)
     : OmniboxAction(
           OmniboxAction::LabelStrings(
               IDS_OMNIBOX_ACTION_HISTORY_CLUSTERS_SEARCH_HINT,
@@ -107,7 +108,8 @@
               IDS_ACC_OMNIBOX_ACTION_HISTORY_CLUSTERS_SEARCH),
           GetFullJourneysUrlForQuery(query)),
       matched_keyword_data_(matched_keyword_data),
-      query_(query) {
+      query_(query),
+      takes_over_match_(takes_over_match) {
 #if BUILDFLAG(IS_ANDROID)
     CreateOrUpdateJavaObject(query);
 #endif
@@ -166,6 +168,10 @@
   OmniboxAction::Execute(context);
 }
 
+bool HistoryClustersAction::TakesOverMatch() const {
+  return takes_over_match_;
+}
+
 int32_t HistoryClustersAction::GetID() const {
   return static_cast<int32_t>(OmniboxActionId::HISTORY_CLUSTERS);
 }
@@ -252,7 +258,8 @@
           service->DoesQueryMatchAnyCluster(query);
       if (matched_keyword_data) {
         match.action = base::MakeRefCounted<HistoryClustersAction>(
-            query, std::move(matched_keyword_data.value()));
+            query, std::move(matched_keyword_data.value()),
+            /*takes_over_match=*/false);
       }
     } else if (GetConfig().omnibox_action_on_urls) {
       // We do the URL stripping here, because we need it to both execute the
@@ -262,7 +269,8 @@
           history_clusters::ComputeURLKeywordForLookup(match.destination_url);
       if (service->DoesURLMatchAnyCluster(url_keyword)) {
         match.action = base::MakeRefCounted<HistoryClustersAction>(
-            url_keyword, history::ClusterKeywordData());
+            url_keyword, history::ClusterKeywordData(),
+            /*takes_over_match=*/false);
       }
     }
 
diff --git a/components/omnibox/browser/actions/history_clusters_action.h b/components/omnibox/browser/actions/history_clusters_action.h
index 2bd296b..854a6ede 100644
--- a/components/omnibox/browser/actions/history_clusters_action.h
+++ b/components/omnibox/browser/actions/history_clusters_action.h
@@ -46,12 +46,13 @@
 // Made public for testing.
 class HistoryClustersAction : public OmniboxAction {
  public:
-  HistoryClustersAction(
-      const std::string& query,
-      const history::ClusterKeywordData& matched_keyword_data);
+  HistoryClustersAction(const std::string& query,
+                        const history::ClusterKeywordData& matched_keyword_data,
+                        bool takes_over_match);
 
   void RecordActionShown(size_t position, bool executed) const override;
   void Execute(ExecutionContext& context) const override;
+  bool TakesOverMatch() const override;
   int32_t GetID() const override;
 #if defined(SUPPORT_PEDALS_VECTOR_ICONS)
   const gfx::VectorIcon& GetVectorIcon() const override;
@@ -72,6 +73,9 @@
   // Used to open journeys in side panel with relevant clusters
   std::string query_;
 
+  // Used to make the action chip take over the whole match.
+  bool takes_over_match_ = false;
+
 #if BUILDFLAG(IS_ANDROID)
   base::android::ScopedJavaGlobalRef<jobject> j_omnibox_action_;
 #endif
diff --git a/components/omnibox/browser/actions/omnibox_action.cc b/components/omnibox/browser/actions/omnibox_action.cc
index 304c0b30..e944221 100644
--- a/components/omnibox/browser/actions/omnibox_action.cc
+++ b/components/omnibox/browser/actions/omnibox_action.cc
@@ -86,6 +86,10 @@
   return true;
 }
 
+bool OmniboxAction::TakesOverMatch() const {
+  return false;
+}
+
 #if defined(SUPPORT_PEDALS_VECTOR_ICONS)
 const gfx::VectorIcon& OmniboxAction::GetVectorIcon() const {
   // TODO(tommycli): Replace with real icon.
diff --git a/components/omnibox/browser/actions/omnibox_action.h b/components/omnibox/browser/actions/omnibox_action.h
index 304eb494..48173e4 100644
--- a/components/omnibox/browser/actions/omnibox_action.h
+++ b/components/omnibox/browser/actions/omnibox_action.h
@@ -149,6 +149,12 @@
   virtual bool IsReadyToTrigger(const AutocompleteInput& input,
                                 const AutocompleteProviderClient& client) const;
 
+  // Returns true if the Action should take over the whole match - that is:
+  // If the user presses Enter or clicks on the match at all, the navigation
+  // is ignored and the action is executed. Note, when this returns true, the
+  // action chip should be un-rendered, because the whole match IS the action.
+  virtual bool TakesOverMatch() const;
+
 #if defined(SUPPORT_PEDALS_VECTOR_ICONS)
   // Returns the vector icon to represent this Action.
   virtual const gfx::VectorIcon& GetVectorIcon() const;
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 22dfd4a..b42a1b57 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -303,8 +303,9 @@
   DemoteOnDeviceSearchSuggestions();
 #endif  // !BUILDFLAG(IS_IOS)
 
+  const auto& page_classification = input.current_page_classification();
   CompareWithDemoteByType<AutocompleteMatch> comparing_object(
-      input.current_page_classification());
+      page_classification);
 
 #if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
   // Because tail suggestions are a "last resort", we cull the tail suggestions
@@ -395,41 +396,49 @@
   const size_t num_matches =
       CalculateNumMatches(is_zero_suggest, matches_, comparing_object);
 
-  // TODO(manukh): Limiting should be done by the grouping framework.
-  if (!is_zero_suggest)
+  // Group and trim suggestions to the given limit.
+  if (!is_zero_suggest) {
+    // Until limits are applied by the grouping framework, typed suggestions are
+    // trimmed then grouped.
+    // TODO(manukh): Limiting should be done by the grouping framework.
     matches_.resize(num_matches);
 
     // Group search suggestions above URL suggestions.
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-  if (matches_.size() > 2 &&
-      !base::FeatureList::IsEnabled(omnibox::kAdaptiveSuggestionsCount)) {
-#else
-  if (matches_.size() > 2) {
-#endif
-    // TODO(manukh): Grouping search v URL (actually
-    //  `GroupSuggestionsBySearchVsURL` now groups by other types as well)
-    //  should be done by the grouping framework.
-    GroupSuggestionsBySearchVsURL(std::next(matches_.begin()), matches_.end());
-  }
-
-  GroupAndDemoteMatchesInGroups();
-
-  if (is_zero_suggest) {
-    if (base::FeatureList::IsEnabled(omnibox::kRetainSuggestionsWithHeaders)) {
-      size_t num_regular_suggestions = 0;
-      base::EraseIf(matches_,
-                    [&num_regular_suggestions, num_matches](const auto& match) {
-                      // Trim suggestions without group IDs to the given limit.
-                      if (!match.suggestion_group_id.has_value()) {
-                        num_regular_suggestions++;
-                        return num_regular_suggestions > num_matches;
-                      }
-                      // Do not trim suggestions with group IDs.
-                      return false;
-                    });
-    } else {
-      matches_.resize(num_matches);
+    if (matches_.size() > 2 &&
+        !base::FeatureList::IsEnabled(omnibox::kAdaptiveSuggestionsCount)) {
+      // TODO(manukh): Grouping search v URL (actually
+      //  `GroupSuggestionsBySearchVsURL` now groups by other types as well)
+      //  should be done by the grouping framework.
+      GroupSuggestionsBySearchVsURL(std::next(matches_.begin()),
+                                    matches_.end());
     }
+    GroupAndDemoteMatchesInGroups();
+
+  } else if (base::FeatureList::IsEnabled(omnibox::kKeepSecondaryZeroSuggest)) {
+    // Until limits are applied by the grouping framework, zero-prefix
+    // suggestions are grouped then trimmed.
+    // TODO(manukh): Limiting should be done by the grouping framework.
+    GroupAndDemoteMatchesInGroups();
+    size_t num_primary_suggestions = 0;
+    base::EraseIf(matches_, [&](const auto& match) {
+      if (!match.suggestion_group_id.has_value() ||
+          GetSideTypeForSuggestionGroup(match.suggestion_group_id.value()) ==
+              omnibox::GroupConfig_SideType_DEFAULT_PRIMARY) {
+        // Trim the primary suggestions to the given limit.
+        return ++num_primary_suggestions > num_matches;
+      } else {
+        // Keep the secondary suggestions for the NTP realbox.
+        // TODO(ender): Add appropriate page classification for Android.
+        return page_classification != OmniboxEventProto::NTP_REALBOX;
+      }
+    });
+
+  } else {
+    // Until limits are applied by the grouping framework, zero-prefix
+    // suggestions are grouped then trimmed.
+    // TODO(manukh): Limiting should be done by the grouping framework.
+    GroupAndDemoteMatchesInGroups();
+    matches_.resize(num_matches);
   }
 
 #if DCHECK_IS_ON()
@@ -1066,6 +1075,16 @@
   return it->second.section();
 }
 
+omnibox::GroupConfig_SideType AutocompleteResult::GetSideTypeForSuggestionGroup(
+    omnibox::GroupId suggestion_group_id) const {
+  auto it = suggestion_groups_map().find(suggestion_group_id);
+  if (it == suggestion_groups_map().end()) {
+    return omnibox::GroupConfig_SideType_DEFAULT_PRIMARY;
+  }
+
+  return it->second.side_type();
+}
+
 void AutocompleteResult::MergeSuggestionGroupsMap(
     const omnibox::GroupConfigMap& suggestion_groups_map) {
   for (const auto& entry : suggestion_groups_map) {
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index d0731de..4c9823e 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -255,6 +255,12 @@
   omnibox::GroupSection GetSectionForSuggestionGroup(
       omnibox::GroupId suggestion_group_id) const;
 
+  // Returns the side type associated with `suggestion_group_id`.
+  // Returns omnibox::DEFAULT_PRIMARY if `suggestion_group_id` is not found in
+  // `suggestion_groups_map_`.
+  omnibox::GroupConfig_SideType GetSideTypeForSuggestionGroup(
+      omnibox::GroupId suggestion_group_id) const;
+
   // Updates |suggestion_groups_map_| with the suggestion groups information
   // from |suggeston_groups_map|. Followed by GroupAndDemoteMatchesInGroups()
   // which sorts the matches based on the order in which their groups should
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index 845b3ca..ff3443db 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -1891,8 +1891,7 @@
         {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "6"}}},
        {omnibox::kMaxZeroSuggestMatches,
         {{OmniboxFieldTrial::kMaxZeroSuggestMatchesParam, "5"}}}},
-      {omnibox::kDynamicMaxAutocomplete,
-       omnibox::kRetainSuggestionsWithHeaders});
+      {omnibox::kDynamicMaxAutocomplete, omnibox::kKeepSecondaryZeroSuggest});
 
   const auto group_1 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
   const auto group_2 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED_ENTITY_CHIPS;
@@ -1908,10 +1907,11 @@
   ACMatches matches;
   PopulateAutocompleteMatches(data, std::size(data), &matches);
 
-  // Suggestion groups have the omnibox::SECTION_DEFAULT by default.
+  // Suggestion groups have the omnibox::SECTION_DEFAULT and
+  // omnibox::GroupConfig_SideType_DEFAULT_PRIMARY by default.
   omnibox::GroupConfigMap suggestion_groups_map;
-  suggestion_groups_map[group_1].set_header_text("1");
-  suggestion_groups_map[group_2].set_header_text("2");
+  suggestion_groups_map[group_1];
+  suggestion_groups_map[group_2];
 
   {
     SCOPED_TRACE("Input 'a'");
@@ -1987,7 +1987,7 @@
         {1, 2, 600, false, {}, AutocompleteMatchType::HISTORY_URL},
         // omnibox::SECTION_REMOTE_ZPS_1 comes first.
         {2, 1, 700, false, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-        // omnibox::SECTION_REMOTE_ZPS_1 comes second.
+        // omnibox::SECTION_REMOTE_ZPS_2 comes second.
         {5, 2, 1000, false, {}, AutocompleteMatchType::SEARCH_SUGGEST},
         {4, 1, 900, false, {}, AutocompleteMatchType::SEARCH_SUGGEST},
     }};
@@ -2022,14 +2022,14 @@
 }
 
 TEST_F(AutocompleteResultTest,
-       SortAndCull_RetainSuggestionGroups_NoDynamicMaxAutocomplete) {
+       SortAndCull_KeepSecondaryZeroSuggest_NoDynamicMaxAutocomplete) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeaturesAndParameters(
       {{omnibox::kUIExperimentMaxAutocompleteMatches,
         {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "2"}}},
        {omnibox::kMaxZeroSuggestMatches,
         {{OmniboxFieldTrial::kMaxZeroSuggestMatchesParam, "2"}}},
-       {omnibox::kRetainSuggestionsWithHeaders, {}}},
+       {omnibox::kKeepSecondaryZeroSuggest, {}}},
       {{omnibox::kDynamicMaxAutocomplete}});
 
   const auto group_1 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
@@ -2049,10 +2049,111 @@
   ACMatches matches;
   PopulateAutocompleteMatches(data, std::size(data), &matches);
 
+  // Suggestion groups have the omnibox::SECTION_DEFAULT and
+  // omnibox::GroupConfig_SideType_DEFAULT_PRIMARY by default.
+  omnibox::GroupConfigMap suggestion_groups_map;
+  suggestion_groups_map[group_1];
+  suggestion_groups_map[group_2].set_side_type(
+      omnibox::GroupConfig_SideType_SECONDARY);
+
+  {
+    SCOPED_TRACE("Input 'a'");
+    AutocompleteInput typed_input(u"a", metrics::OmniboxEventProto::OTHER,
+                                  TestSchemeClassifier());
+    AutocompleteResult result;
+    result.MergeSuggestionGroupsMap(suggestion_groups_map);
+    result.AppendMatches(matches);
+    result.SortAndCull(typed_input, template_url_service_.get());
+
+    // Showing secondary suggestions has no effect on typed matched.
+    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false));
+    const std::array<TestData, 2> expected_data{{
+        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+    }};
+    AssertResultMatches(result, expected_data.begin(), expected_data.size());
+  }
+  {
+    SCOPED_TRACE("Zero input");
+    AutocompleteInput zero_prefix_input(u"", metrics::OmniboxEventProto::NTP,
+                                        TestSchemeClassifier());
+    zero_prefix_input.set_focus_type(
+        metrics::OmniboxFocusType::INTERACTION_FOCUS);
+    AutocompleteResult result;
+
+    result.MergeSuggestionGroupsMap(suggestion_groups_map);
+    result.AppendMatches(matches);
+    result.SortAndCull(zero_prefix_input, template_url_service_.get());
+
+    // Showing secondary suggestions is not supported in NTP.
+    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
+    const std::array<TestData, 2> expected_data{{
+        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+    }};
+    AssertResultMatches(result, expected_data.begin(), expected_data.size());
+  }
+  {
+    AutocompleteInput zero_prefix_input(
+        u"", metrics::OmniboxEventProto::NTP_REALBOX, TestSchemeClassifier());
+    zero_prefix_input.set_focus_type(
+        metrics::OmniboxFocusType::INTERACTION_FOCUS);
+    AutocompleteResult result;
+
+    result.MergeSuggestionGroupsMap(suggestion_groups_map);
+    result.AppendMatches(matches);
+    result.SortAndCull(zero_prefix_input, template_url_service_.get());
+
+    // Showing secondary suggestions is supported in NTP_REALBOX.
+    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
+    const std::array<TestData, 5> expected_data{{
+        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        // secondary suggestions are shown.
+        {7, 1, 1094, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+        {8, 1, 1093, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+        {9, 1, 1092, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+    }};
+    AssertResultMatches(result, expected_data.begin(), expected_data.size());
+  }
+}
+
+TEST_F(AutocompleteResultTest,
+       SortAndCull_KeepSecondaryZeroSuggest_WithDynamicMaxAutocomplete) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{omnibox::kUIExperimentMaxAutocompleteMatches,
+        {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "2"}}},
+       {omnibox::kMaxZeroSuggestMatches,
+        {{OmniboxFieldTrial::kMaxZeroSuggestMatchesParam, "2"}}},
+       {omnibox::kDynamicMaxAutocomplete,
+        {{OmniboxFieldTrial::kDynamicMaxAutocompleteIncreasedLimitParam, "3"}}},
+       {omnibox::kKeepSecondaryZeroSuggest, {}}},
+      {});
+
+  const auto group_1 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
+  const auto group_2 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED_ENTITY_CHIPS;
+  TestData data[] = {
+      {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+      {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+      {3, 1, 1098, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+      {4, 1, 1097, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+      {5, 1, 1096, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+      {5, 2, 1097, false, {}, AutocompleteMatchType::HISTORY_URL},
+      {6, 1, 1095, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_1},
+      {7, 1, 1094, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+      {8, 1, 1093, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+      {9, 1, 1092, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
+  };
+  ACMatches matches;
+  PopulateAutocompleteMatches(data, std::size(data), &matches);
+
   // Suggestion groups have the omnibox::SECTION_DEFAULT by default.
   omnibox::GroupConfigMap suggestion_groups_map;
-  suggestion_groups_map[group_1].set_header_text("1");
-  suggestion_groups_map[group_2].set_header_text("2");
+  suggestion_groups_map[group_1].set_side_type(
+      omnibox::GroupConfig_SideType_DEFAULT_PRIMARY);
+  suggestion_groups_map[group_2].set_side_type(
+      omnibox::GroupConfig_SideType_SECONDARY);
 
   {
     SCOPED_TRACE("Input 'a'");
@@ -2063,89 +2164,7 @@
     result.AppendMatches(matches);
     result.SortAndCull(typed_input, template_url_service_.get());
 
-    // Retaining suggestion groups has no effect on typed matched.
-    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false));
-    const std::array<TestData, 2> expected_data{{
-        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-    }};
-    AssertResultMatches(result, expected_data.begin(), expected_data.size());
-  }
-  {
-    SCOPED_TRACE("Zero input");
-    AutocompleteInput zero_prefix_input(u"", metrics::OmniboxEventProto::NTP,
-                                        TestSchemeClassifier());
-    zero_prefix_input.set_focus_type(
-        metrics::OmniboxFocusType::INTERACTION_FOCUS);
-    AutocompleteResult result;
-
-    result.MergeSuggestionGroupsMap(suggestion_groups_map);
-    result.AppendMatches(matches);
-    result.SortAndCull(zero_prefix_input, template_url_service_.get());
-
-    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
-    const std::array<TestData, 6> expected_data{{
-        // Default suggestion comes 1st.
-        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-        // Group one is scored higher
-        {6, 1, 1095, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_1},
-        // Group two is scored lower
-        {7, 1, 1094, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-        {8, 1, 1093, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-        {9, 1, 1092, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-    }};
-    AssertResultMatches(result, expected_data.begin(), expected_data.size());
-  }
-}
-
-TEST_F(AutocompleteResultTest,
-       SortAndCull_RetainSuggestionGroups_WithDynamicMaxAutocomplete) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeaturesAndParameters(
-      {{omnibox::kUIExperimentMaxAutocompleteMatches,
-        {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "2"}}},
-       {omnibox::kMaxZeroSuggestMatches,
-        {{OmniboxFieldTrial::kMaxZeroSuggestMatchesParam, "2"}}},
-       {omnibox::kDynamicMaxAutocomplete,
-        {{OmniboxFieldTrial::kDynamicMaxAutocompleteIncreasedLimitParam, "3"}}},
-       {omnibox::kRetainSuggestionsWithHeaders, {}}},
-      {});
-
-  const auto group_1 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED;
-  const auto group_2 = omnibox::GROUP_PREVIOUS_SEARCH_RELATED_ENTITY_CHIPS;
-  TestData data[] = {
-      {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-      {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-      {3, 1, 1098, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-      {4, 1, 1097, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-      {5, 1, 1096, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-      {5, 2, 1097, false, {}, AutocompleteMatchType::HISTORY_URL},
-      {6, 1, 1095, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_1},
-      {7, 1, 1094, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-      {8, 1, 1093, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-      {9, 1, 1092, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-  };
-  ACMatches matches;
-  PopulateAutocompleteMatches(data, std::size(data), &matches);
-
-  // Set sections that contradict the scores of the matches in groups.
-  omnibox::GroupConfigMap suggestion_groups_map;
-  suggestion_groups_map[group_1].set_header_text("1");
-  suggestion_groups_map[group_1].set_section(omnibox::SECTION_REMOTE_ZPS_2);
-  suggestion_groups_map[group_2].set_header_text("2");
-  suggestion_groups_map[group_2].set_section(omnibox::SECTION_REMOTE_ZPS_1);
-
-  {
-    SCOPED_TRACE("Input 'a'");
-    AutocompleteInput typed_input(u"a", metrics::OmniboxEventProto::OTHER,
-                                  TestSchemeClassifier());
-    AutocompleteResult result;
-    result.MergeSuggestionGroupsMap(suggestion_groups_map);
-    result.AppendMatches(matches);
-    result.SortAndCull(typed_input, template_url_service_.get());
-
-    // Retaining suggestion groups has no effect on typed matched.
+    // Showing secondary suggestions has no effect on typed matched.
     ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false));
     const std::array<TestData, 3> expected_data{{
         {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
@@ -2166,19 +2185,38 @@
     result.AppendMatches(matches);
     result.SortAndCull(zero_prefix_input, template_url_service_.get());
 
-    // DynamicMaxAutocomplete has no effect on zero-prefix matched. The number
-    // of results not in groups is determined by the zero-suggest limit.
+    // DynamicMaxAutocomplete has no effect on zero-prefix matched.
+    // Number of primary suggestions is determined by  zero-suggest limit.
+    // Showing secondary suggestions is not supported in NTP.
     ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
-    const std::array<TestData, 6> expected_data{{
-        // Default suggestion comes 1st.
+    const std::array<TestData, 2> expected_data{{
         {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
         {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
-        // omnibox::SECTION_REMOTE_ZPS_1 comes first.
+    }};
+    AssertResultMatches(result, expected_data.begin(), expected_data.size());
+  }
+  {
+    AutocompleteInput zero_prefix_input(
+        u"", metrics::OmniboxEventProto::NTP_REALBOX, TestSchemeClassifier());
+    zero_prefix_input.set_focus_type(
+        metrics::OmniboxFocusType::INTERACTION_FOCUS);
+    AutocompleteResult result;
+
+    result.MergeSuggestionGroupsMap(suggestion_groups_map);
+    result.AppendMatches(matches);
+    result.SortAndCull(zero_prefix_input, template_url_service_.get());
+
+    // DynamicMaxAutocomplete has no effect on zero-prefix matched.
+    // Number of primary suggestions is determined by  zero-suggest limit.
+    // Showing secondary suggestions is supported in NTP_REALBOX.
+    ASSERT_EQ(2U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
+    const std::array<TestData, 5> expected_data{{
+        {1, 1, 1100, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        {2, 1, 1099, true, {}, AutocompleteMatchType::SEARCH_SUGGEST},
+        // secondary suggestions are shown.
         {7, 1, 1094, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
         {8, 1, 1093, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
         {9, 1, 1092, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_2},
-        // omnibox::SECTION_REMOTE_ZPS_2 comes second.
-        {6, 1, 1095, true, {}, AutocompleteMatchType::SEARCH_SUGGEST, group_1},
     }};
     AssertResultMatches(result, expected_data.begin(), expected_data.size());
   }
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 7024339..71459628 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -2150,6 +2150,15 @@
                                         !current_visibility);
       break;
     }
+    case OmniboxPopupSelection::NORMAL:
+      if (match.action && match.action->TakesOverMatch()) {
+        DCHECK(timestamp != base::TimeTicks());
+        ExecuteAction(match, selection.line, timestamp, disposition);
+        return true;
+      } else {
+        return false;
+      }
+
     case OmniboxPopupSelection::FOCUSED_BUTTON_TAB_SWITCH:
       DCHECK(timestamp != base::TimeTicks());
       OpenMatch(match, WindowOpenDisposition::SWITCH_TO_TAB, GURL(),
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 63b1431..c98ef7b 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -195,6 +195,12 @@
              "OmniboxFocusTriggersSRPZeroSuggest",
              enabled_by_default_android_only);
 
+// If enabled, keeps all zero-prefix suggestions in the second column and does
+// not count them toward the overall zero-suggest limit.
+BASE_FEATURE(kKeepSecondaryZeroSuggest,
+             "KeepSecondaryZeroSuggest",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Revamps how local search history is extracted and processed for generating
 // zero-prefix and prefix suggestions.
 BASE_FEATURE(kLocalHistorySuggestRevamp,
@@ -224,6 +230,12 @@
              "OmniboxOnClobberFocusTypeOnContent",
              enabled_by_default_desktop_only);
 
+// If enabled, zero prefix suggestions will be stored using an in-memory caching
+// service, instead of using the existing prefs-based cache.
+BASE_FEATURE(kZeroSuggestInMemoryCaching,
+             "ZeroSuggestInMemoryCaching",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables on-focus zero-prefix suggestions on the NTP for signed-out users.
 BASE_FEATURE(kZeroSuggestOnNTPForSignedOutUsers,
              "OmniboxTrendingZeroPrefixSuggestionsOnNTP",
@@ -245,12 +257,6 @@
              "ZeroSuggestPrefetchingOnWeb",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// If enabled, zero prefix suggestions will be stored using an in-memory caching
-// service, instead of using the existing prefs-based cache.
-BASE_FEATURE(kZeroSuggestInMemoryCaching,
-             "ZeroSuggestInMemoryCaching",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Features to provide head and tail non personalized search suggestion from
 // compact on device models. More specifically, feature name with suffix
 // Incognito / NonIncognito  will only controls behaviors under incognito /
@@ -492,12 +498,6 @@
              "OmniboxReportSearchboxStats",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// If enabled, retains all suggestions with headers to be presented entirely.
-// Disabling the feature trims the suggestions list to the predefined limit.
-BASE_FEATURE(kRetainSuggestionsWithHeaders,
-             "OmniboxRetainSuggestionsWithHeaders",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // If enabled, logs Omnibox URL scoring signals to OmniboxEventProto in UMA.
 BASE_FEATURE(kLogUrlScoringSignals,
              "LogUrlScoringSignals",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 756eb30c..827b901 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -40,7 +40,6 @@
 extern const bool kOmniboxMaxURLMatchesEnabledByDefault;
 BASE_DECLARE_FEATURE(kOmniboxMaxURLMatches);
 BASE_DECLARE_FEATURE(kDynamicMaxAutocomplete);
-BASE_DECLARE_FEATURE(kRetainSuggestionsWithHeaders);
 
 // Entity suggestion disambiguation.
 BASE_DECLARE_FEATURE(kDisambiguateEntitySuggestions);
@@ -52,15 +51,16 @@
 BASE_DECLARE_FEATURE(kClobberTriggersSRPZeroSuggest);
 BASE_DECLARE_FEATURE(kFocusTriggersContextualWebZeroSuggest);
 BASE_DECLARE_FEATURE(kFocusTriggersSRPZeroSuggest);
+BASE_DECLARE_FEATURE(kKeepSecondaryZeroSuggest);
 BASE_DECLARE_FEATURE(kLocalHistorySuggestRevamp);
 BASE_DECLARE_FEATURE(kLocalHistoryZeroSuggestBeyondNTP);
 BASE_DECLARE_FEATURE(kOmniboxLocalZeroSuggestAgeThreshold);
 BASE_DECLARE_FEATURE(kOmniboxOnClobberFocusTypeOnContent);
+BASE_DECLARE_FEATURE(kZeroSuggestInMemoryCaching);
 BASE_DECLARE_FEATURE(kZeroSuggestOnNTPForSignedOutUsers);
 BASE_DECLARE_FEATURE(kZeroSuggestPrefetching);
 BASE_DECLARE_FEATURE(kZeroSuggestPrefetchingOnSRP);
 BASE_DECLARE_FEATURE(kZeroSuggestPrefetchingOnWeb);
-BASE_DECLARE_FEATURE(kZeroSuggestInMemoryCaching);
 // Related, kMaxZeroSuggestMatches.
 
 // On Device Suggest.
diff --git a/components/optimization_guide/core/optimization_guide_enums.h b/components/optimization_guide/core/optimization_guide_enums.h
index 5bbbc39..6cd237b 100644
--- a/components/optimization_guide/core/optimization_guide_enums.h
+++ b/components/optimization_guide/core/optimization_guide_enums.h
@@ -110,9 +110,11 @@
   // The new directory to persist this model version's files could not be
   // created.
   kCouldNotCreateDirectory = 12,
+  // The model info was not saved to model store file.
+  kFailedModelInfoSaving = 13,
 
   // Add new values above this line.
-  kMaxValue = kCouldNotCreateDirectory,
+  kMaxValue = kFailedModelInfoSaving,
 };
 
 // The status for the page content annotations being stored.
diff --git a/components/optimization_guide/core/prediction_manager.cc b/components/optimization_guide/core/prediction_manager.cc
index deefa109..c365071 100644
--- a/components/optimization_guide/core/prediction_manager.cc
+++ b/components/optimization_guide/core/prediction_manager.cc
@@ -39,6 +39,7 @@
 #include "components/optimization_guide/core/prediction_model_download_manager.h"
 #include "components/optimization_guide/core/prediction_model_fetcher_impl.h"
 #include "components/optimization_guide/core/prediction_model_override.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
 #include "components/optimization_guide/core/store_update_data.h"
 #include "components/optimization_guide/optimization_guide_internals/webui/optimization_guide_internals.mojom.h"
 #include "components/optimization_guide/proto/models.pb.h"
@@ -57,6 +58,12 @@
                     features::PredictionModelFetchRandomMaxDelaySecs()));
 }
 
+proto::ModelCacheKey GetModelCacheKey(const std::string& locale) {
+  proto::ModelCacheKey model_cache_key;
+  model_cache_key.set_locale(locale);
+  return model_cache_key;
+}
+
 // Util class for recording the state of a prediction model. The result is
 // recorded when it goes out of scope and its destructor is called.
 class ScopedPredictionManagerModelStatusRecorder {
@@ -180,6 +187,7 @@
 
 PredictionManager::PredictionManager(
     base::WeakPtr<OptimizationGuideStore> model_and_features_store,
+    PredictionModelStore* prediction_model_store,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     PrefService* pref_service,
     bool off_the_record,
@@ -190,6 +198,7 @@
     ComponentUpdatesEnabledProvider component_updates_enabled_provider)
     : prediction_model_download_manager_(nullptr),
       model_and_features_store_(model_and_features_store),
+      prediction_model_store_(prediction_model_store),
       url_loader_factory_(url_loader_factory),
       optimization_guide_logger_(optimization_guide_logger),
       pref_service_(pref_service),
@@ -197,7 +206,11 @@
       clock_(base::DefaultClock::GetInstance()),
       off_the_record_(off_the_record),
       application_locale_(application_locale),
+      model_cache_key_(GetModelCacheKey(application_locale_)),
       models_dir_path_(models_dir_path) {
+  DCHECK(!features::IsInstallWideModelStoreEnabled() ||
+         prediction_model_store_);
+  DCHECK(!model_and_features_store_ || !prediction_model_store_);
   Initialize(std::move(background_download_service_provider));
 }
 
@@ -208,7 +221,13 @@
 
 void PredictionManager::Initialize(
     BackgroundDownloadServiceProvider background_download_service_provider) {
-  if (model_and_features_store_) {
+  if (features::IsInstallWideModelStoreEnabled()) {
+    store_is_ready_ = true;
+    init_time_ = base::TimeTicks::Now();
+    LoadPredictionModels(GetRegisteredOptimizationTargets());
+    LOCAL_HISTOGRAM_BOOLEAN(
+        "OptimizationGuide.PredictionManager.StoreInitialized", true);
+  } else if (model_and_features_store_) {
     model_and_features_store_->Initialize(
         switches::ShouldPurgeModelAndFeaturesStoreOnStartup(),
         base::BindOnce(&PredictionManager::OnStoreInitialized,
@@ -471,7 +490,8 @@
         prediction_models) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!model_and_features_store_)
+  // Check whether the model store from the original profile still exists.
+  if (!features::IsInstallWideModelStoreEnabled() && !model_and_features_store_)
     return;
 
   std::unique_ptr<StoreUpdateData> prediction_model_update_data =
@@ -479,7 +499,11 @@
           clock_->Now() + features::StoredModelsValidDuration());
   bool has_models_to_update = false;
   for (const auto& model : prediction_models) {
-    if (model.has_model() && !model.model().download_url().empty()) {
+    if (!model.has_model()) {
+      // We already have this updated model, so don't update in store.
+      continue;
+    }
+    if (!model.model().download_url().empty()) {
       // We should only be updating the store for on-the-record profiles and
       // after the store has been initialized.
       DCHECK(prediction_model_download_manager_);
@@ -511,10 +535,6 @@
       // once the download has completed successfully.
       continue;
     }
-    if (!model.has_model()) {
-      // We already have this updated model, so don't update in store.
-      continue;
-    }
 
     has_models_to_update = true;
     // Storing the model regardless of whether the model is valid or not. Model
@@ -524,7 +544,12 @@
     OnLoadPredictionModel(model.model_info().optimization_target(),
                           /*record_availability_metrics=*/false,
                           std::make_unique<proto::PredictionModel>(model));
-
+    if (features::IsInstallWideModelStoreEnabled()) {
+      // Update the metadata in the store.
+      prediction_model_store_->UpdateMetadataForExistingModel(
+          model.model_info().optimization_target(), model_cache_key_,
+          model.model_info());
+    }
     if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
       OPTIMIZATION_GUIDE_LOGGER(
           optimization_guide_common::mojom::LogSource::MODEL_MANAGEMENT,
@@ -535,7 +560,7 @@
     }
   }
 
-  if (has_models_to_update) {
+  if (has_models_to_update && model_and_features_store_) {
     model_and_features_store_->UpdatePredictionModels(
         std::move(prediction_model_update_data),
         base::BindOnce(&PredictionManager::OnPredictionModelsStored,
@@ -543,11 +568,12 @@
   }
 }
 
-void PredictionManager::OnModelReady(const proto::PredictionModel& model) {
+void PredictionManager::OnModelReady(const base::FilePath& base_model_dir,
+                                     const proto::PredictionModel& model) {
   if (switches::IsModelOverridePresent())
     return;
 
-  if (!model_and_features_store_)
+  if (!features::IsInstallWideModelStoreEnabled() && !model_and_features_store_)
     return;
 
   DCHECK(model.model_info().has_version() &&
@@ -567,14 +593,22 @@
   }
 
   // Store the received model in the store.
-  std::unique_ptr<StoreUpdateData> prediction_model_update_data =
-      StoreUpdateData::CreatePredictionModelStoreUpdateData(
-          clock_->Now() + features::StoredModelsValidDuration());
-  prediction_model_update_data->CopyPredictionModelIntoUpdateData(model);
-  model_and_features_store_->UpdatePredictionModels(
-      std::move(prediction_model_update_data),
-      base::BindOnce(&PredictionManager::OnPredictionModelsStored,
-                     ui_weak_ptr_factory_.GetWeakPtr()));
+  if (features::IsInstallWideModelStoreEnabled()) {
+    prediction_model_store_->UpdateModel(
+        model.model_info().optimization_target(), model_cache_key_,
+        model.model_info(), base_model_dir,
+        base::BindOnce(&PredictionManager::OnPredictionModelsStored,
+                       ui_weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    std::unique_ptr<StoreUpdateData> prediction_model_update_data =
+        StoreUpdateData::CreatePredictionModelStoreUpdateData(
+            clock_->Now() + features::StoredModelsValidDuration());
+    prediction_model_update_data->CopyPredictionModelIntoUpdateData(model);
+    model_and_features_store_->UpdatePredictionModels(
+        std::move(prediction_model_update_data),
+        base::BindOnce(&PredictionManager::OnPredictionModelsStored,
+                       ui_weak_ptr_factory_.GetWeakPtr()));
+  }
 
   if (registered_optimization_targets_and_metadata_.contains(
           model.model_info().optimization_target())) {
@@ -643,31 +677,45 @@
       "OptimizationGuide.PredictionManager.PredictionModelsStored", true);
 }
 
-void PredictionManager::OnStoreInitialized(
-    BackgroundDownloadServiceProvider background_download_service_provider) {
+void PredictionManager::MaybeInitializeModelDownloads(
+    download::BackgroundDownloadService* background_download_service) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  store_is_ready_ = true;
-  init_time_ = base::TimeTicks::Now();
-  LOCAL_HISTOGRAM_BOOLEAN(
-      "OptimizationGuide.PredictionManager.StoreInitialized", true);
 
   // Create the download manager here if we are allowed to.
   if (features::IsModelDownloadingEnabled() && !off_the_record_ &&
       !prediction_model_download_manager_) {
     prediction_model_download_manager_ =
         std::make_unique<PredictionModelDownloadManager>(
-            background_download_service_provider
-                ? std::move(background_download_service_provider).Run()
-                : nullptr,
-            models_dir_path_,
+            background_download_service, models_dir_path_,
+            prediction_model_store_, model_cache_key_,
             base::ThreadPool::CreateSequencedTaskRunner(
                 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
                  base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
     prediction_model_download_manager_->AddObserver(this);
   }
 
+  // Only load models if there are optimization targets registered.
+  if (!registered_optimization_targets_and_metadata_.empty())
+    MaybeScheduleFirstModelFetch();
+}
+
+void PredictionManager::OnStoreInitialized(
+    BackgroundDownloadServiceProvider background_download_service_provider) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  store_is_ready_ = true;
+  init_time_ = base::TimeTicks::Now();
+  LOCAL_HISTOGRAM_BOOLEAN(
+      "OptimizationGuide.PredictionManager.StoreInitialized", true);
+
   // Purge any inactive models from the store.
-  model_and_features_store_->PurgeInactiveModels();
+  if (model_and_features_store_)
+    model_and_features_store_->PurgeInactiveModels();
+
+  MaybeInitializeModelDownloads(
+      background_download_service_provider && !off_the_record_
+          ? std::move(background_download_service_provider).Run()
+          : nullptr);
 
   // Only load models if there are optimization targets registered.
   if (registered_optimization_targets_and_metadata_.empty())
@@ -676,8 +724,6 @@
   // The store is ready so start loading models for the registered optimization
   // targets.
   LoadPredictionModels(GetRegisteredOptimizationTargets());
-
-  MaybeScheduleFirstModelFetch();
 }
 
 void PredictionManager::OnPredictionModelOverrideLoaded(
@@ -705,6 +751,23 @@
     return;
   }
 
+  if (features::IsInstallWideModelStoreEnabled()) {
+    for (const auto optimization_target : optimization_targets) {
+      if (!prediction_model_store_->HasModel(optimization_target,
+                                             model_cache_key_)) {
+        RecordModelAvailableAtRegistration(optimization_target, false);
+        continue;
+      }
+      prediction_model_store_->LoadModel(
+          optimization_target, model_cache_key_,
+          base::BindOnce(&PredictionManager::OnLoadPredictionModel,
+                         ui_weak_ptr_factory_.GetWeakPtr(), optimization_target,
+                         /*record_availability_metrics=*/true));
+    }
+    return;
+  }
+
+  DCHECK(!features::IsInstallWideModelStoreEnabled());
   if (!model_and_features_store_)
     return;
 
diff --git a/components/optimization_guide/core/prediction_manager.h b/components/optimization_guide/core/prediction_manager.h
index 0858c2a..1a61645 100644
--- a/components/optimization_guide/core/prediction_manager.h
+++ b/components/optimization_guide/core/prediction_manager.h
@@ -45,6 +45,7 @@
 class OptimizationTargetModelObserver;
 class PredictionModelDownloadManager;
 class PredictionModelFetcher;
+class PredictionModelStore;
 class ModelInfo;
 
 // A PredictionManager supported by the optimization guide that makes an
@@ -63,6 +64,7 @@
 
   PredictionManager(
       base::WeakPtr<OptimizationGuideStore> model_and_features_store,
+      PredictionModelStore* prediction_model_store,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       PrefService* pref_service,
       bool off_the_record,
@@ -131,7 +133,8 @@
       std::unique_ptr<ModelInfo> model_info);
 
   // PredictionModelDownloadObserver:
-  void OnModelReady(const proto::PredictionModel& model) override;
+  void OnModelReady(const base::FilePath& base_model_dir,
+                    const proto::PredictionModel& model) override;
   void OnModelDownloadStarted(
       proto::OptimizationTarget optimization_target) override;
   void OnModelDownloadFailed(
@@ -140,6 +143,10 @@
   std::vector<optimization_guide_internals::mojom::DownloadedModelInfoPtr>
   GetDownloadedModelsInfoForWebUI() const;
 
+  // Initialize the model metadata fetching and downloads.
+  void MaybeInitializeModelDownloads(
+      download::BackgroundDownloadService* background_download_service);
+
  protected:
   // Process |prediction_models| to be stored in the in memory optimization
   // target prediction model map for immediate use and asynchronously write the
@@ -150,6 +157,7 @@
 
  private:
   friend class PredictionManagerTestBase;
+  friend class PredictionModelStoreBrowserTest;
 
   // Called on construction to initialize the prediction model.
   // |background_dowload_service_provider| can provide the
@@ -248,6 +256,10 @@
   void NotifyObserversOfNewModel(proto::OptimizationTarget optimization_target,
                                  const ModelInfo& model_info);
 
+  void SetModelCacheKeyForTesting(const proto::ModelCacheKey& model_cache_key) {
+    model_cache_key_ = model_cache_key;
+  }
+
   // A map of optimization target to the model file containing the model for the
   // target.
   base::flat_map<proto::OptimizationTarget, std::unique_ptr<ModelInfo>>
@@ -273,12 +285,16 @@
   std::unique_ptr<PredictionModelDownloadManager>
       prediction_model_download_manager_;
 
-  // TODO(crbug/1183507): Remove host model features store and all relevant
-  // code, and deprecate the proto field too.
+  // TODO(crbug/1358568): Remove this old model store once the new model store
+  // is launched.
   // The optimization guide store that contains prediction models and host
   // model features from the remote Optimization Guide Service.
   base::WeakPtr<OptimizationGuideStore> model_and_features_store_;
 
+  // The new optimization guide model store. Will be null when the feature is
+  // not enabled. Not owned and outlives |this| since its an install-wide store.
+  raw_ptr<PredictionModelStore> prediction_model_store_;
+
   // A stored response from a model and host model features fetch used to hold
   // models to be stored once host model features are processed and stored.
   std::unique_ptr<proto::GetModelsResponse> get_models_response_data_to_store_;
@@ -300,6 +316,8 @@
   ComponentUpdatesEnabledProvider component_updates_enabled_provider_;
 
   // Time the prediction manager got initialized.
+  // TODO(crbug/1358568): Remove this old model store once the new model store
+  // is launched.
   base::TimeTicks init_time_;
 
   // The timer used to schedule fetching prediction models and host model
@@ -311,18 +329,19 @@
   raw_ptr<const base::Clock> clock_;
 
   // Whether the |model_and_features_store_| is initialized and ready for use.
+  // TODO(crbug/1358568): Remove this old model store once the new model store
+  // is launched.
   bool store_is_ready_ = false;
 
-  // Whether host model features have been loaded from the store and are ready
-  // for use.
-  bool host_model_features_loaded_ = false;
-
   // Whether the profile for this PredictionManager is off the record.
   bool off_the_record_ = false;
 
   // The locale of the application.
   std::string application_locale_;
 
+  // Model cache key for the profile.
+  proto::ModelCacheKey model_cache_key_;
+
   // The path to the directory containing the models.
   base::FilePath models_dir_path_;
 
diff --git a/components/optimization_guide/core/prediction_manager_unittest.cc b/components/optimization_guide/core/prediction_manager_unittest.cc
index 7a11b37..a3a503c 100644
--- a/components/optimization_guide/core/prediction_manager_unittest.cc
+++ b/components/optimization_guide/core/prediction_manager_unittest.cc
@@ -11,6 +11,7 @@
 
 #include "base/base64.h"
 #include "base/command_line.h"
+#include "base/files/file_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/gtest_util.h"
@@ -33,6 +34,7 @@
 #include "components/optimization_guide/core/prediction_model_download_manager.h"
 #include "components/optimization_guide/core/prediction_model_fetcher.h"
 #include "components/optimization_guide/core/prediction_model_fetcher_impl.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
 #include "components/optimization_guide/core/proto_database_provider_test_base.h"
 #include "components/optimization_guide/proto/hint_cache.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
@@ -48,12 +50,15 @@
 using leveldb_proto::test::FakeDB;
 
 namespace {
+
 // Retry delay is 2 minutes to allow for fetch retry delay + some random delay
 // to pass.
 constexpr int kTestFetchRetryDelaySecs = 60 * 2 + 62;
 // 24 hours + random fetch delay.
 constexpr int kUpdateFetchModelAndFeaturesTimeSecs = 24 * 60 * 60 + 62;
 
+constexpr char kTestLocale[] = "en-US";
+
 }  // namespace
 
 namespace optimization_guide {
@@ -83,6 +88,12 @@
   return get_models_response;
 }
 
+proto::ModelCacheKey GetTestModelCacheKey() {
+  proto::ModelCacheKey model_cache_key;
+  model_cache_key.set_locale(kTestLocale);
+  return model_cache_key;
+}
+
 class FakeOptimizationTargetModelObserver
     : public OptimizationTargetModelObserver {
  public:
@@ -114,6 +125,8 @@
       scoped_refptr<base::SequencedTaskRunner> task_runner)
       : PredictionModelDownloadManager(/*download_service=*/nullptr,
                                        models_dir_path,
+                                       /*prediction_model_store=*/nullptr,
+                                       proto::ModelCacheKey(),
                                        task_runner) {}
   ~FakePredictionModelDownloadManager() override = default;
 
@@ -297,7 +310,8 @@
                        bool have_models_in_store = true) {
     load_models_ = load_models;
     have_models_in_store_ = have_models_in_store;
-    std::move(init_callback_).Run();
+    if (init_callback_)
+      std::move(init_callback_).Run();
   }
 
   void LoadPredictionModel(const EntryKey& prediction_model_entry_key,
@@ -346,6 +360,7 @@
  public:
   TestPredictionManager(
       base::WeakPtr<OptimizationGuideStore> model_and_features_store,
+      PredictionModelStore* prediction_model_store,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       PrefService* pref_service,
       ComponentUpdatesEnabledProvider component_updates_enabled_provider,
@@ -354,6 +369,7 @@
       const base::FilePath& models_dir_path)
       : PredictionManager(
             model_and_features_store,
+            prediction_model_store,
             url_loader_factory,
             pref_service,
             off_the_record,
@@ -403,12 +419,14 @@
 
     model_and_features_store_ = CreateModelAndHostModelFeaturesStore();
     prediction_manager_ = std::make_unique<TestPredictionManager>(
-        model_and_features_store_->AsWeakPtr(), url_loader_factory_,
-        pref_service_.get(),
+        !features::IsInstallWideModelStoreEnabled()
+            ? model_and_features_store_->AsWeakPtr()
+            : nullptr,
+        prediction_model_store_.get(), url_loader_factory_, pref_service_.get(),
         base::BindRepeating(
             &PredictionManagerTestBase::AreComponentUpdatesEnabled,
             base::Unretained(this)),
-        false, "en-US", temp_dir());
+        false, kTestLocale, temp_dir());
     prediction_manager_->SetClockForTesting(task_environment_.GetMockClock());
   }
 
@@ -437,8 +455,13 @@
 
   void SetStoreInitialized(bool load_models = true,
                            bool have_models_in_store = true) {
-    models_and_features_store()->RunInitCallback(load_models,
-                                                 have_models_in_store);
+    if (features::IsInstallWideModelStoreEnabled()) {
+      prediction_manager_->MaybeInitializeModelDownloads(
+          /*background_download_service=*/nullptr);
+    } else {
+      models_and_features_store()->RunInitCallback(load_models,
+                                                   have_models_in_store);
+    }
     RunUntilIdle();
     // Move clock forward for any short delays added for the fetcher, until the
     // startup fetch could start.
@@ -464,7 +487,7 @@
   TestOptimizationGuideStore* models_and_features_store() const {
     base::WeakPtr<OptimizationGuideStore> store =
         prediction_manager()->model_and_features_store();
-    DCHECK(store);
+    DCHECK(features::IsInstallWideModelStoreEnabled() || store);
     return static_cast<TestOptimizationGuideStore*>(store.get());
   }
 
@@ -490,6 +513,7 @@
   // tsan flakes caused by other tasks running while |feature_list_| is
   // destroyed.
   base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<PredictionModelStore> prediction_model_store_;
 
  private:
   base::test::TaskEnvironment task_environment_{
@@ -558,24 +582,63 @@
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
 }
 
-class PredictionManagerTest : public PredictionManagerTestBase {
+class PredictionManagerTest : public testing::WithParamInterface<bool>,
+                              public PredictionManagerTestBase {
  public:
   PredictionManagerTest() {
     // This needs to be done before any tasks are run that might check if a
     // feature is enabled, to avoid tsan errors.
 
-    feature_list_.InitWithFeatures(
-        {features::kRemoteOptimizationGuideFetching,
-         features::kOptimizationGuideModelDownloading},
-        {});
+    std::vector<base::test::FeatureRef> enabled_features = {
+        features::kRemoteOptimizationGuideFetching,
+        features::kOptimizationGuideModelDownloading,
+    };
+    if (ShouldEnableInstallWideModelStore()) {
+      local_state_prefs_ = std::make_unique<TestingPrefServiceSimple>();
+      prefs::RegisterLocalStatePrefs(local_state_prefs_->registry());
+      enabled_features.emplace_back(
+          features::kOptimizationGuideInstallWideModelStore);
+    }
+    feature_list_.InitWithFeatures(enabled_features, {});
   }
 
+  void SetUp() override {
+    PredictionManagerTestBase::SetUp();
+    if (ShouldEnableInstallWideModelStore()) {
+      prediction_model_store_ =
+          PredictionModelStore::CreatePredictionModelStoreForTesting(
+              local_state_prefs_.get(), temp_dir());
+    }
+  }
+
+  void CreateTestModelFiles(const proto::ModelInfo model_info,
+                            const base::FilePath& base_model_dir) {
+    CreateDirectory(base_model_dir);
+    WriteFile(base_model_dir.Append(GetBaseFileNameForModels()), "");
+    std::string model_info_str;
+    ASSERT_TRUE(model_info.SerializeToString(&model_info_str));
+    WriteFile(base_model_dir.Append(GetBaseFileNameForModelInfo()),
+              model_info_str);
+    RunUntilIdle();
+  }
+
+  PredictionModelStore* prediction_model_store() {
+    return prediction_model_store_.get();
+  }
+
+  bool ShouldEnableInstallWideModelStore() const { return GetParam(); }
+
  private:
   variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
       variations::VariationsIdsProvider::Mode::kUseSignedInState};
+  std::unique_ptr<TestingPrefServiceSimple> local_state_prefs_;
 };
 
-TEST_F(PredictionManagerTest, RemoteFetchingPrefDisabled) {
+INSTANTIATE_TEST_SUITE_P(All,
+                         PredictionManagerTest,
+                         /*ShouldEnableInstallWideModelStore=*/testing::Bool());
+
+TEST_P(PredictionManagerTest, RemoteFetchingPrefDisabled) {
   SetComponentUpdatesPrefEnabled(false);
   CreatePredictionManager();
 
@@ -591,7 +654,7 @@
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
 }
 
-TEST_F(PredictionManagerTest, AddObserverForOptimizationTargetModel) {
+TEST_P(PredictionManagerTest, AddObserverForOptimizationTargetModel) {
   base::HistogramTester histogram_tester;
 
   CreatePredictionManager();
@@ -643,7 +706,7 @@
                    .has_value());
 
   base::FilePath additional_file_path =
-      temp_dir().AppendASCII("whatever").AppendASCII("additional_file.txt");
+      temp_dir().AppendASCII("foo").AppendASCII("additional_file.txt");
   proto::ModelInfo model_info;
   model_info.set_optimization_target(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
@@ -660,17 +723,17 @@
 
     proto::PredictionModel model1;
     *model1.mutable_model_info() = model_info;
-    model1.mutable_model()->set_download_url(
-        FilePathToString(temp_dir().AppendASCII("whatever")));
-    prediction_manager()->OnModelReady(model1);
+    model1.mutable_model()->set_download_url(FilePathToString(
+        temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels())));
+    prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model1);
     RunUntilIdle();
 
     absl::optional<ModelInfo> received_model =
         observer.last_received_model_for_target(
             proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
     EXPECT_EQ(received_model->GetModelMetadata()->type_url(), "sometypeurl");
-    EXPECT_EQ(received_model->GetModelFilePath().BaseName().value(),
-              FILE_PATH_LITERAL("whatever"));
+    EXPECT_EQ(temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels()),
+              received_model->GetModelFilePath());
     EXPECT_EQ(received_model->GetAdditionalFiles(),
               base::flat_set<base::FilePath>{additional_file_path});
 
@@ -705,11 +768,12 @@
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &observer);
   observer.Reset();
   proto::PredictionModel model2;
+  model_info.clear_additional_files();
   *model2.mutable_model_info() = model_info;
   model2.mutable_model_info()->set_version(2);
-  model2.mutable_model()->set_download_url(
-      FilePathToString(temp_dir().AppendASCII("whatever2")));
-  prediction_manager()->OnModelReady(model2);
+  model2.mutable_model()->set_download_url(FilePathToString(
+      temp_dir().AppendASCII("bar").AppendASCII("bar_model.tflite")));
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("bar"), model2);
   RunUntilIdle();
 
   // Last received path should not have been updated since the observer was
@@ -720,7 +784,7 @@
                    .has_value());
 }
 
-TEST_F(PredictionManagerTest,
+TEST_P(PredictionManagerTest,
        AddObserverForOptimizationTargetModelAddAnotherObserverForSameTarget) {
   // Fails under "threadsafe" mode.
   testing::GTEST_FLAG(death_test_style) = "fast";
@@ -742,18 +806,16 @@
   // Ensure observer is hooked up.
   proto::PredictionModel model1;
   *model1.mutable_model_info() = model_info;
-  model1.mutable_model()->set_download_url(
-      FilePathToString(temp_dir().AppendASCII("whatever")));
-  prediction_manager()->OnModelReady(model1);
+  model1.mutable_model()->set_download_url(FilePathToString(
+      temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels())));
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model1);
   RunUntilIdle();
 
-  EXPECT_EQ(observer1
+  EXPECT_EQ(temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels()),
+            observer1
                 .last_received_model_for_target(
                     proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD)
-                ->GetModelFilePath()
-                .BaseName()
-                .value(),
-            FILE_PATH_LITERAL("whatever"));
+                ->GetModelFilePath());
 
 #if !BUILDFLAG(IS_WIN)
   // Do not run the DCHECK death test on Windows since there's some weird
@@ -771,7 +833,7 @@
 
 // See crbug/1227996.
 #if !BUILDFLAG(IS_WIN)
-TEST_F(PredictionManagerTest,
+TEST_P(PredictionManagerTest,
        AddObserverForOptimizationTargetModelCommandLineOverride) {
   base::HistogramTester histogram_tester;
 
@@ -836,9 +898,9 @@
   model.mutable_model_info()->set_optimization_target(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
   model.mutable_model_info()->set_version(1);
-  model.mutable_model()->set_download_url(
-      FilePathToString(temp_dir().AppendASCII("whatever2")));
-  prediction_manager()->OnModelReady(model);
+  model.mutable_model()->set_download_url(FilePathToString(
+      temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels())));
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model);
   RunUntilIdle();
 
   // Last received path should not have been updated since the observer was
@@ -850,7 +912,7 @@
 }
 #endif
 
-TEST_F(PredictionManagerTest,
+TEST_P(PredictionManagerTest,
        NoPredictionModelForRegisteredOptimizationTarget) {
   base::HistogramTester histogram_tester;
 
@@ -866,7 +928,7 @@
       false, 1);
 }
 
-TEST_F(PredictionManagerTest, UpdatePredictionModelsWithInvalidModel) {
+TEST_P(PredictionManagerTest, UpdatePredictionModelsWithInvalidModel) {
   base::HistogramTester histogram_tester;
 
   CreatePredictionManager();
@@ -882,7 +944,7 @@
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
   model.mutable_model_info()->set_version(3);
   model.mutable_model();
-  prediction_manager()->OnModelReady(model);
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model);
   RunUntilIdle();
 
   histogram_tester.ExpectBucketCount("OptimizationGuide.IsPredictionModelValid",
@@ -894,11 +956,13 @@
       "OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 1);
   histogram_tester.ExpectTotalCount(
       "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
-  histogram_tester.ExpectUniqueSample(
-      "OptimizationGuide.PredictionModelRemoved.PainfulPageLoad", true, 1);
+  if (!ShouldEnableInstallWideModelStore()) {
+    histogram_tester.ExpectUniqueSample(
+        "OptimizationGuide.PredictionModelRemoved.PainfulPageLoad", true, 1);
+  }
 }
 
-TEST_F(PredictionManagerTest, UpdateModelFileWithSameVersion) {
+TEST_P(PredictionManagerTest, UpdateModelFileWithSameVersion) {
   base::HistogramTester histogram_tester;
 
   CreatePredictionManager();
@@ -912,9 +976,9 @@
   model.mutable_model_info()->set_optimization_target(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
   model.mutable_model_info()->set_version(3);
-  model.mutable_model()->set_download_url(
-      FilePathToString(temp_dir().AppendASCII("whatever2")));
-  prediction_manager()->OnModelReady(model);
+  model.mutable_model()->set_download_url(FilePathToString(
+      temp_dir().AppendASCII("foo").Append(GetBaseFileNameForModels())));
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model);
   RunUntilIdle();
 
   EXPECT_TRUE(observer
@@ -926,7 +990,7 @@
   observer.Reset();
 
   // Send the same model again.
-  prediction_manager()->OnModelReady(model);
+  prediction_manager()->OnModelReady(temp_dir().AppendASCII("foo"), model);
 
   // The observer should not have received an update.
   EXPECT_FALSE(observer
@@ -935,7 +999,7 @@
                    .has_value());
 }
 
-TEST_F(PredictionManagerTest, DownloadManagerUnavailableShouldNotFetch) {
+TEST_P(PredictionManagerTest, DownloadManagerUnavailableShouldNotFetch) {
   base::HistogramTester histogram_tester;
 
   CreatePredictionManager();
@@ -963,7 +1027,7 @@
       ModelDeliveryEvent::kDownloadServiceUnavailable, 1);
 }
 
-TEST_F(PredictionManagerTest, UpdateModelWithDownloadUrl) {
+TEST_P(PredictionManagerTest, UpdateModelWithDownloadUrl) {
   base::HistogramTester histogram_tester;
 
   CreatePredictionManager();
@@ -999,19 +1063,24 @@
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 }
 
-TEST_F(PredictionManagerTest, UpdateModelForUnregisteredTargetOnModelReady) {
+TEST_P(PredictionManagerTest, UpdateModelForUnregisteredTargetOnModelReady) {
   base::HistogramTester histogram_tester;
   CreatePredictionManager();
 
   SetStoreInitialized();
 
+  auto base_model_dir = temp_dir().AppendASCII("foo");
   proto::PredictionModel model;
   model.mutable_model_info()->set_optimization_target(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
   model.mutable_model_info()->set_version(3);
   model.mutable_model()->set_download_url(
-      FilePathToString(temp_dir().AppendASCII("whatever")));
-  prediction_manager()->OnModelReady(model);
+      FilePathToString(base_model_dir.Append(GetBaseFileNameForModels())));
+  if (ShouldEnableInstallWideModelStore()) {
+    CreateTestModelFiles(model.model_info(), base_model_dir);
+  }
+  prediction_manager()->OnModelReady(base_model_dir, model);
+  RunUntilIdle();
 
   histogram_tester.ExpectTotalCount(
       "OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
@@ -1041,10 +1110,23 @@
       ModelDeliveryEvent::kModelDelivered, 1);
 }
 
-TEST_F(PredictionManagerTest,
+TEST_P(PredictionManagerTest,
        StoreInitializedAfterOptimizationTargetRegistered) {
   base::HistogramTester histogram_tester;
   CreatePredictionManager();
+  if (ShouldEnableInstallWideModelStore()) {
+    proto::ModelInfo model_info;
+    model_info.set_optimization_target(
+        proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+    model_info.set_version(1);
+    auto base_model_dir = temp_dir().AppendASCII("foo");
+    CreateTestModelFiles(model_info, base_model_dir);
+    prediction_model_store()->UpdateModel(
+        proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, GetTestModelCacheKey(),
+        model_info, base_model_dir, base::DoNothing());
+    RunUntilIdle();
+  }
+
   // Ensure that the fetch does not cause any models or features to load.
   prediction_manager()->SetPredictionModelFetcherForTesting(
       BuildTestPredictionModelFetcher(
@@ -1052,10 +1134,12 @@
   FakeOptimizationTargetModelObserver observer;
   prediction_manager()->AddObserverForOptimizationTargetModel(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, absl::nullopt, &observer);
-  EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
+  if (!ShouldEnableInstallWideModelStore())
+    EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
 
   SetStoreInitialized();
-  EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
+  if (!ShouldEnableInstallWideModelStore())
+    EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
 
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
   histogram_tester.ExpectUniqueSample(
@@ -1066,23 +1150,38 @@
       true, 1);
 }
 
-TEST_F(PredictionManagerTest,
+TEST_P(PredictionManagerTest,
        StoreInitializedBeforeOptimizationTargetRegistered) {
   base::HistogramTester histogram_tester;
   CreatePredictionManager();
+  if (ShouldEnableInstallWideModelStore()) {
+    proto::ModelInfo model_info;
+    model_info.set_optimization_target(
+        proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+    model_info.set_version(1);
+    auto base_model_dir = temp_dir().AppendASCII("foo");
+    CreateTestModelFiles(model_info, base_model_dir);
+    prediction_model_store()->UpdateModel(
+        proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, GetTestModelCacheKey(),
+        model_info, base_model_dir, base::DoNothing());
+    RunUntilIdle();
+  }
+
   // Ensure that the fetch does not cause any models or features to load.
   prediction_manager()->SetPredictionModelFetcherForTesting(
       BuildTestPredictionModelFetcher(
           PredictionModelFetcherEndState::kFetchFailed));
   SetStoreInitialized();
 
-  EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
+  if (!ShouldEnableInstallWideModelStore())
+    EXPECT_FALSE(models_and_features_store()->WasModelLoaded());
   FakeOptimizationTargetModelObserver observer;
   prediction_manager()->AddObserverForOptimizationTargetModel(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, absl::nullopt, &observer);
   RunUntilIdle();
 
-  EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
+  if (!ShouldEnableInstallWideModelStore())
+    EXPECT_TRUE(models_and_features_store()->WasModelLoaded());
 
   EXPECT_FALSE(prediction_model_fetcher()->models_fetched());
   histogram_tester.ExpectUniqueSample(
@@ -1093,7 +1192,7 @@
       true, 1);
 }
 
-TEST_F(PredictionManagerTest, ModelFetcherTimerRetryDelay) {
+TEST_P(PredictionManagerTest, ModelFetcherTimerRetryDelay) {
   CreatePredictionManager();
   prediction_manager()->SetPredictionModelFetcherForTesting(
       BuildTestPredictionModelFetcher(
@@ -1120,7 +1219,7 @@
   EXPECT_TRUE(prediction_model_fetcher()->models_fetched());
 }
 
-TEST_F(PredictionManagerTest, ModelFetcherTimerFetchSucceeds) {
+TEST_P(PredictionManagerTest, ModelFetcherTimerFetchSucceeds) {
   CreatePredictionManager();
   prediction_manager()->SetPredictionModelFetcherForTesting(
       BuildTestPredictionModelFetcher(
diff --git a/components/optimization_guide/core/prediction_model_download_manager.cc b/components/optimization_guide/core/prediction_model_download_manager.cc
index 858188ac..201321d 100644
--- a/components/optimization_guide/core/prediction_model_download_manager.cc
+++ b/components/optimization_guide/core/prediction_model_download_manager.cc
@@ -25,6 +25,7 @@
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/prediction_model_download_observer.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
 #include "components/services/unzip/public/cpp/unzip.h"
 #include "crypto/sha2.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -84,6 +85,15 @@
       status);
 }
 
+// Writes the |model_info| to |file_path|.
+bool WriteModelInfoProtoToFile(const proto::ModelInfo& model_info,
+                               const base::FilePath& file_path) {
+  std::string model_info_str;
+  if (!model_info.SerializeToString(&model_info_str))
+    return false;
+  return base::WriteFile(file_path, model_info_str);
+}
+
 }  // namespace
 
 const char kPredictionModelOptimizationTargetCustomDataKey[] =
@@ -92,11 +102,15 @@
 PredictionModelDownloadManager::PredictionModelDownloadManager(
     download::BackgroundDownloadService* download_service,
     const base::FilePath& models_dir_path,
+    PredictionModelStore* prediction_model_store,
+    const proto::ModelCacheKey& model_cache_key,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
     : download_service_(download_service),
       is_available_for_downloads_(true),
       api_key_(features::GetOptimizationGuideServiceAPIKey()),
       models_dir_path_(models_dir_path),
+      prediction_model_store_(prediction_model_store),
+      model_cache_key_(model_cache_key),
       background_task_runner_(background_task_runner) {}
 
 PredictionModelDownloadManager::~PredictionModelDownloadManager() = default;
@@ -340,19 +354,26 @@
         PredictionModelDownloadStatus::kFailedCrxUnzip);
     return;
   }
+  base::FilePath base_model_dir =
+      features::IsInstallWideModelStoreEnabled()
+          ? prediction_model_store_->GetBaseModelDirForModelCacheKey(
+                *optimization_target, model_cache_key_)
+          : models_dir_path_.AppendASCII(
+                base::GUID::GenerateRandomV4().AsLowercaseString());
 
   background_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&PredictionModelDownloadManager::ProcessUnzippedContents,
-                     models_dir_path_, unzipped_dir_path),
+                     base_model_dir, unzipped_dir_path),
       base::BindOnce(&PredictionModelDownloadManager::NotifyModelReady,
-                     ui_weak_ptr_factory_.GetWeakPtr(), optimization_target));
+                     ui_weak_ptr_factory_.GetWeakPtr(), optimization_target,
+                     base_model_dir));
 }
 
 // static
 absl::optional<proto::PredictionModel>
 PredictionModelDownloadManager::ProcessUnzippedContents(
-    const base::FilePath& model_dir_path,
+    const base::FilePath& base_model_dir,
     const base::FilePath& unzipped_dir_path) {
   // Clean up temp dir when this function finishes.
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
@@ -379,17 +400,15 @@
     return absl::nullopt;
   }
 
-  if (model_dir_path.empty()) {
+  if (base_model_dir.empty()) {
     RecordPredictionModelDownloadStatus(
         PredictionModelDownloadStatus::kOptGuideDirectoryDoesNotExist);
     return absl::nullopt;
   }
 
-  // Move each packaged file away from temp directory into a new directory.
-
-  base::FilePath store_dir = model_dir_path.AppendASCII(
-      base::GUID::GenerateRandomV4().AsLowercaseString());
-  if (!base::CreateDirectory(store_dir)) {
+  // Move each packaged file away from temp directory into the model
+  // base directory.
+  if (!base::CreateDirectory(base_model_dir)) {
     RecordPredictionModelDownloadStatus(
         PredictionModelDownloadStatus::kCouldNotCreateDirectory);
     return absl::nullopt;
@@ -400,7 +419,7 @@
   // Note that the base file name is used for backwards compatibility checking
   // in |OptimizationGuideStore::OnLoadModelsToBeUpdated|.
   base::FilePath store_model_path =
-      store_dir.Append(GetBaseFileNameForModels());
+      base_model_dir.Append(GetBaseFileNameForModels());
 
   proto::PredictionModel model;
   *model.mutable_model_info() = model_info;
@@ -428,7 +447,7 @@
     base::FilePath temp_add_file_path =
         unzipped_dir_path.AppendASCII(additional_file_path);
     base::FilePath store_add_file_path =
-        store_dir.AppendASCII(additional_file_path);
+        base_model_dir.AppendASCII(additional_file_path);
 
     // Make sure the additional file gets moved.
     files_to_move.emplace_back(
@@ -439,6 +458,15 @@
         FilePathToString(store_add_file_path));
   }
 
+  if (features::IsInstallWideModelStoreEnabled() &&
+      !WriteModelInfoProtoToFile(
+          model.model_info(),
+          base_model_dir.Append(GetBaseFileNameForModelInfo()))) {
+    RecordPredictionModelDownloadStatus(
+        PredictionModelDownloadStatus::kFailedModelInfoSaving);
+    return absl::nullopt;
+  }
+
   PredictionModelDownloadStatus status =
       PredictionModelDownloadStatus::kSuccess;
   for (const auto& move_file : files_to_move) {
@@ -470,6 +498,7 @@
 
 void PredictionModelDownloadManager::NotifyModelReady(
     absl::optional<proto::OptimizationTarget> optimization_target,
+    const base::FilePath& base_model_dir,
     const absl::optional<proto::PredictionModel>& model) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -481,7 +510,7 @@
   }
 
   for (PredictionModelDownloadObserver& observer : observers_)
-    observer.OnModelReady(*model);
+    observer.OnModelReady(base_model_dir, *model);
 }
 
 void PredictionModelDownloadManager::NotifyModelDownloadFailed(
diff --git a/components/optimization_guide/core/prediction_model_download_manager.h b/components/optimization_guide/core/prediction_model_download_manager.h
index 1247cc9..db39a511 100644
--- a/components/optimization_guide/core/prediction_model_download_manager.h
+++ b/components/optimization_guide/core/prediction_model_download_manager.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "components/download/public/background_service/download_params.h"
+#include "components/optimization_guide/core/prediction_model_store.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -25,6 +26,7 @@
 
 class PredictionModelDownloadClient;
 class PredictionModelDownloadObserver;
+class PredictionModelStore;
 
 namespace proto {
 class PredictionModel;
@@ -51,6 +53,8 @@
   PredictionModelDownloadManager(
       download::BackgroundDownloadService* download_service,
       const base::FilePath& models_dir_path,
+      PredictionModelStore* prediction_model_store,
+      const proto::ModelCacheKey& model_cache_key,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
   virtual ~PredictionModelDownloadManager();
   PredictionModelDownloadManager(const PredictionModelDownloadManager&) =
@@ -149,6 +153,7 @@
   // Must be invoked on the UI thread.
   void NotifyModelReady(
       absl::optional<proto::OptimizationTarget> optimization_target,
+      const base::FilePath& base_model_dir,
       const absl::optional<proto::PredictionModel>& model);
 
   // Notifies |observers_| that a model download failed for
@@ -178,6 +183,12 @@
   // The path to the dir containing models.
   base::FilePath models_dir_path_;
 
+  // The optimization guide model store. Not owned. Should outlive |this|.
+  raw_ptr<PredictionModelStore> prediction_model_store_;
+
+  // The ModelCacheKey that the user profile for |this| is associated with.
+  const proto::ModelCacheKey model_cache_key_;
+
   // Background thread where download file processing should be performed.
   scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
 
diff --git a/components/optimization_guide/core/prediction_model_download_manager_unittest.cc b/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
index 1eb39fa3..c60b2a3e 100644
--- a/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
@@ -42,7 +42,8 @@
   TestPredictionModelDownloadObserver() = default;
   ~TestPredictionModelDownloadObserver() override = default;
 
-  void OnModelReady(const proto::PredictionModel& model) override {
+  void OnModelReady(const base::FilePath& base_model_dir,
+                    const proto::PredictionModel& model) override {
     last_ready_model_ = model;
   }
 
@@ -81,6 +82,7 @@
         std::make_unique<download::test::MockDownloadService>();
     download_manager_ = std::make_unique<PredictionModelDownloadManager>(
         mock_download_service_.get(), temp_models_dir_.GetPath(),
+        /*prediction_model_store=*/nullptr, proto::ModelCacheKey(),
         task_environment_.GetMainThreadTaskRunner());
 
 #if !BUILDFLAG(IS_IOS)
diff --git a/components/optimization_guide/core/prediction_model_download_observer.h b/components/optimization_guide/core/prediction_model_download_observer.h
index 4c6468d1..0c16e78 100644
--- a/components/optimization_guide/core/prediction_model_download_observer.h
+++ b/components/optimization_guide/core/prediction_model_download_observer.h
@@ -14,8 +14,10 @@
 // completed.
 class PredictionModelDownloadObserver : public base::CheckedObserver {
  public:
-  // Invoked when a model has been downloaded and verified.
-  virtual void OnModelReady(const proto::PredictionModel& model) = 0;
+  // Invoked when a model has been downloaded and verified. |base_model_dir| is
+  // the base dir where model files are available.
+  virtual void OnModelReady(const base::FilePath& base_model_dir,
+                            const proto::PredictionModel& model) = 0;
 
   // Invoked when a model download started.
   virtual void OnModelDownloadStarted(
diff --git a/components/optimization_guide/core/prediction_model_store.cc b/components/optimization_guide/core/prediction_model_store.cc
index e2a576fb..69f548a 100644
--- a/components/optimization_guide/core/prediction_model_store.cc
+++ b/components/optimization_guide/core/prediction_model_store.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file_util.h"
 #include "base/guid.h"
+#include "base/memory/ptr_util.h"
 #include "base/rand_util.h"
 #include "base/task/thread_pool.h"
 #include "components/optimization_guide/core/model_store_metadata_entry.h"
@@ -13,7 +14,6 @@
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_prefs.h"
 #include "components/prefs/pref_service.h"
-#include "model_store_metadata_entry.h"
 
 namespace optimization_guide {
 
@@ -57,19 +57,44 @@
 
 }  // namespace
 
-PredictionModelStore::PredictionModelStore(PrefService* local_state,
-                                           const base::FilePath& base_store_dir)
-    : local_state_(local_state),
-      base_store_dir_(base_store_dir),
-      background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+// static
+PredictionModelStore* PredictionModelStore::GetInstance() {
+  static base::NoDestructor<PredictionModelStore> model_store;
+  return model_store.get();
+}
+
+PredictionModelStore::PredictionModelStore()
+    : background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
   DCHECK(optimization_guide::features::IsInstallWideModelStoreEnabled());
-  DCHECK(local_state);
-  DCHECK(!base_store_dir.empty());
 }
 
 PredictionModelStore::~PredictionModelStore() = default;
 
+void PredictionModelStore::Initialize(PrefService* local_state,
+                                      const base::FilePath& base_store_dir) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(local_state);
+  DCHECK(!base_store_dir.empty());
+
+  // Should not be initialized already.
+  DCHECK(!local_state_);
+  DCHECK(base_store_dir_.empty());
+
+  local_state_ = local_state;
+  base_store_dir_ = base_store_dir;
+}
+
+// static
+std::unique_ptr<PredictionModelStore>
+PredictionModelStore::CreatePredictionModelStoreForTesting(
+    PrefService* local_state,
+    const base::FilePath& base_store_dir) {
+  auto store = base::WrapUnique(new PredictionModelStore());
+  store->Initialize(local_state, base_store_dir);
+  return store;
+}
+
 bool PredictionModelStore::HasModel(
     proto::OptimizationTarget optimization_target,
     const proto::ModelCacheKey& model_cache_key) const {
@@ -155,6 +180,36 @@
   std::move(callback).Run(std::move(model));
 }
 
+void PredictionModelStore::UpdateMetadataForExistingModel(
+    proto::OptimizationTarget optimization_target,
+    const proto::ModelCacheKey& model_cache_key,
+    const proto::ModelInfo& model_info) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(model_info.has_version());
+  DCHECK_EQ(optimization_target, model_info.optimization_target());
+
+  if (!HasModel(optimization_target, model_cache_key))
+    return;
+
+  ModelStoreMetadataEntryUpdater metadata(local_state_, optimization_target,
+                                          model_cache_key);
+  auto base_model_dir = metadata.GetModelBaseDir();
+  DCHECK(base_store_dir_.IsParent(*base_model_dir));
+  if (model_info.has_valid_duration()) {
+    metadata.SetExpiryTime(
+        base::Time::Now() +
+        base::Seconds(model_info.valid_duration().seconds()));
+  }
+  metadata.SetKeepBeyondValidDuration(model_info.keep_beyond_valid_duration());
+  background_task_runner_->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&CheckAllPathsExist,
+                     GetModelFilePaths(model_info, *base_model_dir)),
+      base::BindOnce(&PredictionModelStore::OnModelUpdateVerified,
+                     weak_ptr_factory_.GetWeakPtr(), optimization_target,
+                     model_cache_key, base::DoNothing()));
+}
+
 void PredictionModelStore::UpdateModel(
     proto::OptimizationTarget optimization_target,
     const proto::ModelCacheKey& model_cache_key,
diff --git a/components/optimization_guide/core/prediction_model_store.h b/components/optimization_guide/core/prediction_model_store.h
index 60599d63..eaf8c8df 100644
--- a/components/optimization_guide/core/prediction_model_store.h
+++ b/components/optimization_guide/core/prediction_model_store.h
@@ -8,6 +8,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/no_destructor.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
@@ -32,8 +33,17 @@
   using PredictionModelLoadedCallback =
       base::OnceCallback<void(std::unique_ptr<proto::PredictionModel>)>;
 
-  PredictionModelStore(PrefService* local_state,
-                       const base::FilePath& base_store_dir);
+  // Returns the singleton model store.
+  static PredictionModelStore* GetInstance();
+
+  static std::unique_ptr<PredictionModelStore>
+  CreatePredictionModelStoreForTesting(PrefService* local_state,
+                                       const base::FilePath& base_store_dir);
+
+  // Initializes the model store with |local_state| and the |base_store_dir|.
+  // Model store will be usable only after it is initialized.
+  void Initialize(PrefService* local_state,
+                  const base::FilePath& base_store_dir);
 
   PredictionModelStore(const PredictionModelStore&) = delete;
   PredictionModelStore& operator=(const PredictionModelStore&) = delete;
@@ -55,6 +65,13 @@
                  const proto::ModelCacheKey& model_cache_key,
                  PredictionModelLoadedCallback callback);
 
+  // Update the model metadata for |model_info| if the model represented by
+  // |optimization_target| and |model_cache_key| exists.
+  void UpdateMetadataForExistingModel(
+      proto::OptimizationTarget optimization_target,
+      const proto::ModelCacheKey& model_cache_key,
+      const proto::ModelInfo& model_info);
+
   // Update the model for |model_info| in the store represented by
   // |optimization_target| and |model_cache_key|. The model files are stored in
   // |base_model_dir|. |callback| is invoked on completion.
@@ -72,7 +89,9 @@
       const proto::ModelCacheKey& model_cache_key);
 
  private:
-  friend class PredictionModelStoreTest;
+  friend base::NoDestructor<PredictionModelStore>;
+
+  PredictionModelStore();
 
   // Loads the model and verifies if the model files exist and returns the
   // model. Otherwise nullptr is returned on any failures.
diff --git a/components/optimization_guide/core/prediction_model_store_unittest.cc b/components/optimization_guide/core/prediction_model_store_unittest.cc
index 4d2828e..227847fe 100644
--- a/components/optimization_guide/core/prediction_model_store_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_store_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "components/optimization_guide/core/prediction_model_store.h"
-#include <memory>
 
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -59,8 +58,9 @@
     ASSERT_TRUE(temp_models_dir_.CreateUniqueTempDir());
     local_state_prefs_ = std::make_unique<TestingPrefServiceSimple>();
     prefs::RegisterLocalStatePrefs(local_state_prefs_->registry());
-    prediction_model_store_ = std::make_unique<PredictionModelStore>(
-        local_state_prefs_.get(), temp_models_dir_.GetPath());
+    prediction_model_store_ =
+        PredictionModelStore::CreatePredictionModelStoreForTesting(
+            local_state_prefs_.get(), temp_models_dir_.GetPath());
   }
 
   void OnPredictionModelLoaded(
@@ -206,4 +206,42 @@
   EXPECT_FALSE(last_loaded_prediction_model());
 }
 
+TEST_F(PredictionModelStoreTest, UpdateMetadataForExistingModel) {
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+  auto model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetFoo, model_cache_key, {});
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+
+  EXPECT_TRUE(prediction_model_store_->HasModel(kTestOptimizationTargetFoo,
+                                                model_cache_key));
+  prediction_model_store_->LoadModel(
+      kTestOptimizationTargetFoo, model_cache_key,
+      base::BindOnce(&PredictionModelStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  RunUntilIdle();
+  proto::PredictionModel* loaded_model = last_loaded_prediction_model();
+  EXPECT_TRUE(loaded_model);
+  EXPECT_EQ(kTestOptimizationTargetFoo,
+            loaded_model->model_info().optimization_target());
+  EXPECT_FALSE(loaded_model->model_info().keep_beyond_valid_duration());
+
+  proto::ModelInfo model_info;
+  model_info.set_optimization_target(kTestOptimizationTargetFoo);
+  model_info.set_version(1);
+  model_info.mutable_valid_duration()->set_seconds(
+      base::Minutes(100).InSeconds());
+  model_info.set_keep_beyond_valid_duration(true);
+  prediction_model_store_->UpdateMetadataForExistingModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_info);
+  RunUntilIdle();
+  auto metadata_entry = ModelStoreMetadataEntry::GetModelMetadataEntryIfExists(
+      local_state_prefs_.get(), kTestOptimizationTargetFoo, model_cache_key);
+  EXPECT_LE(base::Minutes(99),
+            metadata_entry->GetExpiryTime() - base::Time::Now());
+  EXPECT_TRUE(metadata_entry->GetKeepBeyondValidDuration());
+}
+
 }  // namespace optimization_guide
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index b823aca..e63cb51 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -577,7 +577,8 @@
 int32_t PasswordAutofillManager::GetWebContentsPopupControllerAxId() const {
   // TODO: Needs to be implemented when we step up accessibility features in the
   // future.
-  NOTIMPLEMENTED_LOG_ONCE() << "See http://crbug.com/991253";
+  // See http://crbug.com/991253
+  NOTIMPLEMENTED_LOG_ONCE();
   return 0;
 }
 
diff --git a/components/permissions/contexts/window_management_permission_context.cc b/components/permissions/contexts/window_management_permission_context.cc
index a984068..c1c5abf 100644
--- a/components/permissions/contexts/window_management_permission_context.cc
+++ b/components/permissions/contexts/window_management_permission_context.cc
@@ -28,7 +28,7 @@
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   // TODO(crbug.com/897300): Add window-placement support on Android.
-  NOTIMPLEMENTED_LOG_ONCE() << "window-placement permission is not supported";
+  NOTIMPLEMENTED_LOG_ONCE();
   return CONTENT_SETTING_BLOCK;
 }
 #endif  // IS_ANDROID
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index 5b171b23..de045ed 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1041,6 +1041,7 @@
   1040: ''
   1041: NewBaseUrlInheritanceBehaviorAllowed
   1042: ShowCastSessionsStartedByOtherDevices
+  1043: CloudAPAuthEnabled
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml b/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml
new file mode 100644
index 0000000..8fb2b243
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml
@@ -0,0 +1,35 @@
+caption: Allow automatic sign-in to Microsoft® cloud identity providers
+default: 0
+desc: |-
+  Configures automatic user sign-in for accounts backed by a Microsoft® cloud identity provider.
+
+  By setting this policy to 1 (<ph name="POLICY_VALUE_ENABLED">Enabled</ph>), users who sign into their computer with an account backed by a Microsoft® cloud identity provider (i.e., <ph name="MS_AAD_NAME">Microsoft® Azure® Active Directory®</ph> or the consumer Microsoft® account identity provider) or who have added a work or school account to <ph name="MS_WIN_NAME">Microsoft® Windows®</ph> can be signed into web properties using that identity automatically. Information pertaining to the user's device and account is transmitted to the user's cloud identity provider for each authentication event.
+
+  By setting this policy to 0 (<ph name="POLICY_VALUE_DISABLED">Disabled</ph>) or leaving it unset, automatic sign-in as described above is disabled.
+
+  This feature is available starting in <ph name="WIN_NAME">Microsoft® Windows®</ph> 10.
+
+  Note: This policy doesn't apply to Incognito or Guest modes unless <ph name="POLICY_AMBIENTAUTHENTICATIONINPRIVATEMODESENABLED">AmbientAuthenticationInPrivateModesEnabled</ph> is configured.
+example_value: 1
+features:
+  dynamic_refresh: true
+  per_profile: false
+future_on:
+- chrome.win
+items:
+- caption: Disable Microsoft® cloud authentication
+  name: Disabled
+  value: 0
+- caption: Enable Microsoft® cloud authentication
+  name: Enabled
+  value: 1
+owners:
+- igorruvinov@chromium.org
+- file://chrome/browser/enterprise/OWNERS
+schema:
+  enum:
+  - 0
+  - 1
+  type: integer
+tags: []
+type: int-enum
diff --git a/components/reporting/compression/BUILD.gn b/components/reporting/compression/BUILD.gn
index 434a1b4b..a3d060d 100644
--- a/components/reporting/compression/BUILD.gn
+++ b/components/reporting/compression/BUILD.gn
@@ -16,7 +16,7 @@
     "//base",
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
     "//third_party/snappy:snappy",
   ]
@@ -35,7 +35,7 @@
     "//base",
     "//base/test:test_support",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//testing/gmock",
@@ -54,7 +54,7 @@
     "//base",
     "//base/test:test_support",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//components/reporting/util:test_callbacks_support",
diff --git a/components/reporting/compression/compression_module.cc b/components/reporting/compression/compression_module.cc
index 007ba99..defb755 100644
--- a/components/reporting/compression/compression_module.cc
+++ b/components/reporting/compression/compression_module.cc
@@ -13,7 +13,7 @@
 #include "base/strings/string_piece.h"
 #include "base/task/thread_pool.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/snappy/src/snappy.h"
 
@@ -33,7 +33,7 @@
 
 void CompressionModule::CompressRecord(
     std::string record,
-    scoped_refptr<ResourceInterface> memory_resource,
+    scoped_refptr<ResourceManager> memory_resource,
     base::OnceCallback<void(std::string,
                             absl::optional<CompressionInformation>)> cb) const {
   if (!is_enabled()) {
diff --git a/components/reporting/compression/compression_module.h b/components/reporting/compression/compression_module.h
index dc80029..bfde175f 100644
--- a/components/reporting/compression/compression_module.h
+++ b/components/reporting/compression/compression_module.h
@@ -11,7 +11,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_piece.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/statusor.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -40,7 +40,7 @@
   // std::move(record).
   void CompressRecord(
       std::string record,
-      scoped_refptr<ResourceInterface> memory_resource,
+      scoped_refptr<ResourceManager> memory_resource,
       base::OnceCallback<
           void(std::string, absl::optional<CompressionInformation>)> cb) const;
 
diff --git a/components/reporting/compression/compression_module_unittest.cc b/components/reporting/compression/compression_module_unittest.cc
index c1b0417..9d81a4d 100644
--- a/components/reporting/compression/compression_module_unittest.cc
+++ b/components/reporting/compression/compression_module_unittest.cc
@@ -21,8 +21,7 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -40,7 +39,7 @@
 class CompressionModuleTest : public ::testing::Test {
  protected:
   CompressionModuleTest()
-      : memory_resource_(base::MakeRefCounted<MemoryResourceImpl>(
+      : memory_resource_(base::MakeRefCounted<ResourceManager>(
             4u * 1024LLu * 1024LLu))  // 4 MiB
   {}
 
@@ -58,9 +57,9 @@
     scoped_feature_list_.InitAndDisableFeature(kCompressReportingPipeline);
   }
 
-  scoped_refptr<ResourceInterface> memory_resource_;
+  base::test::TaskEnvironment task_environment_;
+  scoped_refptr<ResourceManager> memory_resource_;
   scoped_refptr<CompressionModule> compression_module_;
-  base::test::TaskEnvironment task_environment_{};
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
diff --git a/components/reporting/compression/test_compression_module.cc b/components/reporting/compression/test_compression_module.cc
index 038766b..fe2de0a 100644
--- a/components/reporting/compression/test_compression_module.cc
+++ b/components/reporting/compression/test_compression_module.cc
@@ -11,7 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 using ::testing::Invoke;
@@ -28,7 +28,7 @@
   ON_CALL(*this, CompressRecord)
       .WillByDefault(Invoke(
           [](std::string record,
-             scoped_refptr<ResourceInterface> resource_interface,
+             scoped_refptr<ResourceManager> resource_manager,
              base::OnceCallback<void(
                  std::string, absl::optional<CompressionInformation>)> cb) {
             // compression_info is not set.
diff --git a/components/reporting/compression/test_compression_module.h b/components/reporting/compression/test_compression_module.h
index 6b3c869..60099bd 100644
--- a/components/reporting/compression/test_compression_module.h
+++ b/components/reporting/compression/test_compression_module.h
@@ -12,7 +12,7 @@
 #include "base/strings/string_piece.h"
 #include "components/reporting/compression/compression_module.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -29,7 +29,7 @@
       void,
       CompressRecord,
       (std::string record,
-       scoped_refptr<ResourceInterface> memory_resource,
+       scoped_refptr<ResourceManager> memory_resource,
        base::OnceCallback<void(std::string,
                                absl::optional<CompressionInformation>)> cb),
       (const override));
diff --git a/components/reporting/encryption/BUILD.gn b/components/reporting/encryption/BUILD.gn
index fbed082..e3ce678 100644
--- a/components/reporting/encryption/BUILD.gn
+++ b/components/reporting/encryption/BUILD.gn
@@ -116,7 +116,7 @@
     "//base",
     "//base/test:test_support",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//testing/gmock",
@@ -145,7 +145,7 @@
     "//base",
     "//base/test:test_support",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
     "//components/reporting/util:test_callbacks_support",
diff --git a/components/reporting/encryption/encryption_module_unittest.cc b/components/reporting/encryption/encryption_module_unittest.cc
index decfca99..ae64eed76 100644
--- a/components/reporting/encryption/encryption_module_unittest.cc
+++ b/components/reporting/encryption/encryption_module_unittest.cc
@@ -25,7 +25,7 @@
 #include "components/reporting/encryption/primitives.h"
 #include "components/reporting/encryption/testing_primitives.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
diff --git a/components/reporting/resources/BUILD.gn b/components/reporting/resources/BUILD.gn
index 30924b0..118bcfcd 100644
--- a/components/reporting/resources/BUILD.gn
+++ b/components/reporting/resources/BUILD.gn
@@ -4,19 +4,15 @@
 
 import("//build/config/features.gni")
 
-static_library("resource_interface") {
+static_library("resource_manager") {
   visibility = [
     "//chrome/browser/*",
     "//chrome/test/*",
     "//components/reporting/*",
   ]
   sources = [
-    "disk_resource_impl.cc",
-    "disk_resource_impl.h",
-    "memory_resource_impl.cc",
-    "memory_resource_impl.h",
-    "resource_interface.cc",
-    "resource_interface.h",
+    "resource_manager.cc",
+    "resource_manager.h",
   ]
 
   deps = [ "//base" ]
@@ -27,9 +23,9 @@
 # TODO(chromium:1169835) These tests can't be run on iOS until they are updated.
 source_set("unit_tests") {
   testonly = true
-  sources = [ "resource_interface_unittest.cc" ]
+  sources = [ "resource_manager_unittest.cc" ]
   deps = [
-    ":resource_interface",
+    ":resource_manager",
     "//base",
     "//base/test:test_support",
     "//components/reporting/util:test_callbacks_support",
diff --git a/components/reporting/resources/disk_resource_impl.cc b/components/reporting/resources/disk_resource_impl.cc
deleted file mode 100644
index af87da9..0000000
--- a/components/reporting/resources/disk_resource_impl.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/reporting/resources/disk_resource_impl.h"
-
-#include <atomic>
-#include <cstdint>
-
-#include "base/check_op.h"
-
-namespace reporting {
-
-DiskResourceImpl::DiskResourceImpl(uint64_t total_size) : total_(total_size) {}
-
-DiskResourceImpl::~DiskResourceImpl() = default;
-
-bool DiskResourceImpl::Reserve(uint64_t size) {
-  uint64_t old_used = used_.fetch_add(size);
-  if (old_used + size > total_) {
-    used_.fetch_sub(size);
-    return false;
-  }
-  return true;
-}
-
-void DiskResourceImpl::Discard(uint64_t size) {
-  DCHECK_LE(size, used_.load());
-  used_.fetch_sub(size);
-}
-
-uint64_t DiskResourceImpl::GetTotal() const {
-  return total_;
-}
-
-uint64_t DiskResourceImpl::GetUsed() const {
-  return used_.load();
-}
-
-void DiskResourceImpl::Test_SetTotal(uint64_t test_total) {
-  total_ = test_total;
-}
-
-}  // namespace reporting
diff --git a/components/reporting/resources/disk_resource_impl.h b/components/reporting/resources/disk_resource_impl.h
deleted file mode 100644
index be3d59c..0000000
--- a/components/reporting/resources/disk_resource_impl.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_REPORTING_RESOURCES_DISK_RESOURCE_IMPL_H_
-#define COMPONENTS_REPORTING_RESOURCES_DISK_RESOURCE_IMPL_H_
-
-#include <atomic>
-#include <cstdint>
-
-#include "components/reporting/resources/resource_interface.h"
-
-namespace reporting {
-
-// Interface to resources management by Storage module.
-// Must be implemented by the caller base on the platform limitations.
-// All APIs are non-blocking.
-class DiskResourceImpl : public ResourceInterface {
- public:
-  explicit DiskResourceImpl(uint64_t total_size);
-
-  // Implementation of ResourceInterface methods.
-  bool Reserve(uint64_t size) override;
-  void Discard(uint64_t size) override;
-  uint64_t GetTotal() const override;
-  uint64_t GetUsed() const override;
-  void Test_SetTotal(uint64_t test_total) override;
-
- private:
-  ~DiskResourceImpl() override;
-
-  uint64_t total_;
-  std::atomic<uint64_t> used_{0};
-};
-
-}  // namespace reporting
-
-#endif  // COMPONENTS_REPORTING_RESOURCES_DISK_RESOURCE_IMPL_H_
diff --git a/components/reporting/resources/memory_resource_impl.cc b/components/reporting/resources/memory_resource_impl.cc
deleted file mode 100644
index b7f8588..0000000
--- a/components/reporting/resources/memory_resource_impl.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/reporting/resources/memory_resource_impl.h"
-
-#include <atomic>
-#include <cstdint>
-
-#include "base/check_op.h"
-
-namespace reporting {
-
-MemoryResourceImpl::MemoryResourceImpl(uint64_t total_size)
-    : total_(total_size) {}
-
-MemoryResourceImpl::~MemoryResourceImpl() = default;
-
-bool MemoryResourceImpl::Reserve(uint64_t size) {
-  uint64_t old_used = used_.fetch_add(size);
-  if (old_used + size > total_) {
-    used_.fetch_sub(size);
-    return false;
-  }
-  return true;
-}
-
-void MemoryResourceImpl::Discard(uint64_t size) {
-  DCHECK_LE(size, used_.load());
-  used_.fetch_sub(size);
-}
-
-uint64_t MemoryResourceImpl::GetTotal() const {
-  return total_;
-}
-
-uint64_t MemoryResourceImpl::GetUsed() const {
-  return used_.load();
-}
-
-void MemoryResourceImpl::Test_SetTotal(uint64_t test_total) {
-  total_ = test_total;
-}
-
-}  // namespace reporting
diff --git a/components/reporting/resources/memory_resource_impl.h b/components/reporting/resources/memory_resource_impl.h
deleted file mode 100644
index 8da03d20..0000000
--- a/components/reporting/resources/memory_resource_impl.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_REPORTING_RESOURCES_MEMORY_RESOURCE_IMPL_H_
-#define COMPONENTS_REPORTING_RESOURCES_MEMORY_RESOURCE_IMPL_H_
-
-#include <atomic>
-#include <cstdint>
-
-#include "components/reporting/resources/resource_interface.h"
-
-namespace reporting {
-
-// Interface to resources management by Storage module.
-// Must be implemented by the caller base on the platform limitations.
-// All APIs are non-blocking.
-class MemoryResourceImpl : public ResourceInterface {
- public:
-  explicit MemoryResourceImpl(uint64_t total_size);
-
-  // Implementation of ResourceInterface methods.
-  bool Reserve(uint64_t size) override;
-  void Discard(uint64_t size) override;
-  uint64_t GetTotal() const override;
-  uint64_t GetUsed() const override;
-  void Test_SetTotal(uint64_t test_total) override;
-
- private:
-  ~MemoryResourceImpl() override;
-
-  uint64_t total_;
-  std::atomic<uint64_t> used_{0};
-};
-
-}  // namespace reporting
-
-#endif  // COMPONENTS_REPORTING_RESOURCES_MEMORY_RESOURCE_IMPL_H_
diff --git a/components/reporting/resources/resource_interface.cc b/components/reporting/resources/resource_interface.cc
deleted file mode 100644
index fbcfeb9..0000000
--- a/components/reporting/resources/resource_interface.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/reporting/resources/resource_interface.h"
-
-#include <utility>
-
-#include <cstdint>
-
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_refptr.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace reporting {
-
-ScopedReservation::ScopedReservation() noexcept = default;
-
-ScopedReservation::ScopedReservation(
-    uint64_t size,
-    scoped_refptr<ResourceInterface> resource_interface) noexcept
-    : resource_interface_(resource_interface) {
-  if (size == 0uL || !resource_interface->Reserve(size)) {
-    return;
-  }
-  size_ = size;
-}
-
-ScopedReservation::ScopedReservation(
-    uint64_t size,
-    const ScopedReservation& other_reservation) noexcept
-    : resource_interface_(other_reservation.resource_interface_) {
-  if (size == 0uL || !resource_interface_.get() ||
-      !resource_interface_->Reserve(size)) {
-    return;
-  }
-  size_ = size;
-}
-
-ScopedReservation::ScopedReservation(ScopedReservation&& other) noexcept
-    : resource_interface_(other.resource_interface_),
-      size_(std::exchange(other.size_, absl::nullopt)) {}
-
-bool ScopedReservation::reserved() const {
-  return size_.has_value();
-}
-
-bool ScopedReservation::Reduce(uint64_t new_size) {
-  if (!reserved()) {
-    return false;
-  }
-  if (new_size < 0 || size_.value() < new_size) {
-    return false;
-  }
-  resource_interface_->Discard(size_.value() - new_size);
-  if (new_size > 0) {
-    size_ = new_size;
-  } else {
-    size_ = absl::nullopt;
-  }
-  return true;
-}
-
-void ScopedReservation::HandOver(ScopedReservation& other) {
-  if (resource_interface_.get()) {
-    DCHECK_EQ(resource_interface_.get(), other.resource_interface_.get())
-        << "Reservations are not related";
-  } else {
-    DCHECK(!reserved()) << "Unattached reservation may not have size";
-    resource_interface_ = other.resource_interface_;
-  }
-  if (!other.reserved()) {
-    return;  // Nothing changes.
-  }
-  const uint64_t old_size = (reserved() ? size_.value() : 0uL);
-  size_ = old_size + std::exchange(other.size_, absl::nullopt).value();
-}
-
-ScopedReservation::~ScopedReservation() {
-  if (reserved()) {
-    resource_interface_->Discard(size_.value());
-  }
-}
-
-}  // namespace reporting
diff --git a/components/reporting/resources/resource_interface.h b/components/reporting/resources/resource_interface.h
deleted file mode 100644
index 44c4c87..0000000
--- a/components/reporting/resources/resource_interface.h
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_REPORTING_RESOURCES_RESOURCE_INTERFACE_H_
-#define COMPONENTS_REPORTING_RESOURCES_RESOURCE_INTERFACE_H_
-
-#include <cstdint>
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_refptr.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace reporting {
-
-// Interface to resources management by Storage module.
-// Must be implemented by the caller base on the platform limitations.
-// All APIs are non-blocking.
-class ResourceInterface : public base::RefCountedThreadSafe<ResourceInterface> {
- public:
-  // Needs to be called before attempting to allocate specified size.
-  // Returns true if requested amount can be allocated.
-  // After that the caller can actually allocate it or must call
-  // |Discard| if decided not to allocate.
-  virtual bool Reserve(uint64_t size) = 0;
-
-  // Reverts reservation.
-  // Must be called after the specified amount is released.
-  virtual void Discard(uint64_t size) = 0;
-
-  // Returns total amount.
-  virtual uint64_t GetTotal() const = 0;
-
-  // Returns current used amount.
-  virtual uint64_t GetUsed() const = 0;
-
-  // Test only: Sets non-default usage limit.
-  virtual void Test_SetTotal(uint64_t test_total) = 0;
-
- protected:
-  friend class base::RefCountedThreadSafe<ResourceInterface>;
-
-  ResourceInterface() = default;
-  virtual ~ResourceInterface() = default;
-};
-
-// Moveable RAII class used for scoped Reserve-Discard.
-//
-// Usage:
-//  {
-//    ScopedReservation reservation(1024u, options.memory_resource());
-//    if (!reservation.reserved()) {
-//      // Allocation failed.
-//      return;
-//    }
-//    // Allocation succeeded.
-//    ...
-//  }   // Automatically discarded.
-//
-// Can be handed over to another owner by move-constructor or using HandOver
-// method:
-// {
-//   ScopedReservation summary;
-//   for (const uint64_t size : sizes) {
-//     ScopedReservation single_reservation(size, resource);
-//     ...
-//     summary.HandOver(single_reservation);
-//   }
-// }
-class ScopedReservation {
- public:
-  // Zero-size reservation with no resource interface attached.
-  // reserved() returns false.
-  ScopedReservation() noexcept;
-  // Specified reservation, must have resource interface attached.
-  ScopedReservation(
-      uint64_t size,
-      scoped_refptr<ResourceInterface> resource_interface) noexcept;
-  // New reservation on the same resource interface as |other_reservation|.
-  ScopedReservation(uint64_t size,
-                    const ScopedReservation& other_reservation) noexcept;
-  // Move constructor.
-  ScopedReservation(ScopedReservation&& other) noexcept;
-  ScopedReservation(const ScopedReservation& other) = delete;
-  ScopedReservation& operator=(ScopedReservation&& other) = delete;
-  ScopedReservation& operator=(const ScopedReservation& other) = delete;
-  ~ScopedReservation();
-
-  bool reserved() const;
-
-  // Reduces reservation to |new_size|.
-  bool Reduce(uint64_t new_size);
-
-  // Adds |other| to |this| without assigning or releasing any reservation.
-  // Used for seamless transition from one reservation to another (more generic
-  // than std::move). Resets |other| to non-reserved state upon return from this
-  // method.
-  void HandOver(ScopedReservation& other);
-
- private:
-  scoped_refptr<ResourceInterface> resource_interface_;
-  absl::optional<uint64_t> size_;
-};
-
-}  // namespace reporting
-
-#endif  // COMPONENTS_REPORTING_RESOURCES_RESOURCE_INTERFACE_H_
diff --git a/components/reporting/resources/resource_interface_unittest.cc b/components/reporting/resources/resource_interface_unittest.cc
deleted file mode 100644
index b7664616..0000000
--- a/components/reporting/resources/resource_interface_unittest.cc
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/reporting/resources/resource_interface.h"
-
-#include <cstdint>
-#include <utility>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/task/task_runner.h"
-#include "base/task/thread_pool.h"
-#include "base/test/task_environment.h"
-#include "components/reporting/resources/disk_resource_impl.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/util/test_support_callbacks.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::Eq;
-
-namespace reporting {
-namespace {
-
-class ResourceInterfaceTest
-    : public ::testing::TestWithParam<scoped_refptr<ResourceInterface>> {
- protected:
-  void SetUp() override {
-    // Make sure parameters define reasonably large total resource size.
-    ASSERT_GE(resource_interface()->GetTotal(), 1u * 1024LLu * 1024LLu);
-  }
-
-  scoped_refptr<ResourceInterface> resource_interface() const {
-    return GetParam();
-  }
-
-  void TearDown() override {
-    EXPECT_THAT(resource_interface()->GetUsed(), Eq(0u));
-  }
-
- private:
-  base::test::TaskEnvironment task_environment_;
-};
-
-TEST_P(ResourceInterfaceTest, NestedReservationTest) {
-  uint64_t size = resource_interface()->GetTotal();
-  while ((size / 2) > 0u) {
-    size /= 2;
-    EXPECT_TRUE(resource_interface()->Reserve(size));
-  }
-
-  for (; size < resource_interface()->GetTotal(); size *= 2) {
-    resource_interface()->Discard(size);
-  }
-}
-
-TEST_P(ResourceInterfaceTest, SimultaneousReservationTest) {
-  uint64_t size = resource_interface()->GetTotal();
-
-  // Schedule reservations.
-  test::TestCallbackWaiter reserve_waiter;
-  while ((size / 2) > 0u) {
-    size /= 2;
-    reserve_waiter.Attach();
-    base::ThreadPool::PostTask(
-        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
-        base::BindOnce(
-            [](size_t size, scoped_refptr<ResourceInterface> resource_interface,
-               test::TestCallbackWaiter* waiter) {
-              EXPECT_TRUE(resource_interface->Reserve(size));
-              waiter->Signal();
-            },
-            size, resource_interface(), &reserve_waiter));
-  }
-  reserve_waiter.Wait();
-
-  // Schedule discards.
-  test::TestCallbackWaiter discard_waiter;
-  for (; size < resource_interface()->GetTotal(); size *= 2) {
-    discard_waiter.Attach();
-    base::ThreadPool::PostTask(
-        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
-        base::BindOnce(
-            [](size_t size, scoped_refptr<ResourceInterface> resource_interface,
-               test::TestCallbackWaiter* waiter) {
-              resource_interface->Discard(size);
-              waiter->Signal();
-            },
-            size, resource_interface(), &discard_waiter));
-  }
-  discard_waiter.Wait();
-}
-
-TEST_P(ResourceInterfaceTest, SimultaneousScopedReservationTest) {
-  uint64_t size = resource_interface()->GetTotal();
-  test::TestCallbackWaiter waiter;
-  while ((size / 2) > 0u) {
-    size /= 2;
-    waiter.Attach();
-    base::ThreadPool::PostTask(
-        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
-        base::BindOnce(
-            [](size_t size, scoped_refptr<ResourceInterface> resource_interface,
-               test::TestCallbackWaiter* waiter) {
-              { ScopedReservation(size, resource_interface); }
-              waiter->Signal();
-            },
-            size, resource_interface(), &waiter));
-  }
-  waiter.Wait();
-}
-
-TEST_P(ResourceInterfaceTest, MoveScopedReservationTest) {
-  uint64_t size = resource_interface()->GetTotal();
-  ScopedReservation scoped_reservation(size / 2, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-  {
-    ScopedReservation moved_scoped_reservation(std::move(scoped_reservation));
-    EXPECT_TRUE(moved_scoped_reservation.reserved());
-    EXPECT_FALSE(scoped_reservation.reserved());
-  }
-  EXPECT_FALSE(scoped_reservation.reserved());
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationBasicReduction) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-  EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationReductionWithLargerNewSize) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-  EXPECT_FALSE(scoped_reservation.Reduce(size + 1));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationReductionWithNegativeNewSize) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-  EXPECT_FALSE(scoped_reservation.Reduce(-(size / 2)));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingReductions) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-
-  for (; size >= 2; size /= 2) {
-    EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
-  }
-  EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
-  EXPECT_FALSE(scoped_reservation.reserved());
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationBasicHandOver) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  ASSERT_TRUE(scoped_reservation.reserved());
-  {
-    ScopedReservation another_reservation(size - 1, resource_interface());
-    ASSERT_TRUE(another_reservation.reserved());
-    EXPECT_THAT(resource_interface()->GetUsed(),
-                Eq(resource_interface()->GetTotal() - 1));
-    EXPECT_TRUE(scoped_reservation.reserved());
-    EXPECT_TRUE(another_reservation.reserved());
-    scoped_reservation.HandOver(another_reservation);
-    EXPECT_THAT(resource_interface()->GetUsed(),
-                Eq(resource_interface()->GetTotal() - 1));
-  }
-  // Destruction of |anoter_reservation| does not change the amount used.
-  EXPECT_THAT(resource_interface()->GetUsed(),
-              Eq(resource_interface()->GetTotal() - 1));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingHandOvers) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-
-  for (; size >= 2; size /= 2) {
-    ScopedReservation another_reservation(size / 2, resource_interface());
-    scoped_reservation.HandOver(another_reservation);
-  }
-  EXPECT_THAT(resource_interface()->GetUsed(),
-              Eq(resource_interface()->GetTotal() - 1));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingCopyHandOvers) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-  EXPECT_TRUE(scoped_reservation.reserved());
-
-  for (; size >= 2; size /= 2) {
-    ScopedReservation another_reservation(size / 2, scoped_reservation);
-    EXPECT_TRUE(another_reservation.reserved());
-    scoped_reservation.HandOver(another_reservation);
-  }
-  EXPECT_THAT(resource_interface()->GetUsed(),
-              Eq(resource_interface()->GetTotal() - 1));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationFailureToCopyFromEmpty) {
-  ScopedReservation scoped_reservation;
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation another_reservation(size, scoped_reservation);
-  EXPECT_FALSE(scoped_reservation.reserved());
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingHandOversToEmpty) {
-  ScopedReservation scoped_reservation;
-  EXPECT_FALSE(scoped_reservation.reserved());
-
-  uint64_t size = resource_interface()->GetTotal();
-  for (; size >= 2; size /= 2) {
-    ScopedReservation another_reservation(size / 2, resource_interface());
-    scoped_reservation.HandOver(another_reservation);
-  }
-  EXPECT_THAT(resource_interface()->GetUsed(),
-              Eq(resource_interface()->GetTotal() - 1));
-}
-
-TEST_P(ResourceInterfaceTest, ScopedReservationEmptyHandOver) {
-  uint64_t size = resource_interface()->GetTotal() / 2;
-  ScopedReservation scoped_reservation(size, resource_interface());
-
-  ASSERT_TRUE(scoped_reservation.reserved());
-  {
-    ScopedReservation another_reservation(size - 1, resource_interface());
-    ASSERT_TRUE(another_reservation.reserved());
-
-    EXPECT_THAT(resource_interface()->GetUsed(),
-                Eq(resource_interface()->GetTotal() - 1));
-    EXPECT_TRUE(scoped_reservation.reserved());
-    EXPECT_TRUE(another_reservation.reserved());
-
-    another_reservation.Reduce(0);
-    ASSERT_FALSE(another_reservation.reserved());
-
-    scoped_reservation.HandOver(another_reservation);
-    EXPECT_THAT(resource_interface()->GetUsed(), Eq(size));
-  }
-  // Destruction of |anoter_reservation| does not change the amount used.
-  EXPECT_THAT(resource_interface()->GetUsed(), Eq(size));
-}
-
-TEST_P(ResourceInterfaceTest, ReservationOverMaxTest) {
-  EXPECT_FALSE(
-      resource_interface()->Reserve(resource_interface()->GetTotal() + 1));
-  EXPECT_TRUE(resource_interface()->Reserve(resource_interface()->GetTotal()));
-  resource_interface()->Discard(resource_interface()->GetTotal());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    VariousResources,
-    ResourceInterfaceTest,
-    testing::Values(
-        base::MakeRefCounted<DiskResourceImpl>(16u * 1024LLu * 1024LLu),
-        base::MakeRefCounted<MemoryResourceImpl>(4u * 1024LLu * 1024LLu)));
-}  // namespace
-}  // namespace reporting
diff --git a/components/reporting/resources/resource_manager.cc b/components/reporting/resources/resource_manager.cc
new file mode 100644
index 0000000..929bd9b
--- /dev/null
+++ b/components/reporting/resources/resource_manager.cc
@@ -0,0 +1,163 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/reporting/resources/resource_manager.h"
+
+#include <atomic>
+#include <utility>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace reporting {
+
+ResourceManager::ResourceManager(uint64_t total_size)
+    : total_(total_size),
+      sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+          {base::TaskPriority::BEST_EFFORT})) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+ResourceManager::~ResourceManager() = default;
+
+bool ResourceManager::Reserve(uint64_t size) {
+  uint64_t old_used = used_.fetch_add(size);
+  if (old_used + size > total_) {
+    used_.fetch_sub(size);
+    return false;
+  }
+  return true;
+}
+
+void ResourceManager::Discard(uint64_t size) {
+  DCHECK_LE(size, used_.load());
+  used_.fetch_sub(size);
+
+  sequenced_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&ResourceManager::FlushCallbacks,
+                                base::WrapRefCounted(this)));
+}
+
+uint64_t ResourceManager::GetTotal() const {
+  return total_;
+}
+
+uint64_t ResourceManager::GetUsed() const {
+  return used_.load();
+}
+
+void ResourceManager::Test_SetTotal(uint64_t test_total) {
+  total_ = test_total;
+}
+
+void ResourceManager::RegisterCallback(uint64_t size, base::OnceClosure cb) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](scoped_refptr<ResourceManager> self, uint64_t size,
+             base::OnceClosure cb) {
+            DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
+            self->resource_callbacks_.emplace(size, std::move(cb));
+
+            // Attempt to apply remaining callbacks
+            // (this is especially important if the new callback is registered
+            // with no allocations to be released - we don't want the callback
+            // to wait indefinitely in this case).
+            self->FlushCallbacks();
+          },
+          base::WrapRefCounted(this), size,
+          base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
+                             std::move(cb))));
+}
+
+void ResourceManager::FlushCallbacks() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  int64_t remained = GetTotal() - GetUsed();  // No synchronization whatsoever.
+  while (remained > 0 && !resource_callbacks_.empty()) {
+    auto& cb_pair = resource_callbacks_.front();
+    if (cb_pair.first > static_cast<uint64_t>(remained)) {
+      break;
+    }
+    remained -= cb_pair.first;
+    std::move(cb_pair.second).Run();
+    resource_callbacks_.pop();
+  }
+}
+
+ScopedReservation::ScopedReservation() noexcept = default;
+
+ScopedReservation::ScopedReservation(
+    uint64_t size,
+    scoped_refptr<ResourceManager> resource_manager) noexcept
+    : resource_manager_(resource_manager) {
+  if (size == 0uL || !resource_manager->Reserve(size)) {
+    return;
+  }
+  size_ = size;
+}
+
+ScopedReservation::ScopedReservation(
+    uint64_t size,
+    const ScopedReservation& other_reservation) noexcept
+    : resource_manager_(other_reservation.resource_manager_) {
+  if (size == 0uL || !resource_manager_.get() ||
+      !resource_manager_->Reserve(size)) {
+    return;
+  }
+  size_ = size;
+}
+
+ScopedReservation::ScopedReservation(ScopedReservation&& other) noexcept
+    : resource_manager_(other.resource_manager_),
+      size_(std::exchange(other.size_, absl::nullopt)) {}
+
+bool ScopedReservation::reserved() const {
+  return size_.has_value();
+}
+
+bool ScopedReservation::Reduce(uint64_t new_size) {
+  if (!reserved()) {
+    return false;
+  }
+  if (new_size < 0 || size_.value() < new_size) {
+    return false;
+  }
+  resource_manager_->Discard(size_.value() - new_size);
+  if (new_size > 0) {
+    size_ = new_size;
+  } else {
+    size_ = absl::nullopt;
+  }
+  return true;
+}
+
+void ScopedReservation::HandOver(ScopedReservation& other) {
+  if (resource_manager_.get()) {
+    DCHECK_EQ(resource_manager_.get(), other.resource_manager_.get())
+        << "Reservations are not related";
+  } else {
+    DCHECK(!reserved()) << "Unattached reservation may not have size";
+    resource_manager_ = other.resource_manager_;
+  }
+  if (!other.reserved()) {
+    return;  // Nothing changes.
+  }
+  const uint64_t old_size = (reserved() ? size_.value() : 0uL);
+  size_ = old_size + std::exchange(other.size_, absl::nullopt).value();
+}
+
+ScopedReservation::~ScopedReservation() {
+  if (reserved()) {
+    resource_manager_->Discard(size_.value());
+  }
+}
+}  // namespace reporting
diff --git a/components/reporting/resources/resource_manager.h b/components/reporting/resources/resource_manager.h
new file mode 100644
index 0000000..26fa940
--- /dev/null
+++ b/components/reporting/resources/resource_manager.h
@@ -0,0 +1,141 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_
+#define COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_
+
+#include <atomic>
+#include <cstdint>
+#include <queue>
+#include <utility>
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/thread_annotations.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace reporting {
+
+// Resource management class. The class is thread-safe.
+// Each resource instance is created with its own total size; the rest of the
+// functionality is identical. All APIs are non-blocking.
+class ResourceManager : public base::RefCountedThreadSafe<ResourceManager> {
+ public:
+  explicit ResourceManager(uint64_t total_size);
+
+  // Needs to be called before attempting to allocate specified size.
+  // Returns true if requested amount can be allocated.
+  // After that the caller can actually allocate it or must call
+  // |Discard| if decided not to allocate.
+  bool Reserve(uint64_t size);
+
+  // Reverts reservation, arranges for callbacks calls as necessary.
+  // Must be called after the specified amount is released.
+  void Discard(uint64_t size);
+
+  // Returns total amount.
+  uint64_t GetTotal() const;
+
+  // Returns current used amount.
+  uint64_t GetUsed() const;
+
+  // Registers a callback to be invoked once there is specified amount
+  // of resource available (does not reserve it, so once called back
+  // the respective code must attempt to reserve again, and if unsuccessful,
+  // may need ot re-register the callback).
+  // Callbacks will be invoked in the context of the sequenced task runner
+  // it was registered in.
+  void RegisterCallback(uint64_t size, base::OnceClosure cb);
+
+  // Test only: Sets non-default usage limit.
+  void Test_SetTotal(uint64_t test_total);
+
+ private:
+  friend class base::RefCountedThreadSafe<ResourceManager>;
+
+  ~ResourceManager();
+
+  // Flushes as many callbacks as possible given the current resource
+  // availability. Callbacks only signal that resource may be available,
+  // the resumed task must try to actually reserve it after that.
+  void FlushCallbacks();
+
+  uint64_t total_;  // Remains constant in prod code, changes only in tests.
+  std::atomic<uint64_t> used_{0};
+
+  // Sequenced task runner for callbacks handling (not for calling callbacks!)
+  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Queue of pairs [size, callback].
+  // When `Discard` leaves enough space available (even momentarily),
+  // calls as many of the callbacks as fit in that size, in the queue order.
+  // Note that in a meantime reservation may change - the called back code
+  // must attempt reservation before using it.
+  std::queue<std::pair<uint64_t, base::OnceClosure>> resource_callbacks_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+// Moveable RAII class used for scoped Reserve-Discard.
+//
+// Usage:
+//  {
+//    ScopedReservation reservation(1024u, options.memory_resource());
+//    if (!reservation.reserved()) {
+//      // Allocation failed.
+//      return;
+//    }
+//    // Allocation succeeded.
+//    ...
+//  }   // Automatically discarded.
+//
+// Can be handed over to another owner by move-constructor or using HandOver
+// method:
+// {
+//   ScopedReservation summary;
+//   for (const uint64_t size : sizes) {
+//     ScopedReservation single_reservation(size, resource);
+//     ...
+//     summary.HandOver(single_reservation);
+//   }
+// }
+class ScopedReservation {
+ public:
+  // Zero-size reservation with no resource interface attached.
+  // reserved() returns false.
+  ScopedReservation() noexcept;
+  // Specified reservation, must have resource interface attached.
+  ScopedReservation(uint64_t size,
+                    scoped_refptr<ResourceManager> resource_manager) noexcept;
+  // New reservation on the same resource interface as |other_reservation|.
+  ScopedReservation(uint64_t size,
+                    const ScopedReservation& other_reservation) noexcept;
+  // Move constructor.
+  ScopedReservation(ScopedReservation&& other) noexcept;
+  ScopedReservation(const ScopedReservation& other) = delete;
+  ScopedReservation& operator=(ScopedReservation&& other) = delete;
+  ScopedReservation& operator=(const ScopedReservation& other) = delete;
+  ~ScopedReservation();
+
+  bool reserved() const;
+
+  // Reduces reservation to |new_size|.
+  bool Reduce(uint64_t new_size);
+
+  // Adds |other| to |this| without assigning or releasing any reservation.
+  // Used for seamless transition from one reservation to another (more generic
+  // than std::move). Resets |other| to non-reserved state upon return from this
+  // method.
+  void HandOver(ScopedReservation& other);
+
+ private:
+  scoped_refptr<ResourceManager> resource_manager_;
+  absl::optional<uint64_t> size_;
+};
+
+}  // namespace reporting
+
+#endif  // COMPONENTS_REPORTING_RESOURCES_RESOURCE_MANAGER_H_
diff --git a/components/reporting/resources/resource_manager_unittest.cc b/components/reporting/resources/resource_manager_unittest.cc
new file mode 100644
index 0000000..90c0b20b
--- /dev/null
+++ b/components/reporting/resources/resource_manager_unittest.cc
@@ -0,0 +1,363 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/reporting/resources/resource_manager.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/reporting/util/test_support_callbacks.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Eq;
+
+namespace reporting {
+namespace {
+
+class ResourceInterfaceTest : public ::testing::TestWithParam<uint64_t> {
+ protected:
+  void SetUp() override {
+    resource_ = base::MakeRefCounted<ResourceManager>(GetParam());
+    // Make sure parameters define reasonably large total resource size.
+    ASSERT_GE(resource_->GetTotal(), 1u * 1024LLu * 1024LLu);
+  }
+
+  void TearDown() override { EXPECT_THAT(resource_->GetUsed(), Eq(0u)); }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  scoped_refptr<ResourceManager> resource_;
+};
+
+TEST_P(ResourceInterfaceTest, NestedReservationTest) {
+  uint64_t size = resource_->GetTotal();
+  while ((size / 2) > 0u) {
+    size /= 2;
+    EXPECT_TRUE(resource_->Reserve(size));
+  }
+
+  for (; size < resource_->GetTotal(); size *= 2) {
+    resource_->Discard(size);
+  }
+}
+
+TEST_P(ResourceInterfaceTest, SimultaneousReservationTest) {
+  uint64_t size = resource_->GetTotal();
+
+  // Schedule reservations.
+  test::TestCallbackWaiter reserve_waiter;
+  while ((size / 2) > 0u) {
+    size /= 2;
+    reserve_waiter.Attach();
+    base::ThreadPool::PostTask(
+        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(
+            [](size_t size, scoped_refptr<ResourceManager> resource_manager,
+               test::TestCallbackWaiter* waiter) {
+              EXPECT_TRUE(resource_manager->Reserve(size));
+              waiter->Signal();
+            },
+            size, resource_, &reserve_waiter));
+  }
+  reserve_waiter.Wait();
+
+  // Schedule discards.
+  test::TestCallbackWaiter discard_waiter;
+  for (; size < resource_->GetTotal(); size *= 2) {
+    discard_waiter.Attach();
+    base::ThreadPool::PostTask(
+        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(
+            [](size_t size, scoped_refptr<ResourceManager> resource_manager,
+               test::TestCallbackWaiter* waiter) {
+              resource_manager->Discard(size);
+              waiter->Signal();
+            },
+            size, resource_, &discard_waiter));
+  }
+  discard_waiter.Wait();
+}
+
+TEST_P(ResourceInterfaceTest, SimultaneousScopedReservationTest) {
+  uint64_t size = resource_->GetTotal();
+  test::TestCallbackWaiter waiter;
+  while ((size / 2) > 0u) {
+    size /= 2;
+    waiter.Attach();
+    base::ThreadPool::PostTask(
+        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(
+            [](size_t size, scoped_refptr<ResourceManager> resource_manager,
+               test::TestCallbackWaiter* waiter) {
+              { ScopedReservation(size, resource_manager); }
+              waiter->Signal();
+            },
+            size, resource_, &waiter));
+  }
+  waiter.Wait();
+}
+
+TEST_P(ResourceInterfaceTest, MoveScopedReservationTest) {
+  uint64_t size = resource_->GetTotal();
+  ScopedReservation scoped_reservation(size / 2, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+  {
+    ScopedReservation moved_scoped_reservation(std::move(scoped_reservation));
+    EXPECT_TRUE(moved_scoped_reservation.reserved());
+    EXPECT_FALSE(scoped_reservation.reserved());
+  }
+  EXPECT_FALSE(scoped_reservation.reserved());
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationBasicReduction) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+  EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationReductionWithLargerNewSize) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+  EXPECT_FALSE(scoped_reservation.Reduce(size + 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationReductionWithNegativeNewSize) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+  EXPECT_FALSE(scoped_reservation.Reduce(-(size / 2)));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingReductions) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+
+  for (; size >= 2; size /= 2) {
+    EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
+  }
+  EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
+  EXPECT_FALSE(scoped_reservation.reserved());
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationBasicHandOver) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  ASSERT_TRUE(scoped_reservation.reserved());
+  {
+    ScopedReservation another_reservation(size - 1, resource_);
+    ASSERT_TRUE(another_reservation.reserved());
+    EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+    EXPECT_TRUE(scoped_reservation.reserved());
+    EXPECT_TRUE(another_reservation.reserved());
+    scoped_reservation.HandOver(another_reservation);
+    EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+  }
+  // Destruction of |anoter_reservation| does not change the amount used.
+  EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingHandOvers) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+
+  for (; size >= 2; size /= 2) {
+    ScopedReservation another_reservation(size / 2, resource_);
+    scoped_reservation.HandOver(another_reservation);
+  }
+  EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingCopyHandOvers) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+  EXPECT_TRUE(scoped_reservation.reserved());
+
+  for (; size >= 2; size /= 2) {
+    ScopedReservation another_reservation(size / 2, scoped_reservation);
+    EXPECT_TRUE(another_reservation.reserved());
+    scoped_reservation.HandOver(another_reservation);
+  }
+  EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationFailureToCopyFromEmpty) {
+  ScopedReservation scoped_reservation;
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation another_reservation(size, scoped_reservation);
+  EXPECT_FALSE(scoped_reservation.reserved());
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingHandOversToEmpty) {
+  ScopedReservation scoped_reservation;
+  EXPECT_FALSE(scoped_reservation.reserved());
+
+  uint64_t size = resource_->GetTotal();
+  for (; size >= 2; size /= 2) {
+    ScopedReservation another_reservation(size / 2, resource_);
+    scoped_reservation.HandOver(another_reservation);
+  }
+  EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationEmptyHandOver) {
+  uint64_t size = resource_->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_);
+
+  ASSERT_TRUE(scoped_reservation.reserved());
+  {
+    ScopedReservation another_reservation(size - 1, resource_);
+    ASSERT_TRUE(another_reservation.reserved());
+
+    EXPECT_THAT(resource_->GetUsed(), Eq(resource_->GetTotal() - 1));
+    EXPECT_TRUE(scoped_reservation.reserved());
+    EXPECT_TRUE(another_reservation.reserved());
+
+    another_reservation.Reduce(0);
+    ASSERT_FALSE(another_reservation.reserved());
+
+    scoped_reservation.HandOver(another_reservation);
+    EXPECT_THAT(resource_->GetUsed(), Eq(size));
+  }
+  // Destruction of |anoter_reservation| does not change the amount used.
+  EXPECT_THAT(resource_->GetUsed(), Eq(size));
+}
+
+TEST_P(ResourceInterfaceTest, ReservationOverMaxTest) {
+  EXPECT_FALSE(resource_->Reserve(resource_->GetTotal() + 1));
+  EXPECT_TRUE(resource_->Reserve(resource_->GetTotal()));
+  resource_->Discard(resource_->GetTotal());
+}
+
+// Helper class with the following behavior:
+// - Once created, immediately posts `Start` action on a dedicated task runner
+// - `Start` registers a callback for `size` resource
+// - When Callback happens (on the same task runner), the work is done;
+//   `done` is called and then teh `Actor` commits suiside.
+class Actor {
+ public:
+  Actor(uint64_t size,
+        base::OnceClosure done,
+        scoped_refptr<ResourceManager> resource_manager)
+      : size_(size),
+        done_(std::move(done)),
+        resource_manager_(resource_manager) {
+    DETACH_FROM_SEQUENCE(sequence_checker_);
+    EXPECT_TRUE(done_);
+    sequenced_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&Actor::Start, base::Unretained(this)));
+  }
+
+  ~Actor() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    EXPECT_FALSE(done_);
+  }
+
+  void Start() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    // Pretend that the reservation was unavailable, schedule callback when it
+    // becomes available.
+    resource_manager_->RegisterCallback(
+        size_,
+        base::BindOnce(&Actor::OnResourceRelease, base::Unretained(this)));
+  }
+
+  void Execute() {
+    auto done = std::move(done_);
+    resource_manager_->Discard(size_);
+    delete this;
+    // Signal after deletion, to prevent potential test flake.
+    std::move(done).Run();
+  }
+
+  void OnResourceRelease() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);  // Same task runner!
+    if (!resource_manager_->Reserve(size_)) {
+      // Still not available, reschedule callback.
+      resource_manager_->RegisterCallback(
+          size_,
+          base::BindOnce(&Actor::OnResourceRelease, base::Unretained(this)));
+      return;
+    }
+    // Reserved. Pause, then release and delete itself.
+    sequenced_task_runner_->PostDelayedTask(
+        FROM_HERE, base::BindOnce(&Actor::Execute, base::Unretained(this)),
+        base::Seconds(1));
+  }
+
+ private:
+  const uint64_t size_;
+  base::OnceClosure done_;
+  scoped_refptr<ResourceManager> resource_manager_;
+  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_{
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::TaskPriority::BEST_EFFORT})};
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+TEST_P(ResourceInterfaceTest, ReservationWithWaits) {
+  static constexpr size_t kActorsCount = 64;
+
+  // Occupy whole resource.
+
+  ASSERT_TRUE(resource_->Reserve(resource_->GetTotal()));
+  // Create a number of Actors reserving 1/2 of total resource.
+  // Only two of them could fit simultaneously, others will wait and retry
+  // getting the resource after being called back.
+  test::TestCallbackAutoWaiter waiter;
+  waiter.Attach(kActorsCount - 1);
+  for (size_t i = 0; i < kActorsCount; ++i) {
+    new Actor(resource_->GetTotal() / 2,
+              base::BindOnce(&test::TestCallbackAutoWaiter::Signal,
+                             base::Unretained(&waiter)),
+              resource_);
+  }
+
+  // Release resource.
+  resource_->Discard(resource_->GetTotal());
+
+  // All waiting Actors are called back to get resource, finish work,
+  // and then the waiter will be signaled.
+  task_environment_.FastForwardUntilNoTasksRemain();
+}
+
+TEST_P(ResourceInterfaceTest, ReservationWithWaitsOnEmptyReservation) {
+  // Similar to the previous test, but with no reservation other than
+  // the Actors.
+  static constexpr size_t kActorsCount = 64;
+
+  // Create a number of Actors reserving 1/2 of total resource.
+  // Only two of them could fit simultaneously, others will wait and retry
+  // getting the resource after being called back.
+  test::TestCallbackAutoWaiter waiter;
+  waiter.Attach(kActorsCount - 1);
+  for (size_t i = 0; i < kActorsCount; ++i) {
+    new Actor(resource_->GetTotal() / 2,
+              base::BindOnce(&test::TestCallbackAutoWaiter::Signal,
+                             base::Unretained(&waiter)),
+              resource_);
+  }
+
+  // Waiting Actors are called back to get resource, finish work,
+  // and then the waiter will be signaled.
+  task_environment_.FastForwardUntilNoTasksRemain();
+}
+
+INSTANTIATE_TEST_SUITE_P(VariousResources,
+                         ResourceInterfaceTest,
+                         testing::Values(16u * 1024LLu * 1024LLu,
+                                         4u * 1024LLu * 1024LLu));
+}  // namespace
+}  // namespace reporting
diff --git a/components/reporting/storage/BUILD.gn b/components/reporting/storage/BUILD.gn
index ac16a953..8b52f23 100644
--- a/components/reporting/storage/BUILD.gn
+++ b/components/reporting/storage/BUILD.gn
@@ -13,7 +13,7 @@
   deps = [
     "//base",
     "//components/reporting/proto:record_constants",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
   ]
 }
 
@@ -28,7 +28,7 @@
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
     "//components/reporting/proto:status_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:status",
   ]
 }
@@ -48,7 +48,7 @@
     "//components/reporting/encryption:verification",
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:file",
     "//components/reporting/util:refcounted_closure_list",
     "//components/reporting/util:status",
@@ -79,7 +79,7 @@
     "//components/reporting/health:health_module",
     "//components/reporting/proto:record_constants",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:file",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
@@ -192,7 +192,7 @@
     "//components/reporting/encryption:test_support",
     "//components/reporting/encryption:testing_primitives",
     "//components/reporting/proto:record_proto",
-    "//components/reporting/resources:resource_interface",
+    "//components/reporting/resources:resource_manager",
     "//components/reporting/util:file",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
diff --git a/components/reporting/storage/storage.cc b/components/reporting/storage/storage.cc
index abc10f75..784e5c2 100644
--- a/components/reporting/storage/storage.cc
+++ b/components/reporting/storage/storage.cc
@@ -32,7 +32,7 @@
 #include "components/reporting/encryption/primitives.h"
 #include "components/reporting/encryption/verification.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_queue.h"
 #include "components/reporting/storage/storage_uploader_interface.h"
diff --git a/components/reporting/storage/storage_configuration.cc b/components/reporting/storage/storage_configuration.cc
index 41d403a..d163be6 100644
--- a/components/reporting/storage/storage_configuration.cc
+++ b/components/reporting/storage/storage_configuration.cc
@@ -60,9 +60,9 @@
 }  // namespace
 
 StorageOptions::StorageOptions()
-    : memory_resource_(base::MakeRefCounted<MemoryResourceImpl>(
+    : memory_resource_(base::MakeRefCounted<ResourceManager>(
           4u * 1024uLL * 1024uLL)),  // 4 MiB by default
-      disk_space_resource_(base::MakeRefCounted<DiskResourceImpl>(
+      disk_space_resource_(base::MakeRefCounted<ResourceManager>(
           64u * 1024uLL * 1024uLL))  // 64 MiB by default.
 {}
 StorageOptions::StorageOptions(const StorageOptions& options) = default;
diff --git a/components/reporting/storage/storage_configuration.h b/components/reporting/storage/storage_configuration.h
index 29154e2..f8f20375 100644
--- a/components/reporting/storage/storage_configuration.h
+++ b/components/reporting/storage/storage_configuration.h
@@ -14,9 +14,7 @@
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/disk_resource_impl.h"
-#include "components/reporting/resources/memory_resource_impl.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 
 namespace reporting {
 
@@ -60,12 +58,12 @@
   }
   StorageOptions& set_max_total_files_size(uint64_t max_total_files_size) {
     disk_space_resource_ =
-        base::MakeRefCounted<DiskResourceImpl>(max_total_files_size);
+        base::MakeRefCounted<ResourceManager>(max_total_files_size);
     return *this;
   }
   StorageOptions& set_max_total_memory_size(uint64_t max_total_memory_size) {
     memory_resource_ =
-        base::MakeRefCounted<MemoryResourceImpl>(max_total_memory_size);
+        base::MakeRefCounted<ResourceManager>(max_total_memory_size);
     return *this;
   }
   const base::FilePath& directory() const { return directory_; }
@@ -80,10 +78,10 @@
     return memory_resource_->GetTotal();
   }
 
-  scoped_refptr<ResourceInterface> disk_space_resource() const {
+  scoped_refptr<ResourceManager> disk_space_resource() const {
     return disk_space_resource_.get();
   }
-  scoped_refptr<ResourceInterface> memory_resource() const {
+  scoped_refptr<ResourceManager> memory_resource() const {
     return memory_resource_;
   }
 
@@ -99,8 +97,8 @@
   size_t max_record_size_ = 1U * 1024UL * 1024UL;  // 1 MiB
 
   // Resources managements.
-  scoped_refptr<ResourceInterface> memory_resource_;
-  scoped_refptr<ResourceInterface> disk_space_resource_;
+  scoped_refptr<ResourceManager> memory_resource_;
+  scoped_refptr<ResourceManager> disk_space_resource_;
 };
 
 // Single queue options class allowing to set parameters individually, e.g.:
@@ -152,10 +150,10 @@
   base::TimeDelta upload_period() const { return upload_period_; }
   base::TimeDelta upload_retry_delay() const { return upload_retry_delay_; }
   bool can_shed_records() const { return can_shed_records_; }
-  scoped_refptr<ResourceInterface> disk_space_resource() const {
+  scoped_refptr<ResourceManager> disk_space_resource() const {
     return storage_options_.disk_space_resource();
   }
-  scoped_refptr<ResourceInterface> memory_resource() const {
+  scoped_refptr<ResourceManager> memory_resource() const {
     return storage_options_.memory_resource();
   }
 
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index 3efcf1e..5862711 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -42,7 +42,7 @@
 #include "components/reporting/compression/compression_module.h"
 #include "components/reporting/encryption/encryption_module_interface.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_uploader_interface.h"
 #include "components/reporting/util/file.h"
@@ -2089,8 +2089,8 @@
 StorageQueue::SingleFile::Create(
     const base::FilePath& filename,
     int64_t size,
-    scoped_refptr<ResourceInterface> memory_resource,
-    scoped_refptr<ResourceInterface> disk_space_resource,
+    scoped_refptr<ResourceManager> memory_resource,
+    scoped_refptr<ResourceManager> disk_space_resource,
     scoped_refptr<RefCountedClosureList> completion_closure_list) {
   if (!disk_space_resource->Reserve(size)) {
     LOG(WARNING) << "Disk space exceeded adding file "
@@ -2109,8 +2109,8 @@
 StorageQueue::SingleFile::SingleFile(
     const base::FilePath& filename,
     int64_t size,
-    scoped_refptr<ResourceInterface> memory_resource,
-    scoped_refptr<ResourceInterface> disk_space_resource,
+    scoped_refptr<ResourceManager> memory_resource,
+    scoped_refptr<ResourceManager> disk_space_resource,
     scoped_refptr<RefCountedClosureList> completion_closure_list)
     : completion_closure_list_(completion_closure_list),
       filename_(filename),
diff --git a/components/reporting/storage/storage_queue.h b/components/reporting/storage/storage_queue.h
index d9085ce..ef18f72 100644
--- a/components/reporting/storage/storage_queue.h
+++ b/components/reporting/storage/storage_queue.h
@@ -170,8 +170,8 @@
     static StatusOr<scoped_refptr<SingleFile>> Create(
         const base::FilePath& filename,
         int64_t size,
-        scoped_refptr<ResourceInterface> memory_resource,
-        scoped_refptr<ResourceInterface> disk_space_resource,
+        scoped_refptr<ResourceManager> memory_resource,
+        scoped_refptr<ResourceManager> disk_space_resource,
         scoped_refptr<RefCountedClosureList> completion_closure_list);
 
     Status Open(bool read_only);  // No-op if already opened.
@@ -213,8 +213,8 @@
     // Private constructor, called by factory method only.
     SingleFile(const base::FilePath& filename,
                int64_t size,
-               scoped_refptr<ResourceInterface> memory_resource,
-               scoped_refptr<ResourceInterface> disk_space_resource,
+               scoped_refptr<ResourceManager> memory_resource,
+               scoped_refptr<ResourceManager> disk_space_resource,
                scoped_refptr<RefCountedClosureList> completion_closure_list);
 
     SEQUENCE_CHECKER(sequence_checker_);
@@ -232,8 +232,8 @@
 
     std::unique_ptr<base::File> handle_;  // Set only when opened/created.
 
-    const scoped_refptr<ResourceInterface> memory_resource_;
-    const scoped_refptr<ResourceInterface> disk_space_resource_;
+    const scoped_refptr<ResourceManager> memory_resource_;
+    const scoped_refptr<ResourceManager> disk_space_resource_;
 
     // When reading the file, this is the buffer and data positions.
     // If the data is read sequentially, buffered portions are reused
diff --git a/components/reporting/storage/storage_queue_stress_test.cc b/components/reporting/storage/storage_queue_stress_test.cc
index 920b84d..8dde253 100644
--- a/components/reporting/storage/storage_queue_stress_test.cc
+++ b/components/reporting/storage/storage_queue_stress_test.cc
@@ -25,7 +25,7 @@
 #include "components/reporting/compression/test_compression_module.h"
 #include "components/reporting/encryption/test_encryption_module.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index 321ad92..32e7d76 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -28,7 +28,7 @@
 #include "components/reporting/compression/decompression.h"
 #include "components/reporting/encryption/test_encryption_module.h"
 #include "components/reporting/proto/synced/record.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/util/file.h"
 #include "components/reporting/util/status.h"
diff --git a/components/reporting/storage/storage_unittest.cc b/components/reporting/storage/storage_unittest.cc
index 9002503..acad306c 100644
--- a/components/reporting/storage/storage_unittest.cc
+++ b/components/reporting/storage/storage_unittest.cc
@@ -30,7 +30,7 @@
 #include "components/reporting/encryption/testing_primitives.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/storage/storage_uploader_interface.h"
 #include "components/reporting/util/status.h"
diff --git a/components/reporting/storage/storage_uploader_interface.h b/components/reporting/storage/storage_uploader_interface.h
index b5abce0..26c3e43c 100644
--- a/components/reporting/storage/storage_uploader_interface.h
+++ b/components/reporting/storage/storage_uploader_interface.h
@@ -12,7 +12,7 @@
 #include "base/strings/string_piece.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
-#include "components/reporting/resources/resource_interface.h"
+#include "components/reporting/resources/resource_manager.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
 
diff --git a/components/segmentation_platform/components_unittests.filter b/components/segmentation_platform/components_unittests.filter
index 9a12d35..3e38a6d 100644
--- a/components/segmentation_platform/components_unittests.filter
+++ b/components/segmentation_platform/components_unittests.filter
@@ -20,6 +20,7 @@
 ModelExecutorTest.*
 ModelProviderFactoryImplTest.*
 OptimizationGuideSegmentationModelProviderTest.*
+PostProcessorTest.*
 PowerUserModelTest.*
 PriceTrackingActionModelTest.*
 PriceTrackingInputDelegateTest.*
diff --git a/components/segmentation_platform/embedder/default_model/cross_device_user_segment.h b/components/segmentation_platform/embedder/default_model/cross_device_user_segment.h
index 62df286..0525b19 100644
--- a/components/segmentation_platform/embedder/default_model/cross_device_user_segment.h
+++ b/components/segmentation_platform/embedder/default_model/cross_device_user_segment.h
@@ -18,8 +18,8 @@
   CrossDeviceUserSegment();
   ~CrossDeviceUserSegment() override = default;
 
-  CrossDeviceUserSegment(CrossDeviceUserSegment&) = delete;
-  CrossDeviceUserSegment& operator=(CrossDeviceUserSegment&) = delete;
+  CrossDeviceUserSegment(const CrossDeviceUserSegment&) = delete;
+  CrossDeviceUserSegment& operator=(const CrossDeviceUserSegment&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/feed_user_segment.h b/components/segmentation_platform/embedder/default_model/feed_user_segment.h
index ecd3b4a..04b30d2a 100644
--- a/components/segmentation_platform/embedder/default_model/feed_user_segment.h
+++ b/components/segmentation_platform/embedder/default_model/feed_user_segment.h
@@ -18,8 +18,8 @@
   FeedUserSegment();
   ~FeedUserSegment() override = default;
 
-  FeedUserSegment(FeedUserSegment&) = delete;
-  FeedUserSegment& operator=(FeedUserSegment&) = delete;
+  FeedUserSegment(const FeedUserSegment&) = delete;
+  FeedUserSegment& operator=(const FeedUserSegment&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.h b/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.h
index 7d71a65e..e385f12e 100644
--- a/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.h
+++ b/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.h
@@ -17,8 +17,8 @@
   ~FrequentFeatureUserModel() override = default;
 
   // Disallow copy/assign.
-  FrequentFeatureUserModel(FrequentFeatureUserModel&) = delete;
-  FrequentFeatureUserModel& operator=(FrequentFeatureUserModel&) = delete;
+  FrequentFeatureUserModel(const FrequentFeatureUserModel&) = delete;
+  FrequentFeatureUserModel& operator=(const FrequentFeatureUserModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/intentional_user_model.h b/components/segmentation_platform/embedder/default_model/intentional_user_model.h
index 37012ed2..01cc791 100644
--- a/components/segmentation_platform/embedder/default_model/intentional_user_model.h
+++ b/components/segmentation_platform/embedder/default_model/intentional_user_model.h
@@ -20,8 +20,8 @@
   ~IntentionalUserModel() override = default;
 
   // Disallow copy/assign.
-  IntentionalUserModel(IntentionalUserModel&) = delete;
-  IntentionalUserModel& operator=(IntentionalUserModel&) = delete;
+  IntentionalUserModel(const IntentionalUserModel&) = delete;
+  IntentionalUserModel& operator=(const IntentionalUserModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
index 6bffe09..46a20f1 100644
--- a/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
+++ b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
@@ -19,8 +19,8 @@
   ~LowUserEngagementModel() override = default;
 
   // Disallow copy/assign.
-  LowUserEngagementModel(LowUserEngagementModel&) = delete;
-  LowUserEngagementModel& operator=(LowUserEngagementModel&) = delete;
+  LowUserEngagementModel(const LowUserEngagementModel&) = delete;
+  LowUserEngagementModel& operator=(const LowUserEngagementModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/power_user_segment.h b/components/segmentation_platform/embedder/default_model/power_user_segment.h
index fe6c36d..4c2de35 100644
--- a/components/segmentation_platform/embedder/default_model/power_user_segment.h
+++ b/components/segmentation_platform/embedder/default_model/power_user_segment.h
@@ -18,8 +18,8 @@
   PowerUserSegment();
   ~PowerUserSegment() override = default;
 
-  PowerUserSegment(PowerUserSegment&) = delete;
-  PowerUserSegment& operator=(PowerUserSegment&) = delete;
+  PowerUserSegment(const PowerUserSegment&) = delete;
+  PowerUserSegment& operator=(const PowerUserSegment&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
index 5f4a32f..cff67213 100644
--- a/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
+++ b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
@@ -16,8 +16,8 @@
   PriceTrackingActionModel();
   ~PriceTrackingActionModel() override = default;
 
-  PriceTrackingActionModel(PriceTrackingActionModel&) = delete;
-  PriceTrackingActionModel& operator=(PriceTrackingActionModel&) = delete;
+  PriceTrackingActionModel(const PriceTrackingActionModel&) = delete;
+  PriceTrackingActionModel& operator=(const PriceTrackingActionModel&) = delete;
 
   // ModelProvider implementation.
   void InitAndFetchModel(
diff --git a/components/segmentation_platform/embedder/default_model/query_tiles_model.h b/components/segmentation_platform/embedder/default_model/query_tiles_model.h
index 6b2199e..bba3afa 100644
--- a/components/segmentation_platform/embedder/default_model/query_tiles_model.h
+++ b/components/segmentation_platform/embedder/default_model/query_tiles_model.h
@@ -19,8 +19,8 @@
   ~QueryTilesModel() override = default;
 
   // Disallow copy/assign.
-  QueryTilesModel(QueryTilesModel&) = delete;
-  QueryTilesModel& operator=(QueryTilesModel&) = delete;
+  QueryTilesModel(const QueryTilesModel&) = delete;
+  QueryTilesModel& operator=(const QueryTilesModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.h b/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.h
index 17e2ea8..5c9f26a 100644
--- a/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.h
+++ b/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.h
@@ -20,8 +20,8 @@
   ~ResumeHeavyUserModel() override = default;
 
   // Disallow copy/assign.
-  ResumeHeavyUserModel(ResumeHeavyUserModel&) = delete;
-  ResumeHeavyUserModel& operator=(ResumeHeavyUserModel&) = delete;
+  ResumeHeavyUserModel(const ResumeHeavyUserModel&) = delete;
+  ResumeHeavyUserModel& operator=(const ResumeHeavyUserModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/search_user_model.h b/components/segmentation_platform/embedder/default_model/search_user_model.h
index cd4e9cc..7fe916d 100644
--- a/components/segmentation_platform/embedder/default_model/search_user_model.h
+++ b/components/segmentation_platform/embedder/default_model/search_user_model.h
@@ -20,8 +20,8 @@
   ~SearchUserModel() override = default;
 
   // Disallow copy/assign.
-  SearchUserModel(SearchUserModel&) = delete;
-  SearchUserModel& operator=(SearchUserModel&) = delete;
+  SearchUserModel(const SearchUserModel&) = delete;
+  SearchUserModel& operator=(const SearchUserModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/default_model/shopping_user_model.h b/components/segmentation_platform/embedder/default_model/shopping_user_model.h
index 3b98f869c..0c3f6be3 100644
--- a/components/segmentation_platform/embedder/default_model/shopping_user_model.h
+++ b/components/segmentation_platform/embedder/default_model/shopping_user_model.h
@@ -19,8 +19,8 @@
   ~ShoppingUserModel() override = default;
 
   // Disallow copy/assign.
-  ShoppingUserModel(ShoppingUserModel&) = delete;
-  ShoppingUserModel& operator=(ShoppingUserModel&) = delete;
+  ShoppingUserModel(const ShoppingUserModel&) = delete;
+  ShoppingUserModel& operator=(const ShoppingUserModel&) = delete;
 
   static std::unique_ptr<Config> GetConfig();
 
diff --git a/components/segmentation_platform/embedder/input_delegate/price_tracking_input_delegate.h b/components/segmentation_platform/embedder/input_delegate/price_tracking_input_delegate.h
index f54859ec..f9f2278 100644
--- a/components/segmentation_platform/embedder/input_delegate/price_tracking_input_delegate.h
+++ b/components/segmentation_platform/embedder/input_delegate/price_tracking_input_delegate.h
@@ -39,8 +39,9 @@
       BookmarkModelGetter bookmark_model_getter);
   ~PriceTrackingInputDelegate() override;
 
-  PriceTrackingInputDelegate(PriceTrackingInputDelegate&) = delete;
-  PriceTrackingInputDelegate& operator=(PriceTrackingInputDelegate&) = delete;
+  PriceTrackingInputDelegate(const PriceTrackingInputDelegate&) = delete;
+  PriceTrackingInputDelegate& operator=(const PriceTrackingInputDelegate&) =
+      delete;
 
   // InputDelegate overrides.
   void Process(const proto::CustomInput& input,
diff --git a/components/segmentation_platform/embedder/model_provider_factory_impl.h b/components/segmentation_platform/embedder/model_provider_factory_impl.h
index 613f24b..449e3bbd 100644
--- a/components/segmentation_platform/embedder/model_provider_factory_impl.h
+++ b/components/segmentation_platform/embedder/model_provider_factory_impl.h
@@ -31,8 +31,8 @@
 
   ~ModelProviderFactoryImpl() override;
 
-  ModelProviderFactoryImpl(ModelProviderFactoryImpl&) = delete;
-  ModelProviderFactoryImpl& operator=(ModelProviderFactoryImpl&) = delete;
+  ModelProviderFactoryImpl(const ModelProviderFactoryImpl&) = delete;
+  ModelProviderFactoryImpl& operator=(const ModelProviderFactoryImpl&) = delete;
 
   // ModelProviderFactory impl:
   std::unique_ptr<ModelProvider> CreateProvider(
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn
index 86346b6f..f247346 100644
--- a/components/segmentation_platform/internal/BUILD.gn
+++ b/components/segmentation_platform/internal/BUILD.gn
@@ -98,6 +98,8 @@
     "metadata/metadata_writer.h",
     "platform_options.cc",
     "platform_options.h",
+    "post_processor/post_processor.cc",
+    "post_processor/post_processor.h",
     "scheduler/execution_service.cc",
     "scheduler/execution_service.h",
     "scheduler/model_execution_scheduler.h",
@@ -247,6 +249,7 @@
     "metadata/metadata_utils_unittest.cc",
     "mock_ukm_data_manager.cc",
     "mock_ukm_data_manager.h",
+    "post_processor/post_processor_unittest.cc",
     "scheduler/model_execution_scheduler_unittest.cc",
     "segmentation_platform_service_impl_unittest.cc",
     "segmentation_platform_service_test_base.cc",
diff --git a/components/segmentation_platform/internal/database/storage_service.h b/components/segmentation_platform/internal/database/storage_service.h
index 8d604de..5b22294 100644
--- a/components/segmentation_platform/internal/database/storage_service.h
+++ b/components/segmentation_platform/internal/database/storage_service.h
@@ -94,8 +94,8 @@
 
   ~StorageService();
 
-  StorageService(StorageService&) = delete;
-  StorageService& operator=(StorageService&) = delete;
+  StorageService(const StorageService&) = delete;
+  StorageService& operator=(const StorageService&) = delete;
 
   // Initialize all the databases and returns true when all of them are
   // initialized successfully.
diff --git a/components/segmentation_platform/internal/database/ukm_database.h b/components/segmentation_platform/internal/database/ukm_database.h
index 06e032cb..0b4768c3 100644
--- a/components/segmentation_platform/internal/database/ukm_database.h
+++ b/components/segmentation_platform/internal/database/ukm_database.h
@@ -27,8 +27,8 @@
   UkmDatabase() = default;
   virtual ~UkmDatabase() = default;
 
-  UkmDatabase(UkmDatabase&) = delete;
-  UkmDatabase& operator=(UkmDatabase&) = delete;
+  UkmDatabase(const UkmDatabase&) = delete;
+  UkmDatabase& operator=(const UkmDatabase&) = delete;
 
   using SuccessCallback = base::OnceCallback<void(bool)>;
 
diff --git a/components/segmentation_platform/internal/database/ukm_database_impl.h b/components/segmentation_platform/internal/database/ukm_database_impl.h
index d86e5ec..c0439cf 100644
--- a/components/segmentation_platform/internal/database/ukm_database_impl.h
+++ b/components/segmentation_platform/internal/database/ukm_database_impl.h
@@ -26,8 +26,8 @@
   explicit UkmDatabaseImpl(const base::FilePath& database_path);
   ~UkmDatabaseImpl() override;
 
-  UkmDatabaseImpl(UkmDatabaseImpl&) = delete;
-  UkmDatabaseImpl& operator=(UkmDatabaseImpl&) = delete;
+  UkmDatabaseImpl(const UkmDatabaseImpl&) = delete;
+  UkmDatabaseImpl& operator=(const UkmDatabaseImpl&) = delete;
 
   void InitDatabase(SuccessCallback callback) override;
   void StoreUkmEntry(ukm::mojom::UkmEntryPtr ukm_entry) override;
diff --git a/components/segmentation_platform/internal/database/ukm_url_table.h b/components/segmentation_platform/internal/database/ukm_url_table.h
index 01662feb..669f0a9 100644
--- a/components/segmentation_platform/internal/database/ukm_url_table.h
+++ b/components/segmentation_platform/internal/database/ukm_url_table.h
@@ -24,8 +24,8 @@
   explicit UkmUrlTable(sql::Database* db);
   ~UkmUrlTable();
 
-  UkmUrlTable(UkmUrlTable&) = delete;
-  UkmUrlTable& operator=(UkmUrlTable&) = delete;
+  UkmUrlTable(const UkmUrlTable&) = delete;
+  UkmUrlTable& operator=(const UkmUrlTable&) = delete;
 
   // Converts the given GURL to string.
   static std::string GetDatabaseUrlString(const GURL& url);
diff --git a/components/segmentation_platform/internal/dummy_ukm_data_manager.h b/components/segmentation_platform/internal/dummy_ukm_data_manager.h
index f7916de..ba0127e 100644
--- a/components/segmentation_platform/internal/dummy_ukm_data_manager.h
+++ b/components/segmentation_platform/internal/dummy_ukm_data_manager.h
@@ -16,8 +16,8 @@
   DummyUkmDataManager();
   ~DummyUkmDataManager() override;
 
-  DummyUkmDataManager(DummyUkmDataManager&) = delete;
-  DummyUkmDataManager& operator=(DummyUkmDataManager&) = delete;
+  DummyUkmDataManager(const DummyUkmDataManager&) = delete;
+  DummyUkmDataManager& operator=(const DummyUkmDataManager&) = delete;
 
   // UkmDataManager implementation:
   void Initialize(const base::FilePath& database_path,
diff --git a/components/segmentation_platform/internal/execution/execution_request.h b/components/segmentation_platform/internal/execution/execution_request.h
index 85e1f54..ca9da25b 100644
--- a/components/segmentation_platform/internal/execution/execution_request.h
+++ b/components/segmentation_platform/internal/execution/execution_request.h
@@ -26,8 +26,8 @@
   explicit ModelExecutionResult(ModelExecutionStatus status);
   ~ModelExecutionResult();
 
-  ModelExecutionResult(ModelExecutionResult&) = delete;
-  ModelExecutionResult& operator=(ModelExecutionResult&) = delete;
+  ModelExecutionResult(const ModelExecutionResult&) = delete;
+  ModelExecutionResult& operator=(const ModelExecutionResult&) = delete;
 
   // The float value is only valid when ModelExecutionStatus == kSuccess.
   // TODO(ritikagup): Change ModelProvider::Response as key value pair in
diff --git a/components/segmentation_platform/internal/execution/model_executor.h b/components/segmentation_platform/internal/execution/model_executor.h
index c9b9c59..840f5d1 100644
--- a/components/segmentation_platform/internal/execution/model_executor.h
+++ b/components/segmentation_platform/internal/execution/model_executor.h
@@ -20,8 +20,8 @@
   ModelExecutor() = default;
   virtual ~ModelExecutor() = default;
 
-  ModelExecutor(ModelExecutor&) = delete;
-  ModelExecutor& operator=(ModelExecutor&) = delete;
+  ModelExecutor(const ModelExecutor&) = delete;
+  ModelExecutor& operator=(const ModelExecutor&) = delete;
 
   // Called to execute a given model. This assumes that data has been collected
   // for long enough for each of the individual ML features.
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.h b/components/segmentation_platform/internal/execution/model_executor_impl.h
index 61bb943..8417f59f 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.h
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.h
@@ -36,8 +36,8 @@
       processing::FeatureListQueryProcessor* feature_list_query_processor);
   ~ModelExecutorImpl() override;
 
-  ModelExecutorImpl(ModelExecutorImpl&) = delete;
-  ModelExecutorImpl& operator=(ModelExecutorImpl&) = delete;
+  ModelExecutorImpl(const ModelExecutorImpl&) = delete;
+  ModelExecutorImpl& operator=(const ModelExecutorImpl&) = delete;
 
   // ModelExecutionManager impl:.
   void ExecuteModel(std::unique_ptr<ExecutionRequest> request) override;
diff --git a/components/segmentation_platform/internal/local_state_helper_impl.h b/components/segmentation_platform/internal/local_state_helper_impl.h
index f654763..8a8856f 100644
--- a/components/segmentation_platform/internal/local_state_helper_impl.h
+++ b/components/segmentation_platform/internal/local_state_helper_impl.h
@@ -17,8 +17,8 @@
 // Implementation of the LocalStateHelper class.
 class LocalStateHelperImpl : public LocalStateHelper {
  public:
-  LocalStateHelperImpl(LocalStateHelperImpl&) = delete;
-  LocalStateHelperImpl& operator=(LocalStateHelperImpl&) = delete;
+  LocalStateHelperImpl(const LocalStateHelperImpl&) = delete;
+  LocalStateHelperImpl& operator=(const LocalStateHelperImpl&) = delete;
 
   // LocalStateHelper implementation.
   void Initialize(PrefService* local_state) override;
diff --git a/components/segmentation_platform/internal/metadata/metadata_utils.cc b/components/segmentation_platform/internal/metadata/metadata_utils.cc
index 4518710..6f0f4f7d 100644
--- a/components/segmentation_platform/internal/metadata/metadata_utils.cc
+++ b/components/segmentation_platform/internal/metadata/metadata_utils.cc
@@ -17,6 +17,7 @@
 #include "components/segmentation_platform/public/features.h"
 #include "components/segmentation_platform/public/proto/aggregation.pb.h"
 #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
+#include "components/segmentation_platform/public/proto/output_config.pb.h"
 #include "components/segmentation_platform/public/proto/segmentation_platform.pb.h"
 #include "components/segmentation_platform/public/proto/types.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -405,5 +406,17 @@
   return features;
 }
 
+proto::PredictionResult CreatePredictionResult(
+    const std::vector<float>& model_scores,
+    const proto::OutputConfig& output_config,
+    base::Time timestamp) {
+  proto::PredictionResult result;
+  result.mutable_result()->Add(model_scores.begin(), model_scores.end());
+  result.mutable_output_config()->CopyFrom(output_config);
+  result.set_timestamp_us(
+      timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
+  return result;
+}
+
 }  // namespace metadata_utils
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/metadata/metadata_utils.h b/components/segmentation_platform/internal/metadata/metadata_utils.h
index 490ea93..bf35b0a 100644
--- a/components/segmentation_platform/internal/metadata/metadata_utils.h
+++ b/components/segmentation_platform/internal/metadata/metadata_utils.h
@@ -116,6 +116,12 @@
     const proto::SegmentationModelMetadata& model_metadata,
     bool include_outputs);
 
+// Creates prediction result for a given segment.
+proto::PredictionResult CreatePredictionResult(
+    const std::vector<float>& model_scores,
+    const proto::OutputConfig& output_config,
+    base::Time timestamp);
+
 }  // namespace metadata_utils
 }  // namespace segmentation_platform
 
diff --git a/components/segmentation_platform/internal/metadata/metadata_writer.cc b/components/segmentation_platform/internal/metadata/metadata_writer.cc
index d3c59624..545f222 100644
--- a/components/segmentation_platform/internal/metadata/metadata_writer.cc
+++ b/components/segmentation_platform/internal/metadata/metadata_writer.cc
@@ -128,4 +128,48 @@
                                 /*result_time_to_live=*/1);
 }
 
+void MetadataWriter::AddOutputConfigForBinaryClassifier(
+    float threshold,
+    const std::string& positive_label,
+    const std::string& negative_label) {
+  proto::Predictor_BinaryClassifier* binary_classifier =
+      metadata_->mutable_output_config()
+          ->mutable_predictor()
+          ->mutable_binary_classifier();
+
+  binary_classifier->set_threshold(threshold);
+  binary_classifier->set_positive_label(positive_label);
+  binary_classifier->set_negative_label(negative_label);
+}
+
+void MetadataWriter::AddOutputConfigForMultiClassClassifier(
+    const std::vector<std::string>& class_labels,
+    int top_k_outputs) {
+  proto::Predictor_MultiClassClassifier* multi_class_classifier =
+      metadata_->mutable_output_config()
+          ->mutable_predictor()
+          ->mutable_multi_class_classifier();
+
+  multi_class_classifier->set_top_k_outputs(top_k_outputs);
+  multi_class_classifier->mutable_class_labels()->Assign(class_labels.begin(),
+                                                         class_labels.end());
+}
+
+void MetadataWriter::AddOutputConfigForBinnedClassifier(
+    const std::vector<std::pair<float, std::string>>& bins,
+    std::string underflow_label) {
+  proto::Predictor_BinnedClassifier* binned_classifier =
+      metadata_->mutable_output_config()
+          ->mutable_predictor()
+          ->mutable_binned_classifier();
+
+  binned_classifier->set_underflow_label(underflow_label);
+  for (const std::pair<float, std::string>& bin : bins) {
+    proto::Predictor::BinnedClassifier::Bin* current_bin =
+        binned_classifier->add_bins();
+    current_bin->set_min_range(bin.first);
+    current_bin->set_label(bin.second);
+  }
+}
+
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/metadata/metadata_writer.h b/components/segmentation_platform/internal/metadata/metadata_writer.h
index cfe76a3..9d9e3b5 100644
--- a/components/segmentation_platform/internal/metadata/metadata_writer.h
+++ b/components/segmentation_platform/internal/metadata/metadata_writer.h
@@ -20,8 +20,8 @@
   explicit MetadataWriter(proto::SegmentationModelMetadata* metadata);
   ~MetadataWriter();
 
-  MetadataWriter(MetadataWriter&) = delete;
-  MetadataWriter& operator=(MetadataWriter&) = delete;
+  MetadataWriter(const MetadataWriter&) = delete;
+  MetadataWriter& operator=(const MetadataWriter&) = delete;
 
   // Defines a feature based on UMA metric.
   struct UMAFeature {
@@ -140,6 +140,21 @@
       int min_signal_collection_length_days = 7,
       int signal_storage_length_days = 28);
 
+  // Adds a BinaryClassifier.
+  void AddOutputConfigForBinaryClassifier(float threshold,
+                                          const std::string& positive_label,
+                                          const std::string& negative_label);
+
+  // Adds a MultiClassClassifier.
+  void AddOutputConfigForMultiClassClassifier(
+      const std::vector<std::string>& class_labels,
+      int top_k_outputs);
+
+  // Adds a BinnedClassifier.
+  void AddOutputConfigForBinnedClassifier(
+      const std::vector<std::pair<float, std::string>>& bins,
+      std::string underflow_label);
+
  private:
   const raw_ptr<proto::SegmentationModelMetadata> metadata_;
 };
diff --git a/components/segmentation_platform/internal/post_processor/post_processor.cc b/components/segmentation_platform/internal/post_processor/post_processor.cc
new file mode 100644
index 0000000..a0e821fb
--- /dev/null
+++ b/components/segmentation_platform/internal/post_processor/post_processor.cc
@@ -0,0 +1,89 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/segmentation_platform/internal/post_processor/post_processor.h"
+
+#include "base/check_op.h"
+#include "base/notreached.h"
+
+namespace segmentation_platform {
+
+std::vector<std::string> PostProcessor::GetClassifierResults(
+    const proto::PredictionResult& prediction_result) {
+  const std::vector<float> model_scores(prediction_result.result().begin(),
+                                        prediction_result.result().end());
+  const proto::Predictor& predictor =
+      prediction_result.output_config().predictor();
+  switch (predictor.PredictorType_case()) {
+    case proto::Predictor::kBinaryClassifier:
+      return GetBinaryClassifierResults(model_scores,
+                                        predictor.binary_classifier());
+    case proto::Predictor::kMultiClassClassifier:
+      return GetMultiClassClassifierResults(model_scores,
+                                            predictor.multi_class_classifier());
+    case proto::Predictor::kBinnedClassifier:
+      return GetBinnedClassifierResults(model_scores,
+                                        predictor.binned_classifier());
+    default:
+      NOTREACHED();
+      return std::vector<std::string>();
+  }
+}
+
+std::vector<std::string> PostProcessor::GetBinaryClassifierResults(
+    const std::vector<float>& model_scores,
+    const proto::Predictor::BinaryClassifier& binary_classifier) const {
+  DCHECK_EQ(1u, model_scores.size());
+
+  const std::string& winning_label =
+      (model_scores[0] >= binary_classifier.threshold()
+           ? binary_classifier.positive_label()
+           : binary_classifier.negative_label());
+  return std::vector<std::string>(1, winning_label);
+}
+
+std::vector<std::string> PostProcessor::GetMultiClassClassifierResults(
+    const std::vector<float>& model_scores,
+    const proto::Predictor::MultiClassClassifier& multi_class_classifier)
+    const {
+  DCHECK_EQ(static_cast<int>(model_scores.size()),
+            multi_class_classifier.class_labels_size());
+
+  std::vector<std::pair<std::string, float>> labeled_results;
+  for (int index = 0; index < static_cast<int>(model_scores.size()); index++) {
+    labeled_results.emplace_back(multi_class_classifier.class_labels(index),
+                                 model_scores[index]);
+  }
+  // Sort the labels in descending order of score.
+  std::sort(labeled_results.begin(), labeled_results.end(),
+            [](const std::pair<std::string, float>& a,
+               const std::pair<std::string, float>& b) {
+              return a.second > b.second;
+            });
+
+  int top_k_outputs = multi_class_classifier.top_k_outputs();
+  std::vector<std::string> top_k_output_labels;
+  for (int index = 0; index < top_k_outputs; index++) {
+    top_k_output_labels.emplace_back(labeled_results[index].first);
+  }
+  return top_k_output_labels;
+}
+
+std::vector<std::string> PostProcessor::GetBinnedClassifierResults(
+    const std::vector<float>& model_scores,
+    const proto::Predictor::BinnedClassifier& binned_classifier) const {
+  DCHECK_EQ(1u, model_scores.size());
+  DCHECK_LE(1, binned_classifier.bins_size());
+
+  std::string winning_bin_label = binned_classifier.underflow_label();
+
+  for (int index = 0; index < binned_classifier.bins_size(); index++) {
+    if (model_scores[0] >= binned_classifier.bins(index).min_range()) {
+      winning_bin_label = binned_classifier.bins(index).label();
+    }
+  }
+  return std::vector<std::string>(1, winning_bin_label);
+}
+
+}  // namespace segmentation_platform
\ No newline at end of file
diff --git a/components/segmentation_platform/internal/post_processor/post_processor.h b/components/segmentation_platform/internal/post_processor/post_processor.h
new file mode 100644
index 0000000..ef3d513
--- /dev/null
+++ b/components/segmentation_platform/internal/post_processor/post_processor.h
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_POST_PROCESSOR_POST_PROCESSOR_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_POST_PROCESSOR_POST_PROCESSOR_H_
+
+#include <string>
+#include <vector>
+
+#include "components/segmentation_platform/public/proto/output_config.pb.h"
+#include "components/segmentation_platform/public/proto/prediction_result.pb.h"
+
+namespace segmentation_platform {
+
+// Handles post processing of model evaluation results.
+// Postprocessing layer gives the result to the client based on the predictor
+// they supplied in the config.
+class PostProcessor {
+ public:
+  PostProcessor() = default;
+  ~PostProcessor() = default;
+
+  // Disallow copy/assign.
+  PostProcessor(const PostProcessor&) = delete;
+  PostProcessor& operator=(const PostProcessor&) = delete;
+
+  // Called when the result from model execution are ready. Gives list of
+  // ordered `output_labels` based on the classifier given by the client in the
+  // OutputConfig.
+  std::vector<std::string> GetClassifierResults(
+      const proto::PredictionResult& prediction_result);
+
+ private:
+  std::vector<std::string> GetBinaryClassifierResults(
+      const std::vector<float>& model_scores,
+      const proto::Predictor::BinaryClassifier& binary_classifier) const;
+
+  std::vector<std::string> GetMultiClassClassifierResults(
+      const std::vector<float>& model_scores,
+      const proto::Predictor::MultiClassClassifier& multi_class_classifier)
+      const;
+
+  std::vector<std::string> GetBinnedClassifierResults(
+      const std::vector<float>& model_scores,
+      const proto::Predictor::BinnedClassifier& binned_classifier) const;
+};
+
+}  // namespace segmentation_platform
+
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_POST_PROCESSOR_POST_PROCESSOR_H_
\ No newline at end of file
diff --git a/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc b/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc
new file mode 100644
index 0000000..2729f9b5d
--- /dev/null
+++ b/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/segmentation_platform/internal/post_processor/post_processor.h"
+
+#include "components/segmentation_platform/internal/metadata/metadata_utils.h"
+#include "components/segmentation_platform/internal/metadata/metadata_writer.h"
+#include "components/segmentation_platform/public/proto/model_metadata.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace segmentation_platform {
+
+namespace {
+
+// Labels for BinaryClassifier.
+const char kNotShowShare[] = "Not Show Share";
+const char kShowShare[] = "Show Share";
+
+// Labels for MultiClassClassifier.
+const char kNewTabUser[] = "NewTab";
+const char kShareUser[] = "Share";
+const char kShoppingUser[] = "Shopping";
+const char kVoiceUser[] = "Voice";
+
+// Labels for BinnedClassifier.
+const char kLowUsed[] = "Low";
+const char kMediumUsed[] = "Medium";
+const char kHighUsed[] = "High";
+const char kUnderflowLabel[] = "Underflow";
+
+proto::OutputConfig GetTestOutputConfigForBinaryClassifier() {
+  proto::SegmentationModelMetadata model_metadata;
+  MetadataWriter writer(&model_metadata);
+
+  writer.AddOutputConfigForBinaryClassifier(
+      /*threshold=*/0.5, /*positive_label=*/kShowShare,
+      /*negative_label=*/kNotShowShare);
+
+  return model_metadata.output_config();
+}
+
+proto::OutputConfig GetTestOutputConfigForMultiClassClassifier(
+    int top_k_outputs) {
+  proto::SegmentationModelMetadata model_metadata;
+  MetadataWriter writer(&model_metadata);
+
+  writer.AddOutputConfigForMultiClassClassifier(
+      /*class_labels=*/{kShareUser, kNewTabUser, kVoiceUser, kShoppingUser},
+      top_k_outputs);
+  return model_metadata.output_config();
+}
+
+proto::OutputConfig GetTestOutputConfigForBinnedClassifier() {
+  proto::SegmentationModelMetadata model_metadata;
+  MetadataWriter writer(&model_metadata);
+  writer.AddOutputConfigForBinnedClassifier(
+      /*bins=*/{{0.2, kLowUsed}, {0.3, kMediumUsed}, {0.5, kHighUsed}},
+      kUnderflowLabel);
+  return model_metadata.output_config();
+}
+
+}  // namespace
+
+TEST(PostProcessorTest, BinaryClassifierScoreGreaterThanThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> selected_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.6}, GetTestOutputConfigForBinaryClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(selected_label, testing::ElementsAre(kShowShare));
+}
+
+TEST(PostProcessorTest, BinaryClassifierScoreGreaterEqualToThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> selected_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5}, GetTestOutputConfigForBinaryClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(selected_label, testing::ElementsAre(kShowShare));
+}
+
+TEST(PostProcessorTest, BinaryClassifierScoreGreaterLessThanThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> selected_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.4}, GetTestOutputConfigForBinaryClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(selected_label, testing::ElementsAre(kNotShowShare));
+}
+
+TEST(PostProcessorTest, MultiClassClassifierWithTopKLessThanElements) {
+  PostProcessor post_processor;
+  std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
+          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/2),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(top_k_labels, testing::ElementsAre(kShoppingUser, kShareUser));
+}
+
+TEST(PostProcessorTest, MultiClassClassifierWithTopKEqualToElements) {
+  PostProcessor post_processor;
+  std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
+          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/4),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(top_k_labels, testing::ElementsAre(kShoppingUser, kShareUser,
+                                                 kVoiceUser, kNewTabUser));
+}
+
+TEST(PostProcessorTest, BinnedClassifierScoreGreaterThanHighUserThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> winning_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.6}, GetTestOutputConfigForBinnedClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(winning_label, testing::ElementsAre(kHighUsed));
+}
+
+TEST(PostProcessorTest, BinnedClassifierScoreGreaterThanMediumUserThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> winning_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.4}, GetTestOutputConfigForBinnedClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(winning_label, testing::ElementsAre(kMediumUsed));
+}
+
+TEST(PostProcessorTest, BinnedClassifierScoreGreaterThanLowUserThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> winning_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.24}, GetTestOutputConfigForBinnedClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(winning_label, testing::ElementsAre(kLowUsed));
+}
+
+TEST(PostProcessorTest, BinnedClassifierScoreEqualToLowUserThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> winning_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.2}, GetTestOutputConfigForBinnedClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(winning_label, testing::ElementsAre(kLowUsed));
+}
+
+TEST(PostProcessorTest, BinnedClassifierScoreLessThanLowUserThreshold) {
+  PostProcessor post_processor;
+  std::vector<std::string> winning_label = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.1}, GetTestOutputConfigForBinnedClassifier(),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(winning_label, testing::ElementsAre(kUnderflowLabel));
+}
+
+}  // namespace segmentation_platform
\ No newline at end of file
diff --git a/components/segmentation_platform/internal/scheduler/execution_service.h b/components/segmentation_platform/internal/scheduler/execution_service.h
index a8bb012..4dda5831 100644
--- a/components/segmentation_platform/internal/scheduler/execution_service.h
+++ b/components/segmentation_platform/internal/scheduler/execution_service.h
@@ -39,8 +39,8 @@
   ExecutionService();
   ~ExecutionService();
 
-  ExecutionService(ExecutionService&) = delete;
-  ExecutionService& operator=(ExecutionService&) = delete;
+  ExecutionService(const ExecutionService&) = delete;
+  ExecutionService& operator=(const ExecutionService&) = delete;
 
   void InitForTesting(
       std::unique_ptr<processing::FeatureListQueryProcessor> feature_processor,
diff --git a/components/segmentation_platform/internal/selection/experimental_group_recorder.h b/components/segmentation_platform/internal/selection/experimental_group_recorder.h
index e7c8fe1..9c02f31e 100644
--- a/components/segmentation_platform/internal/selection/experimental_group_recorder.h
+++ b/components/segmentation_platform/internal/selection/experimental_group_recorder.h
@@ -29,8 +29,9 @@
                             proto::SegmentId segment_id);
   ~ExperimentalGroupRecorder();
 
-  ExperimentalGroupRecorder(ExperimentalGroupRecorder&) = delete;
-  ExperimentalGroupRecorder& operator=(ExperimentalGroupRecorder&) = delete;
+  ExperimentalGroupRecorder(const ExperimentalGroupRecorder&) = delete;
+  ExperimentalGroupRecorder& operator=(const ExperimentalGroupRecorder&) =
+      delete;
 
  private:
   void OnGetSegment(
diff --git a/components/segmentation_platform/internal/selection/segment_result_provider.cc b/components/segmentation_platform/internal/selection/segment_result_provider.cc
index 1b7b87b..944b0fdf 100644
--- a/components/segmentation_platform/internal/selection/segment_result_provider.cc
+++ b/components/segmentation_platform/internal/selection/segment_result_provider.cc
@@ -66,8 +66,9 @@
 
   void GetSegmentResult(std::unique_ptr<GetResultOptions> options) override;
 
-  SegmentResultProviderImpl(SegmentResultProviderImpl&) = delete;
-  SegmentResultProviderImpl& operator=(SegmentResultProviderImpl&) = delete;
+  SegmentResultProviderImpl(const SegmentResultProviderImpl&) = delete;
+  SegmentResultProviderImpl& operator=(const SegmentResultProviderImpl&) =
+      delete;
 
  private:
   struct RequestState {
diff --git a/components/segmentation_platform/internal/selection/segment_result_provider.h b/components/segmentation_platform/internal/selection/segment_result_provider.h
index eb3945d..2cd80987 100644
--- a/components/segmentation_platform/internal/selection/segment_result_provider.h
+++ b/components/segmentation_platform/internal/selection/segment_result_provider.h
@@ -53,8 +53,8 @@
                   float rank,
                   std::unique_ptr<ModelExecutionResult> execution_result);
     ~SegmentResult();
-    SegmentResult(SegmentResult&) = delete;
-    SegmentResult& operator=(SegmentResult&) = delete;
+    SegmentResult(const SegmentResult&) = delete;
+    SegmentResult& operator=(const SegmentResult&) = delete;
 
     ResultState state = ResultState::kUnknown;
     absl::optional<float> rank;
diff --git a/components/segmentation_platform/internal/signals/history_delegate_impl.h b/components/segmentation_platform/internal/signals/history_delegate_impl.h
index aee45bf..1c9ba4e 100644
--- a/components/segmentation_platform/internal/signals/history_delegate_impl.h
+++ b/components/segmentation_platform/internal/signals/history_delegate_impl.h
@@ -31,8 +31,8 @@
                       UrlSignalHandler* url_signal_handler);
 
   ~HistoryDelegateImpl() override;
-  HistoryDelegateImpl(HistoryDelegateImpl&) = delete;
-  HistoryDelegateImpl& operator=(HistoryDelegateImpl&) = delete;
+  HistoryDelegateImpl(const HistoryDelegateImpl&) = delete;
+  HistoryDelegateImpl& operator=(const HistoryDelegateImpl&) = delete;
 
   // Called by history observer when URLs are added/removed in the history
   // database, useful to store a cache of recent visits.
diff --git a/components/segmentation_platform/internal/signals/history_service_observer.h b/components/segmentation_platform/internal/signals/history_service_observer.h
index aecab9e..dbf4b0a 100644
--- a/components/segmentation_platform/internal/signals/history_service_observer.h
+++ b/components/segmentation_platform/internal/signals/history_service_observer.h
@@ -31,8 +31,8 @@
   HistoryServiceObserver();
   ~HistoryServiceObserver() override;
 
-  HistoryServiceObserver(HistoryServiceObserver&) = delete;
-  HistoryServiceObserver& operator=(HistoryServiceObserver&) = delete;
+  HistoryServiceObserver(const HistoryServiceObserver&) = delete;
+  HistoryServiceObserver& operator=(const HistoryServiceObserver&) = delete;
 
   // history::HistoryServiceObserver impl:
   void OnURLVisited(history::HistoryService* history_service,
diff --git a/components/segmentation_platform/internal/signals/signal_handler.h b/components/segmentation_platform/internal/signals/signal_handler.h
index de02de17..7f26a8a2 100644
--- a/components/segmentation_platform/internal/signals/signal_handler.h
+++ b/components/segmentation_platform/internal/signals/signal_handler.h
@@ -31,8 +31,8 @@
   SignalHandler();
   ~SignalHandler();
 
-  SignalHandler(SignalHandler&) = delete;
-  SignalHandler& operator=(SignalHandler&) = delete;
+  SignalHandler(const SignalHandler&) = delete;
+  SignalHandler& operator=(const SignalHandler&) = delete;
 
   void Initialize(StorageService* storage_service,
                   history::HistoryService* history_service,
diff --git a/components/segmentation_platform/internal/signals/ukm_config.h b/components/segmentation_platform/internal/signals/ukm_config.h
index 374751a2..bfa5018 100644
--- a/components/segmentation_platform/internal/signals/ukm_config.h
+++ b/components/segmentation_platform/internal/signals/ukm_config.h
@@ -22,8 +22,8 @@
   UkmConfig();
   ~UkmConfig();
 
-  UkmConfig(UkmConfig&) = delete;
-  UkmConfig& operator=(UkmConfig&) = delete;
+  UkmConfig(const UkmConfig&) = delete;
+  UkmConfig& operator=(const UkmConfig&) = delete;
 
   // Merge all the events from the given |config|. Returns whether new UKM
   // events were added to the current config as a result of merging.
diff --git a/components/segmentation_platform/internal/signals/ukm_observer.h b/components/segmentation_platform/internal/signals/ukm_observer.h
index 79ecd75..82588faf 100644
--- a/components/segmentation_platform/internal/signals/ukm_observer.h
+++ b/components/segmentation_platform/internal/signals/ukm_observer.h
@@ -28,8 +28,8 @@
   explicit UkmObserver(ukm::UkmRecorderImpl* ukm_recorder);
   ~UkmObserver() override;
 
-  UkmObserver(UkmObserver&) = delete;
-  UkmObserver& operator=(UkmObserver&) = delete;
+  UkmObserver(const UkmObserver&) = delete;
+  UkmObserver& operator=(const UkmObserver&) = delete;
 
   // Starts observing with the given |config| if not started. Otherwise, merges
   // the currently observed config with the new |config|, and observes a
diff --git a/components/segmentation_platform/internal/signals/url_signal_handler.h b/components/segmentation_platform/internal/signals/url_signal_handler.h
index 971e2ed..d12a2fdd 100644
--- a/components/segmentation_platform/internal/signals/url_signal_handler.h
+++ b/components/segmentation_platform/internal/signals/url_signal_handler.h
@@ -43,8 +43,8 @@
   explicit UrlSignalHandler(UkmDatabase* ukm_database);
   ~UrlSignalHandler();
 
-  UrlSignalHandler(UrlSignalHandler&) = delete;
-  UrlSignalHandler& operator=(UrlSignalHandler&) = delete;
+  UrlSignalHandler(const UrlSignalHandler&) = delete;
+  UrlSignalHandler& operator=(const UrlSignalHandler&) = delete;
 
   // Called by UKM observer when source URL for the |source_id| is updated.
   void OnUkmSourceUpdated(ukm::SourceId source_id,
diff --git a/components/segmentation_platform/internal/ukm_data_manager.h b/components/segmentation_platform/internal/ukm_data_manager.h
index b2c17c7..1e25155b 100644
--- a/components/segmentation_platform/internal/ukm_data_manager.h
+++ b/components/segmentation_platform/internal/ukm_data_manager.h
@@ -30,8 +30,8 @@
   UkmDataManager() = default;
   virtual ~UkmDataManager() = default;
 
-  UkmDataManager(UkmDataManager&) = delete;
-  UkmDataManager& operator=(UkmDataManager&) = delete;
+  UkmDataManager(const UkmDataManager&) = delete;
+  UkmDataManager& operator=(const UkmDataManager&) = delete;
 
   // Initializes UKM database and the observer of all UKM events.
   virtual void Initialize(const base::FilePath& database_path,
diff --git a/components/segmentation_platform/internal/ukm_data_manager_impl.h b/components/segmentation_platform/internal/ukm_data_manager_impl.h
index 6fbbcafd..6c07b10 100644
--- a/components/segmentation_platform/internal/ukm_data_manager_impl.h
+++ b/components/segmentation_platform/internal/ukm_data_manager_impl.h
@@ -24,8 +24,8 @@
   UkmDataManagerImpl();
   ~UkmDataManagerImpl() override;
 
-  UkmDataManagerImpl(UkmDataManagerImpl&) = delete;
-  UkmDataManagerImpl& operator=(UkmDataManagerImpl&) = delete;
+  UkmDataManagerImpl(const UkmDataManagerImpl&) = delete;
+  UkmDataManagerImpl& operator=(const UkmDataManagerImpl&) = delete;
 
   void InitializeForTesting(std::unique_ptr<UkmDatabase> ukm_database,
                             UkmObserver* ukm_observer);
diff --git a/components/segmentation_platform/public/field_trial_register.h b/components/segmentation_platform/public/field_trial_register.h
index cc33f8f..a5df6cf 100644
--- a/components/segmentation_platform/public/field_trial_register.h
+++ b/components/segmentation_platform/public/field_trial_register.h
@@ -17,8 +17,8 @@
   FieldTrialRegister() = default;
   virtual ~FieldTrialRegister() = default;
 
-  FieldTrialRegister(FieldTrialRegister&) = delete;
-  FieldTrialRegister& operator=(FieldTrialRegister&) = delete;
+  FieldTrialRegister(const FieldTrialRegister&) = delete;
+  FieldTrialRegister& operator=(const FieldTrialRegister&) = delete;
 
   // Records that the current session uses `trial_name` and `group_name` as
   // segmentation groups. Calling multiple times with same `trial_name`
diff --git a/components/segmentation_platform/public/input_context.h b/components/segmentation_platform/public/input_context.h
index b0426f4..9064273 100644
--- a/components/segmentation_platform/public/input_context.h
+++ b/components/segmentation_platform/public/input_context.h
@@ -17,8 +17,8 @@
  public:
   InputContext();
 
-  InputContext(InputContext&) = delete;
-  InputContext& operator=(InputContext&) = delete;
+  InputContext(const InputContext&) = delete;
+  InputContext& operator=(const InputContext&) = delete;
 
   // A list of params that can be used as input either directly to the model, or
   // to SQL queries, or custom input delegates. The exact mechanism and
diff --git a/components/segmentation_platform/public/input_delegate.h b/components/segmentation_platform/public/input_delegate.h
index b6155a83..34d62b6a 100644
--- a/components/segmentation_platform/public/input_delegate.h
+++ b/components/segmentation_platform/public/input_delegate.h
@@ -21,8 +21,8 @@
   InputDelegate();
   virtual ~InputDelegate();
 
-  InputDelegate(InputDelegate&) = delete;
-  InputDelegate& operator=(InputDelegate&) = delete;
+  InputDelegate(const InputDelegate&) = delete;
+  InputDelegate& operator=(const InputDelegate&) = delete;
 
   // Processes the given `input`, and returns the result via `callback`. Should
   // return an error if the processing failed. On success, the number of outputs
@@ -39,8 +39,8 @@
   InputDelegateHolder();
   ~InputDelegateHolder();
 
-  InputDelegateHolder(InputDelegateHolder&) = delete;
-  InputDelegateHolder& operator=(InputDelegateHolder&) = delete;
+  InputDelegateHolder(const InputDelegateHolder&) = delete;
+  InputDelegateHolder& operator=(const InputDelegateHolder&) = delete;
 
   // Returns a delegate for the `policy` if available or nullptr otherwise.
   InputDelegate* GetDelegate(proto::CustomInput::FillPolicy policy);
diff --git a/components/segmentation_platform/public/model_provider.h b/components/segmentation_platform/public/model_provider.h
index 4f1123e9..7d00705 100644
--- a/components/segmentation_platform/public/model_provider.h
+++ b/components/segmentation_platform/public/model_provider.h
@@ -30,8 +30,8 @@
   explicit ModelProvider(proto::SegmentId segment_id);
   virtual ~ModelProvider();
 
-  ModelProvider(ModelProvider&) = delete;
-  ModelProvider& operator=(ModelProvider&) = delete;
+  ModelProvider(const ModelProvider&) = delete;
+  ModelProvider& operator=(const ModelProvider&) = delete;
 
   // Implementation should return metadata that will be used to execute model.
   // The metadata provided should define the number of features needed by the
diff --git a/components/segmentation_platform/public/proto/output_config.proto b/components/segmentation_platform/public/proto/output_config.proto
index 55dda68b..b64ba2ec 100644
--- a/components/segmentation_platform/public/proto/output_config.proto
+++ b/components/segmentation_platform/public/proto/output_config.proto
@@ -12,9 +12,12 @@
 // Defines what type of model is supplied. Results are based on which
 // classifier/regressor the model is in.
 message Predictor {
-  // A classifier to interpret model results as a boolean.
+  // A classifier to interpret model results as a boolean. The final result
+  // supplied to the client `ordered_lables` of length one, with eithier
+  // `positive_label` or `negative label`.
   message BinaryClassifier {
-    // Limit to classify result as false/true.
+    // Model score below threshold is classified as `negative_label` else
+    // `positive_label`.
     optional float threshold = 1;
     // Labels for positive and negative outputs.
     optional string positive_label = 2;
@@ -22,18 +25,23 @@
   }
 
   // A classifier to interpret model results as one of multiple classes. Each
-  // output of the model corresponds to one of the classes.
+  // output of the model corresponds to one of the classes. The number of labels
+  // should be equal to the number of outputs from the model. The final result
+  // supplied to the client is list of `ordered_labels` of size `top_k_outputs`.
   message MultiClassClassifier {
     // Number of top results the client is interested in. Must be less than
     // or equal to the number of labels specified.
     optional int64 top_k_outputs = 1;
 
-    // Class labels associated with the outputs.
+    // Class labels associated with the outputs. The number of outputs from the
+    // model must be equal to the length of this list. Each label maps to the
+    // corresponding output index.
     repeated string class_labels = 2;
   }
 
   // A post-processor that converts a continuous model score into discrete
-  // ranges.
+  // ranges. The final result supplied to the client is one of the bin labels or
+  // `underflow_label`.
   message BinnedClassifier {
     // A bin represents a bucket containing entries between the current bin min
     // range to the next bin min range.
@@ -43,9 +51,15 @@
       // Signify what the bin corresponds to.
       optional string label = 2;
     }
-    // BinnedClassifier contains multiple bins.
-    // The result is one of the selected bins.
+    // BinnedClassifier contains multiple bins arranged in ascending order of
+    // min_range of the bins. The result is `ordered_labels` of length 1 with
+    // selected bin's label.
     repeated Bin bins = 1;
+
+    // In case the model score is less than the `min_range` of the first label,
+    // `underflow_label` is returned. If not supplied, then empty vector is
+    // returned.
+    optional string underflow_label = 2;
   }
 
   // Describes a regression model.
diff --git a/components/services/app_service/app_service_mojom_impl.cc b/components/services/app_service/app_service_mojom_impl.cc
index afca1fc..734d48f 100644
--- a/components/services/app_service/app_service_mojom_impl.cc
+++ b/components/services/app_service/app_service_mojom_impl.cc
@@ -79,62 +79,6 @@
   subscribers_.Add(std::move(subscriber));
 }
 
-void AppServiceMojomImpl::PauseApp(apps::mojom::AppType app_type,
-                                   const std::string& app_id) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->PauseApp(app_id);
-}
-
-void AppServiceMojomImpl::UnpauseApp(apps::mojom::AppType app_type,
-                                     const std::string& app_id) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->UnpauseApp(app_id);
-}
-
-void AppServiceMojomImpl::StopApp(apps::mojom::AppType app_type,
-                                  const std::string& app_id) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->StopApp(app_id);
-}
-
-void AppServiceMojomImpl::OpenNativeSettings(apps::mojom::AppType app_type,
-                                             const std::string& app_id) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->OpenNativeSettings(app_id);
-}
-
-void AppServiceMojomImpl::SetResizeLocked(apps::mojom::AppType app_type,
-                                          const std::string& app_id,
-                                          mojom::OptionalBool locked) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->SetResizeLocked(app_id, locked);
-}
-
-void AppServiceMojomImpl::SetWindowMode(apps::mojom::AppType app_type,
-                                        const std::string& app_id,
-                                        apps::mojom::WindowMode window_mode) {
-  auto iter = publishers_.find(app_type);
-  if (iter == publishers_.end()) {
-    return;
-  }
-  iter->second->SetWindowMode(app_id, window_mode);
-}
-
 void AppServiceMojomImpl::OnPublisherDisconnected(
     apps::mojom::AppType app_type) {
   publishers_.erase(app_type);
diff --git a/components/services/app_service/app_service_mojom_impl.h b/components/services/app_service/app_service_mojom_impl.h
index 5480cdb..17a84434 100644
--- a/components/services/app_service/app_service_mojom_impl.h
+++ b/components/services/app_service/app_service_mojom_impl.h
@@ -49,20 +49,6 @@
   void RegisterSubscriber(
       mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
       apps::mojom::ConnectOptionsPtr opts) override;
-  void PauseApp(apps::mojom::AppType app_type,
-                const std::string& app_id) override;
-  void UnpauseApp(apps::mojom::AppType app_type,
-                  const std::string& app_id) override;
-  void StopApp(apps::mojom::AppType app_type,
-               const std::string& app_id) override;
-  void OpenNativeSettings(apps::mojom::AppType app_type,
-                          const std::string& app_id) override;
-  void SetResizeLocked(apps::mojom::AppType app_type,
-                       const std::string& app_id,
-                       apps::mojom::OptionalBool locked) override;
-  void SetWindowMode(apps::mojom::AppType app_type,
-                     const std::string& app_id,
-                     apps::mojom::WindowMode window_mode) override;
 
  private:
   void OnPublisherDisconnected(apps::mojom::AppType app_type);
diff --git a/components/services/app_service/public/cpp/features.cc b/components/services/app_service/public/cpp/features.cc
index 23168e1..1293451 100644
--- a/components/services/app_service/public/cpp/features.cc
+++ b/components/services/app_service/public/cpp/features.cc
@@ -6,10 +6,6 @@
 
 namespace apps {
 
-BASE_FEATURE(kAppServiceWithoutMojom,
-             "AppServiceWithoutMojom",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kStopMojomAppService,
              "StopMojomAppService",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/services/app_service/public/cpp/features.h b/components/services/app_service/public/cpp/features.h
index e86ddeb..f9a23281 100644
--- a/components/services/app_service/public/cpp/features.h
+++ b/components/services/app_service/public/cpp/features.h
@@ -10,7 +10,6 @@
 
 namespace apps {
 
-COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kAppServiceWithoutMojom);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kStopMojomAppService);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kUnifiedAppServiceIconLoading);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kAppServiceStorage);
diff --git a/components/services/app_service/public/cpp/icon_types.h b/components/services/app_service/public/cpp/icon_types.h
index 0afe0a6..084a2e37 100644
--- a/components/services/app_service/public/cpp/icon_types.h
+++ b/components/services/app_service/public/cpp/icon_types.h
@@ -96,6 +96,11 @@
   // PNG-encoded bytes for the icon
   std::vector<uint8_t> compressed;
 
+  // Specifies whether the icon provided is a maskable icon. This field should
+  // only be true if the icon type is kCompressed, and the compressed icon data
+  // is from a maskable icon.
+  bool is_maskable_icon = false;
+
   // Specifies whether the icon provided is a placeholder. That field should
   // only be true if the corresponding `LoadIcon` call had
   // `allow_placeholder_icon` set to true, which states whether the caller will
diff --git a/components/services/app_service/public/cpp/publisher_base.cc b/components/services/app_service/public/cpp/publisher_base.cc
index b6a587e..484ebe7 100644
--- a/components/services/app_service/public/cpp/publisher_base.cc
+++ b/components/services/app_service/public/cpp/publisher_base.cc
@@ -71,30 +71,4 @@
   }
 }
 
-void PublisherBase::PauseApp(const std::string& app_id) {
-  NOTIMPLEMENTED();
-}
-
-void PublisherBase::UnpauseApp(const std::string& app_id) {
-  NOTIMPLEMENTED();
-}
-
-void PublisherBase::StopApp(const std::string& app_id) {
-  NOTIMPLEMENTED();
-}
-
-void PublisherBase::OpenNativeSettings(const std::string& app_id) {
-  NOTIMPLEMENTED();
-}
-
-void PublisherBase::SetResizeLocked(const std::string& app_id,
-                                    apps::mojom::OptionalBool locked) {
-  NOTIMPLEMENTED();
-}
-
-void PublisherBase::SetWindowMode(const std::string& app_id,
-                                  apps::mojom::WindowMode window_mode) {
-  NOTIMPLEMENTED();
-}
-
 }  // namespace apps
diff --git a/components/services/app_service/public/cpp/publisher_base.h b/components/services/app_service/public/cpp/publisher_base.h
index f45d269..1a94f0d 100644
--- a/components/services/app_service/public/cpp/publisher_base.h
+++ b/components/services/app_service/public/cpp/publisher_base.h
@@ -51,15 +51,6 @@
   mojo::Receiver<apps::mojom::Publisher>& receiver() { return receiver_; }
 
  private:
-  void PauseApp(const std::string& app_id) override;
-  void UnpauseApp(const std::string& app_id) override;
-  void StopApp(const std::string& app_id) override;
-  void OpenNativeSettings(const std::string& app_id) override;
-  void SetResizeLocked(const std::string& app_id,
-                       apps::mojom::OptionalBool locked) override;
-  void SetWindowMode(const std::string& app_id,
-                     apps::mojom::WindowMode window_mode) override;
-
   mojo::Receiver<apps::mojom::Publisher> receiver_{this};
 };
 
diff --git a/components/services/app_service/public/mojom/app_service.mojom b/components/services/app_service/public/mojom/app_service.mojom
index ede0701..0775488 100644
--- a/components/services/app_service/public/mojom/app_service.mojom
+++ b/components/services/app_service/public/mojom/app_service.mojom
@@ -24,92 +24,11 @@
   // Called by a consumer that wishes to know about available apps to register
   // itself with the App Service.
   RegisterSubscriber(pending_remote<Subscriber> subscriber, ConnectOptions? opts);
-
-  // Pauses an app to stop the current running app, and apply the icon effect.
-  PauseApp(
-      AppType app_type,
-      string app_id);
-
-  // Unpauses an app, and recover the icon effect for the app.
-  UnpauseApp(
-      AppType app_type,
-      string app_id);
-
-  // Stops the current running app for the given |app_id|.
-  StopApp(
-      AppType app_type,
-      string app_id);
-
-  // Opens native settings for the app with |app_id|.
-  OpenNativeSettings(
-      AppType app_type,
-      string app_id);
-
-  // Enables resize lock mode for the app identified by |app_id| with the given
-  // |app_type|.
-  SetResizeLocked(
-      AppType app_type,
-      string app_id,
-      OptionalBool locked);
-
-  // Set the window display mode for the app identified by |app_id|.
-  SetWindowMode(
-      AppType app_type,
-      string app_id,
-      WindowMode window_mode);
 };
 
 interface Publisher {
   // App Registry methods.
   Connect(pending_remote<Subscriber> subscriber, ConnectOptions? opts);
-
-  // Requests that the app identified by |app_id| is marked as paused. Paused
-  // apps cannot be launched. Implemented if the publisher supports the pausing
-  // of apps, and otherwise should do nothing.
-  //
-  // Publishers are expected to update the app icon when it is paused to apply
-  // the kPaused icon effect. Nothing should happen if an already paused app
-  // is paused again.
-  PauseApp(
-      string app_id);
-
-  // Requests that the app identified by |app_id| is unpaused. Implemented if
-  // the publisher supports the pausing of apps, and otherwise should do
-  // nothing.
-  //
-  // Publishers are expected to update the app icon to remove the kPaused
-  // icon effect. Nothing should happen if an unpaused app is unpaused again.
-  UnpauseApp(
-      string app_id);
-
-  // Stops all running instances of |app_id|.
-  StopApp(
-      string app_id);
-
-  // Opens the platform-specific settings page for the app identified by
-  // |app_id|, e.g. the Android Settings app for an ARC app, or the Chrome
-  // browser settings for a web app. Implemented if those settings exist and
-  // need to be accessible to users. Note this is not the same as the Chrome
-  // OS-wide App Management page, which should be used by default. This method
-  // should only be used in cases where settings must be accessed that are not
-  // available in App Management.
-  OpenNativeSettings(
-      string app_id);
-
-  // Enables resize lock mode for the app identified by |app_id|. When |locked|
-  // is kTrue, this means the app cannot be resized and is locked to a certain
-  // set of dimensions. Implemented if the publisher supports resize locking of
-  // apps, and otherwise should do nothing.
-  SetResizeLocked(
-      string app_id,
-      OptionalBool locked);
-
-  // Set the window display mode for the app identified by |app_id|. Implemented
-  // if the publisher supports changing the window mode of apps, and otherwise
-  // should do nothing.
-  SetWindowMode(
-      string app_id,
-      WindowMode window_mode);
 };
 
 // Subscriber works as a proxy, to receive a stream of apps from publishers,
diff --git a/components/test/components_test_suite.cc b/components/test/components_test_suite.cc
index 20d68eea..3ae610e 100644
--- a/components/test/components_test_suite.cc
+++ b/components/test/components_test_suite.cc
@@ -125,8 +125,6 @@
 
   void OnTestEnd(const testing::TestInfo& test_info) override {
     breadcrumbs::BreadcrumbManager::GetInstance().ResetForTesting();
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
 #if BUILDFLAG(IS_IOS)
     ios_initializer_.reset();
 #endif
diff --git a/components/viz/test/test_raster_interface.h b/components/viz/test/test_raster_interface.h
index ec3fab3..b31d8f7b 100644
--- a/components/viz/test/test_raster_interface.h
+++ b/components/viz/test/test_raster_interface.h
@@ -127,6 +127,7 @@
       const gpu::Mailbox& source_mailbox,
       GLenum source_target,
       GrSurfaceOrigin source_origin,
+      const gfx::Size& source_size,
       const gfx::Point& source_starting_point,
       const SkImageInfo& dst_info,
       GLuint dst_row_bytes,
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index f2137fa0..98cca66 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -663,12 +663,12 @@
   EXPECT_FALSE(position->AtEndOfAXTree());
   ui::AXNodePosition::AXPositionInstance test_position =
       position->CreatePositionAtStartOfAXTree();
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 0);
   EXPECT_TRUE(test_position->AtStartOfAXTree());
   EXPECT_FALSE(test_position->AtEndOfAXTree());
   test_position = position->CreatePositionAtEndOfAXTree();
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 17);
   EXPECT_FALSE(test_position->AtStartOfAXTree());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
@@ -676,16 +676,16 @@
   // Test inside iframe.
   position = text_in_iframe->CreateTextPositionAt(3);
   EXPECT_EQ(position->text_offset(), 3);
-  EXPECT_NE(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_NE(test_position->tree_id(), position->tree_id());
   EXPECT_FALSE(position->AtStartOfAXTree());
   EXPECT_FALSE(position->AtEndOfAXTree());
   test_position = position->CreatePositionAtStartOfAXTree();
   EXPECT_TRUE(test_position->AtStartOfAXTree());
   EXPECT_FALSE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 0);
   test_position = position->CreatePositionAtEndOfAXTree();
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 14);
   EXPECT_FALSE(test_position->AtStartOfAXTree());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
@@ -694,14 +694,14 @@
   position = text_after_iframe->CreateTextPositionAt(3);
   EXPECT_FALSE(position->AtStartOfAXTree());
   EXPECT_FALSE(position->AtEndOfAXTree());
-  EXPECT_NE(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_NE(test_position->tree_id(), position->tree_id());
   test_position = position->CreatePositionAtStartOfAXTree();
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 0);
   EXPECT_TRUE(test_position->AtStartOfAXTree());
   EXPECT_FALSE(test_position->AtEndOfAXTree());
   test_position = position->CreatePositionAtEndOfAXTree();
-  EXPECT_EQ(test_position->GetTreeID(), position->GetTreeID());
+  EXPECT_EQ(test_position->tree_id(), position->tree_id());
   EXPECT_EQ(test_position->text_offset(), 17);
   EXPECT_FALSE(test_position->AtStartOfAXTree());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index d43d16a6..efaf311 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -333,7 +333,7 @@
     const download::DownloadCreateInfo& info) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (base::Contains(downloads_, id))
+  if (base::Contains(downloads_by_guid_, info.guid))
     return nullptr;
 
   download::DownloadItemImpl* download =
@@ -560,11 +560,14 @@
   // dangerous downloads which will remain in history if they aren't explicitly
   // accepted or discarded. Canceling will remove the intermediate download
   // file.
-  for (const auto& it : downloads_) {
-    download::DownloadItemImpl* download = it.second.get();
-    if (download->GetState() == download::DownloadItem::IN_PROGRESS)
+  for (const auto& it : downloads_by_guid_) {
+    download::DownloadItemImpl* download = it.second;
+    if (download != nullptr &&
+        download->GetState() == download::DownloadItem::IN_PROGRESS) {
       download->Cancel(false);
+    }
   }
+
   downloads_.clear();
   downloads_by_guid_.clear();
 
@@ -771,9 +774,10 @@
 
 void DownloadManagerImpl::CheckForHistoryFilesRemoval() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  for (const auto& it : downloads_) {
-    download::DownloadItemImpl* item = it.second.get();
-    CheckForFileRemoval(item);
+  for (const auto& it : downloads_by_guid_) {
+    download::DownloadItemImpl* item = it.second;
+    if (item != nullptr)
+      CheckForFileRemoval(item);
   }
 }
 
@@ -797,26 +801,26 @@
 
   // Check whether an task is already queued or running for the current download
   // and skip this check if it is the case.
-  if (!pending_disk_access_query_.insert(download_item->GetId()).second)
+  if (!pending_disk_access_query_.insert(download_item->GetGuid()).second)
     return;
 
   disk_access_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&base::PathExists, download_item->GetTargetFilePath()),
       base::BindOnce(&DownloadManagerImpl::OnFileExistenceChecked,
-                     weak_factory_.GetWeakPtr(), download_item->GetId()));
+                     weak_factory_.GetWeakPtr(), download_item->GetGuid()));
 }
 
-void DownloadManagerImpl::OnFileExistenceChecked(uint32_t download_id,
+void DownloadManagerImpl::OnFileExistenceChecked(const std::string& guid,
                                                  bool result) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // Remove the pending check flag for this download to allow new requests.
-  pending_disk_access_query_.erase(download_id);
+  pending_disk_access_query_.erase(guid);
 
   if (!result) {  // File does not exist.
-    auto it = downloads_.find(download_id);
-    if (it != downloads_.end())
+    auto it = downloads_by_guid_.find(guid);
+    if (it != downloads_by_guid_.end())
       it->second->OnDownloadedFileRemoved();
   }
 }
@@ -970,14 +974,15 @@
     base::Time remove_begin,
     base::Time remove_end) {
   int count = 0;
-  auto it = downloads_.begin();
-  while (it != downloads_.end()) {
-    download::DownloadItemImpl* download = it->second.get();
+  auto it = downloads_by_guid_.begin();
+  while (it != downloads_by_guid_.end()) {
+    download::DownloadItemImpl* download = it->second;
 
     // Increment done here to protect against invalidation below.
     ++it;
 
-    if (download->GetState() != download::DownloadItem::IN_PROGRESS &&
+    if (download != nullptr &&
+        download->GetState() != download::DownloadItem::IN_PROGRESS &&
         url_filter.Run(download->GetURL()) &&
         download->GetStartTime() >= remove_begin &&
         (remove_end.is_null() || download->GetStartTime() < remove_end)) {
@@ -1199,7 +1204,7 @@
   for (auto& observer : observers_)
     observer.OnManagerInitialized();
   size_t size = 0;
-  for (const auto& it : downloads_)
+  for (const auto& it : downloads_by_guid_)
     size += it.second->GetApproximateMemoryUsage();
   if (!IsOffTheRecord() && size > 0)
     download::RecordDownloadManagerMemoryUsage(size);
@@ -1211,8 +1216,9 @@
 
 int DownloadManagerImpl::InProgressCount() {
   int count = 0;
-  for (const auto& it : downloads_) {
-    if (it.second->GetState() == download::DownloadItem::IN_PROGRESS)
+  for (const auto& it : downloads_by_guid_) {
+    if (it.second != nullptr &&
+        it.second->GetState() == download::DownloadItem::IN_PROGRESS)
       ++count;
   }
   return count;
@@ -1220,23 +1226,26 @@
 
 int DownloadManagerImpl::NonMaliciousInProgressCount() {
   int count = 0;
-  for (const auto& it : downloads_) {
-    if (it.second->IsTransient())
-      continue;
-    if (it.second->GetState() == download::DownloadItem::IN_PROGRESS &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS &&
-        it.second->GetDangerType() !=
-            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE) {
-      ++count;
+  for (const auto& it : downloads_by_guid_) {
+    download::DownloadItemImpl* download = it.second;
+    if (download != nullptr) {
+      if (download->IsTransient())
+        continue;
+      if (download->GetState() == download::DownloadItem::IN_PROGRESS &&
+          download->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL &&
+          download->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT &&
+          download->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST &&
+          download->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED &&
+          download->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS &&
+          it.second->GetDangerType() !=
+              download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE) {
+        ++count;
+      }
     }
   }
   return count;
@@ -1261,8 +1270,9 @@
 
 void DownloadManagerImpl::GetAllDownloads(
     download::SimpleDownloadManager::DownloadVector* downloads) {
-  for (const auto& it : downloads_)
-    downloads->push_back(it.second.get());
+  for (const auto& it : downloads_by_guid_)
+    if (it.second != nullptr)
+      downloads->push_back(it.second);
 }
 
 void DownloadManagerImpl::GetUninitializedActiveDownloadsIfAny(
@@ -1273,9 +1283,10 @@
 
 void DownloadManagerImpl::OpenDownload(download::DownloadItemImpl* download) {
   int num_unopened = 0;
-  for (const auto& it : downloads_) {
-    download::DownloadItemImpl* item = it.second.get();
-    if ((item->GetState() == download::DownloadItem::COMPLETE) &&
+  for (const auto& it : downloads_by_guid_) {
+    download::DownloadItemImpl* item = it.second;
+    if (item != nullptr &&
+        (item->GetState() == download::DownloadItem::COMPLETE) &&
         !item->GetOpened())
       ++num_unopened;
   }
diff --git a/content/browser/download/download_manager_impl.h b/content/browser/download/download_manager_impl.h
index 95efa60..fd7b9954 100644
--- a/content/browser/download/download_manager_impl.h
+++ b/content/browser/download/download_manager_impl.h
@@ -238,7 +238,7 @@
 
   // Called with the result of CheckForFileExistence. Updates the state of the
   // file and then notifies this update to the file's observer.
-  void OnFileExistenceChecked(uint32_t download_id, bool result);
+  void OnFileExistenceChecked(const std::string& guid, bool result);
 
   // Overridden from DownloadItemImplDelegate
   void DetermineDownloadTarget(download::DownloadItemImpl* item,
@@ -392,7 +392,7 @@
   const scoped_refptr<base::SequencedTaskRunner> disk_access_task_runner_;
 
   // DownloadItem for which a query is queued in the |disk_access_task_runner_|.
-  std::set<uint32_t> pending_disk_access_query_;
+  std::set<std::string> pending_disk_access_query_;
 
   base::WeakPtrFactory<DownloadManagerImpl> weak_factory_{this};
 };
diff --git a/content/browser/font_access/font_enumeration_data_source_fuchsia.cc b/content/browser/font_access/font_enumeration_data_source_fuchsia.cc
index be15b5235..924fee31 100644
--- a/content/browser/font_access/font_enumeration_data_source_fuchsia.cc
+++ b/content/browser/font_access/font_enumeration_data_source_fuchsia.cc
@@ -43,7 +43,8 @@
     const std::string& locale) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  NOTIMPLEMENTED_LOG_ONCE() << "Use a FIDL interface when available";
+  //  Use a FIDL interface when available.
+  NOTIMPLEMENTED_LOG_ONCE();
 
   blink::FontEnumerationTable font_enumeration_table;
 
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index d7347b6..2ed1d88 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -1444,6 +1444,49 @@
   EXPECT_NE(second_host_id, RenderFrameHost::kNoFrameTreeNodeId);
 }
 
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RetriggerPrerenderAfterRemoval) {
+  // Navigate to an initial page.
+  const GURL kInitialUrl = GetUrl("/title1.html");
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering.
+  const GURL kPrerenderingUrl = GetUrl("/title2.html");
+  {
+    test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl());
+    ASSERT_TRUE(ExecJs(web_contents_impl()->GetPrimaryMainFrame(),
+                       JsReplace(
+                           R"(
+                          let sc = document.createElement('script');
+                          sc.type = 'speculationrules';
+                          sc.textContent = JSON.stringify({
+                            prerender: [
+                              {source: "list", urls: [$1]}
+                            ]
+                          });
+                          document.head.appendChild(sc);
+                          )",
+                           kPrerenderingUrl)));
+    registry_observer.WaitForTrigger(kPrerenderingUrl);
+    int host_id = GetHostForUrl(kPrerenderingUrl);
+    ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
+    test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
+
+    // Remove the rules and check that the prerender is cancelled with an
+    // appropriate final status.
+    ASSERT_TRUE(ExecJs(
+        web_contents_impl()->GetPrimaryMainFrame(),
+        "document.querySelector('script[type=speculationrules]').remove()"));
+    host_observer.WaitForDestroyed();
+    EXPECT_EQ(GetHostForUrl(kPrerenderingUrl),
+              RenderFrameHost::kNoFrameTreeNodeId);
+  }
+  {
+    AddPrerender(kPrerenderingUrl);
+    int host_id = GetHostForUrl(kPrerenderingUrl);
+    EXPECT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
+  }
+}
+
 // Tests that prerendering triggered by prerendered pages is deferred until
 // activation.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderChain) {
@@ -3023,6 +3066,261 @@
   SetBrowserClientForTesting(old_browser_client);
 }
 
+// Test that a PrerenderHost triggered by speculation rules is canceled when
+// it times out in the background.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelPrerenderWhenTimeout) {
+  const GURL kInitialUrl = GetUrl("/empty.html");
+  const GURL kPrerenderUrl = GetUrl("/empty.html?prerender");
+
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  int host_id = AddPrerender(kPrerenderUrl);
+  test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
+
+  PrerenderHostRegistry* registry =
+      web_contents_impl()->GetPrerenderHostRegistry();
+
+  // The timers should not start yet when the prerendered page is in the
+  // foreground.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Inject mock time task runner.
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  registry->SetTaskRunnerForTesting(task_runner);
+
+  // Changing the visibility state to HIDDEN will not stop prerendering
+  // immediately, but start the timers.
+  web_contents()->WasHidden();
+  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  task_runner->FastForwardBy(
+      PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
+
+  prerender_observer.WaitForDestroyed();
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+      PrerenderFinalStatus::kTimeoutBackgrounded, 1);
+}
+
+// Test that multiple PrerenderHosts triggered by speculation rules are canceled
+// when it times out in the background.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       CancelMultiplePrerendersWhenTimeout) {
+  const GURL kInitialUrl = GetUrl("/empty.html");
+  const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
+  const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
+
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  AddPrerender(kPrerenderUrl1);
+  AddPrerender(kPrerenderUrl2);
+
+  test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
+                                                 kPrerenderUrl1);
+
+  PrerenderHostRegistry* registry =
+      web_contents_impl()->GetPrerenderHostRegistry();
+
+  // The timers should not start yet when the prerendered page is in the
+  // foreground.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Inject mock time task runner.
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  registry->SetTaskRunnerForTesting(task_runner);
+
+  // Changing the visibility state to HIDDEN will not stop prerendering
+  // immediately, but start the timers.
+  web_contents()->WasHidden();
+  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  task_runner->FastForwardBy(
+      PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
+
+  prerender_observer.WaitForDestroyed();
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+      PrerenderFinalStatus::kTimeoutBackgrounded, 2);
+}
+
+// Test that a PrerenderHost triggered by embedder is canceled when it times out
+// in the background.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       CancelOnlyEmbedderTriggeredPrerenderWhenTimeout) {
+  const GURL kInitialUrl = GetUrl("/empty.html");
+  const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
+  const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
+
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering by speculation rules.
+  AddPrerender(kPrerenderUrl1);
+
+  test::PrerenderHostObserver host_observer(*web_contents_impl(),
+                                            kPrerenderUrl2);
+  // Start prerendering by embedder.
+  std::unique_ptr<PrerenderHandle> prerender_handle =
+      web_contents_impl()->StartPrerendering(
+          kPrerenderUrl2, PrerenderTriggerType::kEmbedder,
+          "EmbedderSuffixForTest",
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+          nullptr);
+
+  PrerenderHostRegistry* registry =
+      web_contents_impl()->GetPrerenderHostRegistry();
+
+  // The timers should not start yet when the prerendered page is in the
+  // foreground.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Inject mock time task runner.
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  registry->SetTaskRunnerForTesting(task_runner);
+
+  // Changing the visibility state to HIDDEN will not stop prerendering
+  // immediately, but start the timers.
+  web_contents()->WasHidden();
+  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // PrerenderHost triggered by embedder should be destroyed and PrerenderHost
+  // triggered by speculation rules should be alive, since the timeout value
+  // differs depending on the trigger type.
+  ASSERT_GT(PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules,
+            PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
+  task_runner->FastForwardBy(
+      PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
+
+  host_observer.WaitForDestroyed();
+
+  // The timer for speculation rules is still running and PrerenderHost for
+  // speculation rules is alive.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+  EXPECT_NE(GetHostForUrl(kPrerenderUrl1), RenderFrameHost::kNoFrameTreeNodeId);
+
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
+      "EmbedderSuffixForTest",
+      PrerenderFinalStatus::kTimeoutBackgrounded, 1);
+  histogram_tester().ExpectBucketCount(
+      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+      PrerenderFinalStatus::kTimeoutBackgrounded, 0);
+}
+
+// Test that the timers for PrerenderHost timeout is reset when the tab gets
+// visible.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       TimerResetWhenHiddenPageGoBackToForeground) {
+  const GURL kInitialUrl = GetUrl("/empty.html");
+  const GURL kPrerenderUrl = GetUrl("/empty.html?prerender");
+
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+  AddPrerender(kPrerenderUrl);
+
+  PrerenderHostRegistry* registry =
+      web_contents_impl()->GetPrerenderHostRegistry();
+
+  // The timers should not start yet when the prerendered page is in the
+  // foreground.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Changing the visibility state to HIDDEN will not stop prerendering
+  // immediately, but start the timers.
+  web_contents()->WasHidden();
+  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // The timers should be reset when the hidden page goes back to the
+  // foreground.
+  web_contents()->WasShown();
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Activate the prerendered page.
+  test::PrerenderHostObserver prerender_observer(*web_contents(),
+                                                 GetHostForUrl(kPrerenderUrl));
+  NavigatePrimaryPage(kPrerenderUrl);
+  prerender_observer.WaitForActivation();
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderUrl);
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+      PrerenderFinalStatus::kActivated, 1);
+}
+
+// Test that a PrerenderHost in a triggered by speculation rules with
+// "target=_blank" are canceled when it times out in the background.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       CancelPrerenderWithTargetBlankWhenTimeout) {
+  const GURL kInitialUrl = GetUrl("/simple_links.html");
+  const GURL kPrerenderUrl = GetUrl("/title2.html");
+
+  // Navigate to an initial page which has a link to `kPrerenderUrl`.
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering `kPrerenderUrl`.
+  TestNavigationObserver nav_observer(kPrerenderUrl);
+  nav_observer.StartWatchingNewWebContents();
+  AddPrerenderWithTargetHintAsync(kPrerenderUrl, "_blank");
+  nav_observer.WaitForNavigationFinished();
+  EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderUrl);
+
+  PrerenderHost* prerender_host =
+      web_contents_impl()->GetPrerenderHostRegistry()->FindHostByUrlForTesting(
+          kPrerenderUrl);
+  ASSERT_TRUE(prerender_host);
+  auto* prerender_web_contents = WebContentsImpl::FromFrameTreeNode(
+      prerender_host->GetPrerenderFrameTree().root());
+  ASSERT_NE(prerender_web_contents, web_contents_impl());
+  ExpectWebContentsIsForNewTabPrerendering(*prerender_web_contents);
+
+  PrerenderHostRegistry* registry =
+      web_contents_impl()->GetPrerenderHostRegistry();
+
+  // The timers should not start yet when the prerendered page is in the
+  // foreground.
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  // Inject mock time task runner.
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  registry->SetTaskRunnerForTesting(task_runner);
+
+  test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
+                                                 kPrerenderUrl);
+
+  // Changing the visibility state to HIDDEN will not stop prerendering
+  // immediately, but start the timers.
+  web_contents()->WasHidden();
+  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+
+  task_runner->FastForwardBy(
+      PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
+
+  prerender_observer.WaitForDestroyed();
+  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
+  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+      PrerenderFinalStatus::kTimeoutBackgrounded, 1);
+
+  // The navigation occurred in a new WebContents, so the original WebContents
+  // should still be showing the initial trigger page.
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
+}
+
 enum class SSLPrerenderTestErrorBlockType { kClientCertRequested, kCertError };
 
 std::string SSLPrerenderTestErrorBlockTypeToString(
@@ -4868,219 +5166,6 @@
   EXPECT_EQ(web_contents()->GetLastCommittedURL(), initial_url);
 }
 
-// TODO(crbug.com/1356907): Remove this and merge it to PrerenderBrowserTest
-// once kPrerender2InBackground is enabled by default.
-class PrerenderHostRegistryInBackgroundTest : public PrerenderBrowserTest {
- public:
-  PrerenderHostRegistryInBackgroundTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {// Enable to run prerenderings in the background.
-         blink::features::kPrerender2InBackground},
-        // Disable the memory requirement of Prerender2 so the test can run on
-        // any bot.
-        {blink::features::kPrerender2MemoryControls});
-  }
-
-  ~PrerenderHostRegistryInBackgroundTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Test that a PrerenderHost triggered by speculation rules is canceled when
-// it times out in the background.
-IN_PROC_BROWSER_TEST_F(PrerenderHostRegistryInBackgroundTest,
-                       CancelPrerenderWhenTimeout) {
-  const GURL kInitialUrl = GetUrl("/empty.html");
-  const GURL kPrerenderUrl = GetUrl("/empty.html?prerender");
-
-  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
-
-  int host_id = AddPrerender(kPrerenderUrl);
-  test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
-
-  PrerenderHostRegistry* registry =
-      web_contents_impl()->GetPrerenderHostRegistry();
-
-  // The timers should not start yet when the prerendered page is in the
-  // foreground.
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // Inject mock time task runner.
-  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  registry->SetTaskRunnerForTesting(task_runner);
-
-  // Changing the visibility state to HIDDEN will not stop prerendering
-  // immediately, but start the timers.
-  web_contents()->WasHidden();
-  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  task_runner->FastForwardBy(
-      PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
-
-  prerender_observer.WaitForDestroyed();
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-  histogram_tester().ExpectUniqueSample(
-      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
-      PrerenderFinalStatus::kTimeoutBackgrounded, 1);
-}
-
-// Test that multiple PrerenderHosts triggered by speculation rules are canceled
-// when it times out in the background.
-IN_PROC_BROWSER_TEST_F(PrerenderHostRegistryInBackgroundTest,
-                       CancelMultiplePrerendersWhenTimeout) {
-  const GURL kInitialUrl = GetUrl("/empty.html");
-  const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
-  const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
-
-  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
-
-  AddPrerender(kPrerenderUrl1);
-  AddPrerender(kPrerenderUrl2);
-
-  test::PrerenderHostObserver prerender_observer(*web_contents_impl(),
-                                                 kPrerenderUrl1);
-
-  PrerenderHostRegistry* registry =
-      web_contents_impl()->GetPrerenderHostRegistry();
-
-  // The timers should not start yet when the prerendered page is in the
-  // foreground.
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // Inject mock time task runner.
-  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  registry->SetTaskRunnerForTesting(task_runner);
-
-  // Changing the visibility state to HIDDEN will not stop prerendering
-  // immediately, but start the timers.
-  web_contents()->WasHidden();
-  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  task_runner->FastForwardBy(
-      PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules);
-
-  prerender_observer.WaitForDestroyed();
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-  histogram_tester().ExpectUniqueSample(
-      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
-      PrerenderFinalStatus::kTimeoutBackgrounded, 2);
-}
-
-// Test that a PrerenderHost triggered by embedder is canceled when it times out
-// in the background.
-IN_PROC_BROWSER_TEST_F(PrerenderHostRegistryInBackgroundTest,
-                       CancelOnlyEmbedderTriggeredPrerenderWhenTimeout) {
-  const GURL kInitialUrl = GetUrl("/empty.html");
-  const GURL kPrerenderUrl1 = GetUrl("/empty.html?prerender1");
-  const GURL kPrerenderUrl2 = GetUrl("/empty.html?prerender2");
-
-  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
-
-  // Start prerendering by speculation rules.
-  AddPrerender(kPrerenderUrl1);
-
-  test::PrerenderHostObserver host_observer(*web_contents_impl(),
-                                            kPrerenderUrl2);
-  // Start prerendering by embedder.
-  std::unique_ptr<PrerenderHandle> prerender_handle =
-      web_contents_impl()->StartPrerendering(
-          kPrerenderUrl2, PrerenderTriggerType::kEmbedder,
-          "EmbedderSuffixForTest",
-          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
-                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
-          nullptr);
-
-  PrerenderHostRegistry* registry =
-      web_contents_impl()->GetPrerenderHostRegistry();
-
-  // The timers should not start yet when the prerendered page is in the
-  // foreground.
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // Inject mock time task runner.
-  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  registry->SetTaskRunnerForTesting(task_runner);
-
-  // Changing the visibility state to HIDDEN will not stop prerendering
-  // immediately, but start the timers.
-  web_contents()->WasHidden();
-  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // PrerenderHost triggered by embedder should be destroyed and PrerenderHost
-  // triggered by speculation rules should be alive, since the timeout value
-  // differs depending on the trigger type.
-  ASSERT_GT(PrerenderHostRegistry::kTimeToLiveInBackgroundForSpeculationRules,
-            PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
-  task_runner->FastForwardBy(
-      PrerenderHostRegistry::kTimeToLiveInBackgroundForEmbedder);
-
-  host_observer.WaitForDestroyed();
-
-  // The timer for speculation rules is still running and PrerenderHost for
-  // speculation rules is alive.
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-  EXPECT_NE(GetHostForUrl(kPrerenderUrl1), RenderFrameHost::kNoFrameTreeNodeId);
-
-  histogram_tester().ExpectUniqueSample(
-      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
-      "EmbedderSuffixForTest",
-      PrerenderFinalStatus::kTimeoutBackgrounded, 1);
-  histogram_tester().ExpectBucketCount(
-      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
-      PrerenderFinalStatus::kTimeoutBackgrounded, 0);
-}
-
-// Test that the timers for PrerenderHost timeout is reset when the tab gets
-// visible.
-IN_PROC_BROWSER_TEST_F(PrerenderHostRegistryInBackgroundTest,
-                       TimerResetWhenHiddenPageGoBackToForeground) {
-  const GURL kInitialUrl = GetUrl("/empty.html");
-  const GURL kPrerenderUrl = GetUrl("/empty.html?prerender");
-
-  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
-  AddPrerender(kPrerenderUrl);
-
-  PrerenderHostRegistry* registry =
-      web_contents_impl()->GetPrerenderHostRegistry();
-
-  // The timers should not start yet when the prerendered page is in the
-  // foreground.
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // Changing the visibility state to HIDDEN will not stop prerendering
-  // immediately, but start the timers.
-  web_contents()->WasHidden();
-  ASSERT_TRUE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_TRUE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // The timers should be reset when the hidden page goes back to the
-  // foreground.
-  web_contents()->WasShown();
-  ASSERT_FALSE(registry->GetEmbedderTimerForTesting()->IsRunning());
-  ASSERT_FALSE(registry->GetSpeculationRulesTimerForTesting()->IsRunning());
-
-  // Activate the prerendered page.
-  test::PrerenderHostObserver prerender_observer(*web_contents(),
-                                                 GetHostForUrl(kPrerenderUrl));
-  NavigatePrimaryPage(kPrerenderUrl);
-  prerender_observer.WaitForActivation();
-  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderUrl);
-  histogram_tester().ExpectUniqueSample(
-      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
-      PrerenderFinalStatus::kActivated, 1);
-}
-
 // TODO(crbug.com/1356907): Remove this and merge it to
 // PrerenderSequentialPrerenderingBrowserTest once kPrerender2InBackground is
 // enabled by default.
diff --git a/content/browser/preloading/prerenderer.h b/content/browser/preloading/prerenderer.h
index 0544e5b..af74d49 100644
--- a/content/browser/preloading/prerenderer.h
+++ b/content/browser/preloading/prerenderer.h
@@ -26,4 +26,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_PRELOADING_PRERENDERER_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_PRELOADING_PRERENDERER_H_
diff --git a/content/browser/preloading/prerenderer_impl.cc b/content/browser/preloading/prerenderer_impl.cc
index 6956695..eeb58ca2 100644
--- a/content/browser/preloading/prerenderer_impl.cc
+++ b/content/browser/preloading/prerenderer_impl.cc
@@ -147,7 +147,6 @@
       // which permit this prerender.
       if (matching_candidates.empty()) {
         removed_prerender_rules.push_back(prerender.prerender_host_id);
-        prerender.prerender_host_id = RenderFrameHost::kNoFrameTreeNodeId;
       }
     }
 
@@ -167,6 +166,25 @@
   registry_->CancelHosts(
       removed_prerender_rules,
       PrerenderCancellationReason(PrerenderFinalStatus::kTriggerDestroyed));
+  {
+    base::flat_set<int> removed_prerender_rules_set(
+        removed_prerender_rules.begin(), removed_prerender_rules.end());
+
+    // Remove the canceled entries so that the page can re-trigger prerendering.
+    // Here are two options: to remove the entries whose prerender_host_id is
+    // invalid, or to remove the entries whose prerender_host_id is in the
+    // removed list. Here we go with the latter, to ensure the prerender
+    // requests rejected by PrerenderHostRegistry can be filtered out. But
+    // ideally PrerenderHostRegistry should implement the history management
+    // mechanism by itself.
+    started_prerenders_.erase(
+        std::remove_if(started_prerenders_.begin(), started_prerenders_.end(),
+                       [&](const PrerenderInfo& x) {
+                         return base::Contains(removed_prerender_rules_set,
+                                               x.prerender_host_id);
+                       }),
+        started_prerenders_.end());
+  }
 
   // Actually start the candidates once the diffing is done.
   for (const auto& candidate : candidates_to_start) {
@@ -266,6 +284,16 @@
 
         // TODO(crbug.com/1350676): Observe PrerenderHost created for
         // prerendering in a new tab like the kNoHint and kSelf cases.
+        // TODO(crbug.com/1350676): Update the counter of
+        // `count_started_same_tab_prerenders_`. We cannot update this counter
+        // at this point because the counter is used to calculate the
+        // percentage of started prerenders that failed due to
+        // kMemoryLimitExceeded, which is done by observing PrerenderHosts,
+        // but this class cannot observe the started new-tab prerender at this
+        // moment. Rational: COUNT(kMemoryLimitExceeded && non-new-tab) /
+        // COUNT(non-new-tab) should be close to COUNT(kMemoryLimitExceeded) /
+        // COUNT(*), but COUNT(kMemoryLimitExceeded && non-new-tab) / COUNT(*)
+        // would follow another distribution.
         break;
       }
       // Handle the rule as kNoHint if the prerender-in-new-tab is not
@@ -279,7 +307,7 @@
       started_prerenders_.insert(end, {.url = candidate->url,
                                        .referrer = referrer,
                                        .prerender_host_id = prerender_host_id});
-
+      count_started_same_tab_prerenders_++;
       // Start to observe PrerenderHost to get the information about
       // FinalStatus.
       observers_.push_back(std::make_unique<PrerenderHostObserver>(
@@ -306,14 +334,13 @@
   // This function can be called twice and the histogram should be recorded in
   // the first call. Also, skip recording the histogram when no prerendering
   // starts.
-  if (started_prerenders_.empty()) {
+  if (count_started_same_tab_prerenders_ == 0) {
     DCHECK(observers_.empty());
     return;
   }
 
   // Record the percentage of destroyed prerenders due to the excessive memory
-  // usage. `started_prerenders_` can include destroyed prerenders by other
-  // reasons.
+  // usage.
   // The closer the value is to 0, the less prerenders are cancelled by
   // FinalStatus::kMemoryLimitExceeded. The result depends on Finch params
   // `max_num_of_running_speculation_rules` and
@@ -322,7 +349,7 @@
       "Prerender.Experimental.CancellationPercentageByExcessiveMemoryUsage."
       "SpeculationRule",
       GetNumberOfDestroyedByMemoryExceeded() * 100 /
-          started_prerenders_.size());
+          count_started_same_tab_prerenders_);
 
   if (registry_) {
     std::vector<int> started_prerender_ids;
@@ -335,6 +362,7 @@
   }
 
   started_prerenders_.clear();
+  count_started_same_tab_prerenders_ = 0;
   observers_.clear();
 }
 
diff --git a/content/browser/preloading/prerenderer_impl.h b/content/browser/preloading/prerenderer_impl.h
index d059e43..9557583 100644
--- a/content/browser/preloading/prerenderer_impl.h
+++ b/content/browser/preloading/prerenderer_impl.h
@@ -42,6 +42,12 @@
   // are updated.
   // This is kept sorted by URL.
   struct PrerenderInfo;
+
+  // Counts the historical non-new-tab prerenders.
+  // TODO(crbug.com/1350676): Observe PrerenderHost created for
+  // prerendering in a new tab so that this counter can take new-tab prerender
+  // into account.
+  int count_started_same_tab_prerenders_ = 0;
   std::vector<PrerenderInfo> started_prerenders_;
 
   base::WeakPtr<PrerenderHostRegistry> registry_;
@@ -56,4 +62,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_PRELOADING_PRERENDERER_IMPL_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_PRELOADING_PRERENDERER_IMPL_H_
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index bed3d68..ae3a092 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -2319,7 +2319,6 @@
   // `force_browsing_instance_swap` is true. All cases that result in an
   // unrelated SiteInstance should return Yes_ForceSwap or Yes_ProactiveSwap in
   // ShouldSwapBrowsingInstancesForNavigation.
-  NavigationControllerImpl& controller = GetNavigationController();
 
   // If the entry has an instance already we should usually use it, unless it is
   // no longer suitable.
@@ -2402,12 +2401,15 @@
     return SiteInstanceDescriptor(source_instance);
   }
 
+  DCHECK_EQ(GetNavigationController().GetBrowserContext(),
+            current_instance->GetBrowserContext());
+
   // If we haven't used our SiteInstance yet, then we can use it for this
-  // entry.  We won't commit the SiteInstance to this site until the response
-  // is received (in OnResponseStarted), unless the navigation entry was
-  // restored or it's a Web UI as described below.
-  // TODO(ahemery): In theory we should be able to go for an unused SiteInstance
-  // with the same web exposed isolation status.
+  // navigation.  We won't commit the SiteInstance to this site until the
+  // response is received (in OnResponseStarted), unless the navigation entry
+  // was restored or it's a Web UI as described below.
+  // TODO(ahemery): In theory we should be able to go for an unused
+  // SiteInstance with the same web exposed isolation status.
   if (!current_instance->HasSite() && !dest_url_info.IsIsolated() &&
       !current_instance->IsCrossOriginIsolated()) {
     // If we've already created a SiteInstance for our destination, we don't
@@ -2416,27 +2418,29 @@
     // want to compare against the current URL and not the SiteInstance's site.
     // In this case, there is no current URL, so comparing against the site is
     // ok.  See additional comments below.)
-    //
-    // Also, if the URL's site should use process-per-site mode and there is an
+    const SiteInfo dest_site_info =
+        current_instance->DeriveSiteInfo(dest_url_info);
+    if (current_instance->HasRelatedSiteInstance(dest_site_info)) {
+      AppendReason(reason,
+                   "DetermineSiteInstanceForURL / !current->HasSite / "
+                   "has-related-site-instance");
+      return SiteInstanceDescriptor(dest_url_info,
+                                    SiteInstanceRelation::RELATED);
+    }
+
+    // If the URL's site should use process-per-site mode and there is an
     // existing process for the site, we should use it.  We can call
     // GetRelatedSiteInstance() for this, which will eagerly set the site and
     // thus use the correct process.
-    DCHECK_EQ(controller.GetBrowserContext(),
-              current_instance->GetBrowserContext());
-
-    const SiteInfo dest_site_info =
-        current_instance->DeriveSiteInfo(dest_url_info);
     bool use_process_per_site =
         dest_site_info.ShouldUseProcessPerSite(
             current_instance->GetBrowserContext()) &&
         RenderProcessHostImpl::GetSoleProcessHostForSite(
             current_instance->GetIsolationContext(), dest_site_info);
-
-    if (current_instance->HasRelatedSiteInstance(dest_site_info) ||
-        use_process_per_site) {
+    if (use_process_per_site) {
       AppendReason(reason,
                    "DetermineSiteInstanceForURL / !current->HasSite / "
-                   "has-related-site-instance-or-using-process-per-site");
+                   "process-per-site");
       return SiteInstanceDescriptor(dest_url_info,
                                     SiteInstanceRelation::RELATED);
     }
@@ -2453,10 +2457,10 @@
                                     SiteInstanceRelation::RELATED);
     }
 
-    // Normally the "site" on the SiteInstance is set lazily when the load
-    // actually commits. This is to support better process sharing in case
-    // the site redirects to some other site: we want to use the destination
-    // site in the site instance.
+    // Normally the "site" on the SiteInstance is set lazily when the response
+    // is received and SiteInstance selection is finalized. This is to
+    // support better process sharing in case the site redirects to some other
+    // site: we want to use the destination site in the site instance.
     //
     // In the case of session restore, as it loads all the pages immediately
     // we need to set the site first, otherwise after a restore none of the
@@ -2479,7 +2483,8 @@
   // Use the current SiteInstance for same site navigations.
   if (is_same_site.Get(*render_frame_host_, dest_url_info)) {
     AppendReason(reason, "DetermineSiteInstanceForURL / same-site-navigation");
-    return SiteInstanceDescriptor(render_frame_host_->GetSiteInstance());
+    DCHECK_EQ(current_instance, render_frame_host_->GetSiteInstance());
+    return SiteInstanceDescriptor(current_instance);
   }
 
   // Shortcut some common cases for reusing an existing frame's SiteInstance.
@@ -2559,8 +2564,7 @@
   // BrowsingInstance, unless the destination URL's web-exposed isolated state
   // cannot be hosted by it.
   if (IsSiteInstanceCompatibleWithWebExposedIsolation(
-          render_frame_host_->GetSiteInstance(),
-          dest_url_info.web_exposed_isolation_info)) {
+          current_instance, dest_url_info.web_exposed_isolation_info)) {
     AppendReason(reason,
                  "DetermineSiteInstanceForURL / fallback / coop-compatible");
     return SiteInstanceDescriptor(dest_url_info, SiteInstanceRelation::RELATED);
@@ -4289,7 +4293,10 @@
     NotifyPrepareForInnerDelegateAttachComplete(false /* success */);
     return;
   }
-  DCHECK(!current_frame_host()->is_loading());
+  // Reset the loading state. Even though there should be no navigations in the
+  // injected frame, it might not have received a DidStopLoading call.
+  // See also https://crbug.com/1400157.
+  current_frame_host()->ResetLoadingState();
 
   DCHECK(!current_frame_host()->is_main_frame());
   if (current_frame_host()->GetSiteInstance() ==
diff --git a/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java b/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java
index 28690de..943bbd9 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java
@@ -156,7 +156,7 @@
         // If we're in doze mode (N+ devices), onConnectionTypeChanged may not
         // be called, but this function should. So update the connection type
         // if necessary.
-        broadcastNetworkChangeIfNecessary(mNotifier.getCurrentNetworkState().getConnectionType());
+        broadcastNetworkChangeIfNecessary(connectionType);
     }
 
     @Override
diff --git a/content/public/browser/preloading.h b/content/public/browser/preloading.h
index b892a24..f7cb32e6 100644
--- a/content/public/browser/preloading.h
+++ b/content/public/browser/preloading.h
@@ -154,6 +154,9 @@
   // for exceeding the renderer processes limit.
   kRendererProcessLimitExceeded = 13,
 
+  // Preloading was ineligible because the Battery Saver setting was enabled.
+  kBatterySaverEnabled = 14,
+
   // TODO(crbug.com/1309934): Add more specific ineligibility reasons subject to
   // each preloading operation
   // This constant is used to define the value from which embedders can add more
diff --git a/content/public/test/unittest_test_suite.cc b/content/public/test/unittest_test_suite.cc
index dad91bc..4c2495c 100644
--- a/content/public/test/unittest_test_suite.cc
+++ b/content/public/test/unittest_test_suite.cc
@@ -98,8 +98,6 @@
     ResetNetworkServiceForTesting();
 
     breadcrumbs::BreadcrumbManager::GetInstance().ResetForTesting();
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
   }
 
  private:
diff --git a/content/renderer/content_security_policy_util.cc b/content/renderer/content_security_policy_util.cc
index a02e54f..5cb6412 100644
--- a/content/renderer/content_security_policy_util.cc
+++ b/content/renderer/content_security_policy_util.cc
@@ -48,9 +48,10 @@
       std::move(sources), BuildVectorOfStrings(source_list.nonces),
       std::move(hashes), source_list.allow_self, source_list.allow_star,
       source_list.allow_response_redirects, source_list.allow_inline,
-      source_list.allow_eval, source_list.allow_wasm_eval,
-      source_list.allow_wasm_unsafe_eval, source_list.allow_dynamic,
-      source_list.allow_unsafe_hashes, source_list.report_sample);
+      source_list.allow_inline_speculation_rules, source_list.allow_eval,
+      source_list.allow_wasm_eval, source_list.allow_wasm_unsafe_eval,
+      source_list.allow_dynamic, source_list.allow_unsafe_hashes,
+      source_list.report_sample);
 }
 
 blink::WebVector<blink::WebString> ToWebVectorOfWebStrings(
@@ -91,6 +92,7 @@
           source_list->allow_star,
           source_list->allow_response_redirects,
           source_list->allow_inline,
+          source_list->allow_inline_speculation_rules,
           source_list->allow_eval,
           source_list->allow_wasm_eval,
           source_list->allow_wasm_unsafe_eval,
diff --git a/content/test/data/accessibility/html/button-with-listbox-popup-expected-fuchsia.txt b/content/test/data/accessibility/html/button-with-listbox-popup-expected-fuchsia.txt
index 77b0caeb..f7813778 100644
--- a/content/test/data/accessibility/html/button-with-listbox-popup-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/button-with-listbox-popup-expected-fuchsia.txt
@@ -9,19 +9,19 @@
 ++++++++++STATIC_TEXT label='Foo' actions='{DEFAULT}'
 ++++++++++++UNKNOWN label='Foo'
 ++++++++UNKNOWN focusable label='Choose one:' actions='{DEFAULT}'
-++++++++++UNKNOWN focusable label='Baz' actions='{DEFAULT}'
+++++++++++UNKNOWN label='Baz' actions='{DEFAULT}'
 ++++++++++++UNKNOWN hidden
 ++++++++++++++STATIC_TEXT label='%E2%80%A2 ' actions='{DEFAULT}'
 ++++++++++++++++UNKNOWN label='%E2%80%A2 '
 ++++++++++++STATIC_TEXT label='Baz' actions='{DEFAULT}'
 ++++++++++++++UNKNOWN label='Baz'
-++++++++++UNKNOWN focusable label='Bar' actions='{DEFAULT}'
+++++++++++UNKNOWN label='Bar' actions='{DEFAULT}'
 ++++++++++++UNKNOWN hidden
 ++++++++++++++STATIC_TEXT label='%E2%80%A2 ' actions='{DEFAULT}'
 ++++++++++++++++UNKNOWN label='%E2%80%A2 '
 ++++++++++++STATIC_TEXT label='Bar' actions='{DEFAULT}'
 ++++++++++++++UNKNOWN label='Bar'
-++++++++++UNKNOWN focusable label='Foo' actions='{DEFAULT}'
+++++++++++UNKNOWN label='Foo' actions='{DEFAULT}'
 ++++++++++++UNKNOWN hidden
 ++++++++++++++STATIC_TEXT label='%E2%80%A2 ' actions='{DEFAULT}'
 ++++++++++++++++UNKNOWN label='%E2%80%A2 '
diff --git a/content/test/data/accessibility/html/frameset-expected-fuchsia.txt b/content/test/data/accessibility/html/frameset-expected-fuchsia.txt
index 0e55a03..0ec9831 100644
--- a/content/test/data/accessibility/html/frameset-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/frameset-expected-fuchsia.txt
@@ -1,7 +1,7 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN hidden
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
 ++++++++++++UNKNOWN hidden
@@ -18,7 +18,7 @@
 ++++++++++++++++++++UNKNOWN label='Chrome'
 ++++++++++++++++STATIC_TEXT label='!'
 ++++++++++++++++++UNKNOWN label='!'
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
 ++++++++++++UNKNOWN hidden
@@ -29,4 +29,4 @@
 ++++++++++++++++++STATIC_TEXT label='mark tag'
 ++++++++++++++++++++UNKNOWN label='mark tag'
 ++++++++++++++++STATIC_TEXT label='.'
-++++++++++++++++++UNKNOWN label='.'
\ No newline at end of file
+++++++++++++++++++UNKNOWN label='.'
diff --git a/content/test/data/accessibility/html/iframe-coordinates-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-coordinates-expected-fuchsia.txt
index b65b78d..3ece1a0 100644
--- a/content/test/data/accessibility/html/iframe-coordinates-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-coordinates-expected-fuchsia.txt
@@ -10,7 +10,7 @@
 ++++++++++STATIC_TEXT label='Button' actions='{DEFAULT}'
 ++++++++++++UNKNOWN label='Button'
 ++++++UNKNOWN
-++++++++UNKNOWN focusable
+++++++++UNKNOWN
 ++++++++++UNKNOWN focusable
 ++++++++++++UNKNOWN hidden
 ++++++++++++++UNKNOWN hidden
@@ -19,7 +19,7 @@
 ++++++++++++++++++++STATIC_TEXT label='Ordinary Button' actions='{DEFAULT}'
 ++++++++++++++++++++++UNKNOWN label='Ordinary Button'
 ++++++UNKNOWN
-++++++++UNKNOWN focusable
+++++++++UNKNOWN
 ++++++++++UNKNOWN focusable
 ++++++++++++UNKNOWN hidden
 ++++++++++++++UNKNOWN hidden
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-fuchsia.txt
index c7a7bd9c..d02c13d 100644
--- a/content/test/data/accessibility/html/iframe-create-empty-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-fuchsia.txt
@@ -1,7 +1,7 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
-++++++++++++UNKNOWN hidden
\ No newline at end of file
+++++++++++++UNKNOWN hidden
diff --git a/content/test/data/accessibility/html/iframe-create-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-create-expected-fuchsia.txt
index c5b6954..a3859e5d 100644
--- a/content/test/data/accessibility/html/iframe-create-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-create-expected-fuchsia.txt
@@ -1,7 +1,7 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
 ++++++++++++UNKNOWN
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-fuchsia.txt
index c461826..14e283aa1 100644
--- a/content/test/data/accessibility/html/iframe-empty-positioned-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-fuchsia.txt
@@ -1,11 +1,11 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN hidden
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
 ++++++++++++UNKNOWN hidden
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
-++++++++++++UNKNOWN hidden
\ No newline at end of file
+++++++++++++UNKNOWN hidden
diff --git a/content/test/data/accessibility/html/iframe-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-expected-fuchsia.txt
index 2ffdd17e..0482a06 100644
--- a/content/test/data/accessibility/html/iframe-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-expected-fuchsia.txt
@@ -1,7 +1,7 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN
-++++++UNKNOWN focusable label='Empty iframe'
+++++++UNKNOWN label='Empty iframe'
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
-++++++++++++UNKNOWN hidden
\ No newline at end of file
+++++++++++++UNKNOWN hidden
diff --git a/content/test/data/accessibility/html/iframe-presentational-expected-fuchsia.txt b/content/test/data/accessibility/html/iframe-presentational-expected-fuchsia.txt
index c7a7bd9c..d02c13d 100644
--- a/content/test/data/accessibility/html/iframe-presentational-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/iframe-presentational-expected-fuchsia.txt
@@ -1,7 +1,7 @@
 UNKNOWN focusable has_input_focus
 ++UNKNOWN hidden
 ++++UNKNOWN
-++++++UNKNOWN focusable
+++++++UNKNOWN
 ++++++++UNKNOWN focusable
 ++++++++++UNKNOWN hidden
-++++++++++++UNKNOWN hidden
\ No newline at end of file
+++++++++++++UNKNOWN hidden
diff --git a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
index 93d1da70..2d312bd 100644
--- a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
+++ b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
@@ -16,13 +16,13 @@
 ++++++++++++staticText ignored invisible name='amet' notUserSelectableStyle=true
 ++++++genericContainer focusable htmlTag='div' name=' consectetur adipiscing tempor '
 ++++++++staticText name='<newline>  consectetur<newline>  '
-++++++++genericContainer ignored htmlTag='span'
-++++++++++staticText name='adipiscing'
+++++++++genericContainer ignored invisible htmlTag='span'
+++++++++++staticText invisible name='adipiscing'
 ++++++++staticText name='<newline>  '
 ++++++++genericContainer htmlTag='div'
 ++++++++++staticText name='<newline>    '
-++++++++++genericContainer ignored htmlTag='span'
-++++++++++++staticText name='tempor'
+++++++++++genericContainer ignored invisible htmlTag='span'
+++++++++++++staticText invisible name='tempor'
 ++++++++++staticText name='<newline>  '
 ++++++++staticText name='<newline>'
 ++++++canvas focusable htmlTag='canvas' name=' sed do eiusmod tempor '
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index f73844fa..b60b641b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -387,9 +387,6 @@
 crbug.com/1382332 [ android ] Pixel_MediaRecorderFromVideoElementWithOoprCanvasDisabled [ Failure ]
 crbug.com/1382332 [ fuchsia ] Pixel_MediaRecorderFromVideoElementWithOoprCanvasDisabled [ Failure ]
 
-# Lacros failutres.
-crbug.com/1382332 [ desktop linux display-server-wayland passthrough intel ] Pixel_WebGLReadPixelsTabSwitch_SoftwareCompositing [ Failure ]
-
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/device/vr/android/arcore/arcore_device.cc b/device/vr/android/arcore/arcore_device.cc
index 723583d..9dc05802 100644
--- a/device/vr/android/arcore/arcore_device.cc
+++ b/device/vr/android/arcore/arcore_device.cc
@@ -21,6 +21,7 @@
 #include "device/vr/android/mailbox_to_surface_bridge.h"
 #include "device/vr/public/cpp/xr_frame_sink_client.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/android/window_android.h"
 #include "ui/display/display.h"
 
 using base::android::JavaRef;
diff --git a/extensions/common/value_builder.cc b/extensions/common/value_builder.cc
index 404fd30..bb41c44b 100644
--- a/extensions/common/value_builder.cc
+++ b/extensions/common/value_builder.cc
@@ -34,7 +34,7 @@
 
 // ListBuilder
 
-ListBuilder::ListBuilder() : list_(new base::ListValue) {}
+ListBuilder::ListBuilder() = default;
 ListBuilder::~ListBuilder() = default;
 
 }  // namespace extensions
diff --git a/extensions/common/value_builder.h b/extensions/common/value_builder.h
index 0a5cec64..9cc10f1 100644
--- a/extensions/common/value_builder.h
+++ b/extensions/common/value_builder.h
@@ -99,17 +99,19 @@
 
   // Can only be called once, after which it's invalid to use the builder.
   base::Value::List BuildList() {
-    base::Value::List result = std::move(*list_).TakeList();
-    list_.reset();
+    base::Value::List result = std::move(list_);
+    list_ = base::Value::List();
     return result;
   }
 
   // DEPRECATED version of BuildList().
-  std::unique_ptr<base::ListValue> Build() { return std::move(list_); }
+  std::unique_ptr<base::Value> Build() {
+    return std::make_unique<base::Value>(BuildList());
+  }
 
   template <typename T>
   ListBuilder& Append(T in_value) {
-    list_->Append(std::move(in_value));
+    list_.Append(std::move(in_value));
     return *this;
   }
 
@@ -118,19 +120,19 @@
   template <typename InputIt>
   ListBuilder& Append(InputIt first, InputIt last) {
     for (; first != last; ++first)
-      list_->Append(*first);
+      list_.Append(*first);
     return *this;
   }
 
   // See note on DictionaryBuilder::Set().
   template <typename T>
   ListBuilder& Append(std::unique_ptr<T> in_value) {
-    list_->Append(std::move(*in_value));
+    list_.Append(std::move(*in_value));
     return *this;
   }
 
  private:
-  std::unique_ptr<base::ListValue> list_;
+  base::Value::List list_;
 };
 
 }  // namespace extensions
diff --git a/extensions/renderer/bindings/api_binding.cc b/extensions/renderer/bindings/api_binding.cc
index 130c7e8e..5e92062 100644
--- a/extensions/renderer/bindings/api_binding.cc
+++ b/extensions/renderer/bindings/api_binding.cc
@@ -704,7 +704,7 @@
   }
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
-      context, name, std::move(parse_result.arguments_list),
+      context, name, std::move(*parse_result.arguments_list),
       parse_result.async_type, parse_result.callback, custom_callback,
       std::move(result_modifier));
   if (!promise.IsEmpty())
diff --git a/extensions/renderer/bindings/api_binding_js_util.cc b/extensions/renderer/bindings/api_binding_js_util.cc
index 7a68ed17..1072e6f2 100644
--- a/extensions/renderer/bindings/api_binding_js_util.cc
+++ b/extensions/renderer/bindings/api_binding_js_util.cc
@@ -108,7 +108,7 @@
   DCHECK_NE(binding::AsyncResponseType::kPromise, parse_result.async_type);
 
   request_handler_->StartRequest(
-      context, name, std::move(parse_result.arguments_list),
+      context, name, std::move(*parse_result.arguments_list),
       parse_result.async_type, parse_result.callback, custom_callback,
       binding::ResultModifierFunction());
 }
diff --git a/extensions/renderer/bindings/api_binding_js_util_unittest.cc b/extensions/renderer/bindings/api_binding_js_util_unittest.cc
index 92b2f8f..9ff4532 100644
--- a/extensions/renderer/bindings/api_binding_js_util_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_js_util_unittest.cc
@@ -161,7 +161,7 @@
   CallFunctionOnObject(context, v8_util, kSendRequestWithNoOptions);
   ASSERT_TRUE(last_request());
   EXPECT_EQ("alpha.functionWithCallback", last_request()->method_name);
-  EXPECT_EQ("[\"someString\"]", ValueToString(*last_request()->arguments_list));
+  EXPECT_EQ("[\"someString\"]", ValueToString(last_request()->arguments_list));
   reset_last_request();
 
   const char kSendRequestForUIThread[] =
@@ -172,7 +172,7 @@
   ASSERT_TRUE(last_request());
   EXPECT_EQ("alpha.functionWithCallback", last_request()->method_name);
   EXPECT_EQ("[\"someOtherString\"]",
-            ValueToString(*last_request()->arguments_list));
+            ValueToString(last_request()->arguments_list));
   reset_last_request();
 
   const char kSendRequestWithCustomCallback[] =
@@ -188,7 +188,7 @@
   CallFunctionOnObject(context, v8_util, kSendRequestWithCustomCallback);
   ASSERT_TRUE(last_request());
   EXPECT_EQ("alpha.functionWithCallback", last_request()->method_name);
-  EXPECT_EQ("[\"stringy\"]", ValueToString(*last_request()->arguments_list));
+  EXPECT_EQ("[\"stringy\"]", ValueToString(last_request()->arguments_list));
   bindings_system()->CompleteRequest(last_request()->request_id,
                                      base::Value::List(), std::string());
   EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
@@ -213,7 +213,7 @@
   CallFunctionOnObject(context, v8_util, kSendRequest);
   ASSERT_TRUE(last_request());
   EXPECT_EQ("alpha.functionWithCallback", last_request()->method_name);
-  EXPECT_EQ("[null,null]", ValueToString(*last_request()->arguments_list));
+  EXPECT_EQ("[null,null]", ValueToString(last_request()->arguments_list));
   reset_last_request();
 }
 
diff --git a/extensions/renderer/bindings/api_binding_unittest.cc b/extensions/renderer/bindings/api_binding_unittest.cc
index 8e621cc..1a8ac2d 100644
--- a/extensions/renderer/bindings/api_binding_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_unittest.cc
@@ -426,7 +426,7 @@
       return v8::Local<v8::Value>();
     }
     EXPECT_EQ(expected_json_arguments,
-              ValueToString(*last_request_->arguments_list));
+              ValueToString(last_request_->arguments_list));
     EXPECT_EQ(expect_async_handler, last_request_->has_async_response_handler)
         << script_source;
   } else {
@@ -1820,8 +1820,7 @@
          std::vector<v8::Local<v8::Value>>* arguments,
          const APITypeReferenceMap& map) {
         handler->StartRequest(
-            context, "test.handleAndSendRequest",
-            std::make_unique<base::Value::List>(),
+            context, "test.handleAndSendRequest", base::Value::List(),
             binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
             v8::Local<v8::Function>(), binding::ResultModifierFunction());
         return RequestResult(RequestResult::HANDLED);
diff --git a/extensions/renderer/bindings/api_bindings_system_unittest.cc b/extensions/renderer/bindings/api_bindings_system_unittest.cc
index b0fc25b..982c251 100644
--- a/extensions/renderer/bindings/api_bindings_system_unittest.cc
+++ b/extensions/renderer/bindings/api_bindings_system_unittest.cc
@@ -215,12 +215,9 @@
     const std::string& expected_name,
     const std::string& expected_arguments) {
   ASSERT_TRUE(last_request());
-  // Note that even if no arguments are provided by the API call, we should
-  // have an empty list.
-  ASSERT_TRUE(last_request()->arguments_list);
   EXPECT_EQ(expected_name, last_request()->method_name);
   EXPECT_EQ(ReplaceSingleQuotes(expected_arguments),
-            ValueToString(*last_request()->arguments_list));
+            ValueToString(last_request()->arguments_list));
 }
 
 v8::Local<v8::Value> APIBindingsSystemTest::CallFunctionOnObject(
diff --git a/extensions/renderer/bindings/api_request_handler.cc b/extensions/renderer/bindings/api_request_handler.cc
index 04de816..c34f7ee 100644
--- a/extensions/renderer/bindings/api_request_handler.cc
+++ b/extensions/renderer/bindings/api_request_handler.cc
@@ -443,7 +443,7 @@
 v8::Local<v8::Promise> APIRequestHandler::StartRequest(
     v8::Local<v8::Context> context,
     const std::string& method,
-    std::unique_ptr<base::Value::List> arguments_list,
+    base::Value::List arguments_list,
     binding::AsyncResponseType async_type,
     v8::Local<v8::Function> callback,
     v8::Local<v8::Function> custom_callback,
diff --git a/extensions/renderer/bindings/api_request_handler.h b/extensions/renderer/bindings/api_request_handler.h
index b200775..1b28cbd 100644
--- a/extensions/renderer/bindings/api_request_handler.h
+++ b/extensions/renderer/bindings/api_request_handler.h
@@ -41,7 +41,7 @@
     std::string method_name;
     bool has_async_response_handler = false;
     bool has_user_gesture = false;
-    std::unique_ptr<base::Value::List> arguments_list;
+    base::Value::List arguments_list;
   };
 
   // Details about a newly-added request to provide as a return to callers.
@@ -75,7 +75,7 @@
   v8::Local<v8::Promise> StartRequest(
       v8::Local<v8::Context> context,
       const std::string& method,
-      std::unique_ptr<base::Value::List> arguments_list,
+      base::Value::List arguments_list,
       binding::AsyncResponseType async_type,
       v8::Local<v8::Function> callback,
       v8::Local<v8::Function> custom_callback,
diff --git a/extensions/renderer/bindings/api_request_handler_unittest.cc b/extensions/renderer/bindings/api_request_handler_unittest.cc
index 233a6aa..7002821a 100644
--- a/extensions/renderer/bindings/api_request_handler_unittest.cc
+++ b/extensions/renderer/bindings/api_request_handler_unittest.cc
@@ -103,10 +103,10 @@
   v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
   ASSERT_FALSE(function.IsEmpty());
 
-  request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, function,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback, function,
+                                v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -122,9 +122,9 @@
   EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
 
   request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+      context, kMethod, base::Value::List(), binding::AsyncResponseType::kNone,
+      v8::Local<v8::Function>(), v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
   request_id = request_handler->last_sent_request_id();
   EXPECT_NE(-1, request_id);
   request_handler->CompleteRequest(request_id, base::Value::List(),
@@ -141,10 +141,10 @@
   v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
   ASSERT_FALSE(function.IsEmpty());
 
-  request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, function,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback, function,
+                                v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -177,15 +177,15 @@
   v8::Local<v8::Function> function_b = FunctionFromString(
       context_b, "(function(res) { this.result = res + 'beta'; })");
 
-  request_handler->StartRequest(
-      context_a, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, function_a,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context_a, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback,
+                                function_a, v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   int request_a = request_handler->last_sent_request_id();
-  request_handler->StartRequest(
-      context_b, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, function_b,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context_b, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback,
+                                function_b, v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   int request_b = request_handler->last_sent_request_id();
 
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
@@ -223,10 +223,10 @@
   ASSERT_FALSE(callback.IsEmpty());
   ASSERT_FALSE(custom_callback.IsEmpty());
 
-  request_handler->StartRequest(
-      context, "method", std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, callback, custom_callback,
-      binding::ResultModifierFunction());
+  request_handler->StartRequest(context, "method", base::Value::List(),
+                                binding::AsyncResponseType::kCallback, callback,
+                                custom_callback,
+                                binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -296,10 +296,10 @@
   ASSERT_FALSE(callback_throwing_error.IsEmpty());
   ASSERT_FALSE(custom_callback.IsEmpty());
 
-  request_handler.StartRequest(
-      context, "method", std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, callback_throwing_error,
-      custom_callback, binding::ResultModifierFunction());
+  request_handler.StartRequest(context, "method", base::Value::List(),
+                               binding::AsyncResponseType::kCallback,
+                               callback_throwing_error, custom_callback,
+                               binding::ResultModifierFunction());
   int request_id = request_handler.last_sent_request_id();
   EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -340,7 +340,7 @@
   ASSERT_FALSE(custom_callback.IsEmpty());
 
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
-      context, "method", std::make_unique<base::Value::List>(),
+      context, "method", base::Value::List(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
       custom_callback, binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
@@ -391,9 +391,8 @@
 
   v8::Local<v8::Function> empty_callback;
   request_handler->StartRequest(
-      context, "method", std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kNone, empty_callback, custom_callback,
-      binding::ResultModifierFunction());
+      context, "method", base::Value::List(), binding::AsyncResponseType::kNone,
+      empty_callback, custom_callback, binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -444,10 +443,10 @@
       context, "(function(arg1, arg2) {this.arg1 = arg1; this.arg2 = arg2});");
   ASSERT_FALSE(callback.IsEmpty());
 
-  request_handler->StartRequest(
-      context, "method", std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, callback,
-      v8::Local<v8::Function>(), std::move(result_modifier));
+  request_handler->StartRequest(context, "method", base::Value::List(),
+                                binding::AsyncResponseType::kCallback, callback,
+                                v8::Local<v8::Function>(),
+                                std::move(result_modifier));
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -485,10 +484,10 @@
       function_template->GetFunction(context).ToLocalChecked();
 
   // Try first without a user gesture.
-  request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, v8_callback,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback,
+                                v8_callback, v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
                                    std::string());
@@ -507,10 +506,10 @@
 
   EXPECT_TRUE(interaction_provider()->HasActiveInteraction(context));
 
-  request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
-      binding::AsyncResponseType::kCallback, v8_callback,
-      v8::Local<v8::Function>(), binding::ResultModifierFunction());
+  request_handler->StartRequest(context, kMethod, base::Value::List(),
+                                binding::AsyncResponseType::kCallback,
+                                v8_callback, v8::Local<v8::Function>(),
+                                binding::ResultModifierFunction());
   request_id = request_handler->last_sent_request_id();
   request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
                                    std::string());
@@ -556,10 +555,10 @@
     // console or exposed to the callback.
     v8::Local<v8::Function> callback =
         FunctionFromString(context, kReportExposedLastError);
-    request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
-        binding::AsyncResponseType::kCallback, callback,
-        v8::Local<v8::Function>(), binding::ResultModifierFunction());
+    request_handler.StartRequest(context, kMethod, base::Value::List(),
+                                 binding::AsyncResponseType::kCallback,
+                                 callback, v8::Local<v8::Function>(),
+                                 binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     std::string());
@@ -574,10 +573,10 @@
     // exposed to the callback).
     v8::Local<v8::Function> callback =
         FunctionFromString(context, kReportExposedLastError);
-    request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
-        binding::AsyncResponseType::kCallback, callback,
-        v8::Local<v8::Function>(), binding::ResultModifierFunction());
+    request_handler.StartRequest(context, kMethod, base::Value::List(),
+                                 binding::AsyncResponseType::kCallback,
+                                 callback, v8::Local<v8::Function>(),
+                                 binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -591,10 +590,10 @@
     // callback. The error should be logged.
     v8::Local<v8::Function> callback =
         FunctionFromString(context, "(function() {})");
-    request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
-        binding::AsyncResponseType::kCallback, callback,
-        v8::Local<v8::Function>(), binding::ResultModifierFunction());
+    request_handler.StartRequest(context, kMethod, base::Value::List(),
+                                 binding::AsyncResponseType::kCallback,
+                                 callback, v8::Local<v8::Function>(),
+                                 binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -608,10 +607,10 @@
     // and no author-script-provided callback. The error should be logged.
     v8::Local<v8::Function> custom_callback =
         FunctionFromString(context, "(function() {})");
-    request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
-        binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
-        custom_callback, binding::ResultModifierFunction());
+    request_handler.StartRequest(context, kMethod, base::Value::List(),
+                                 binding::AsyncResponseType::kNone,
+                                 v8::Local<v8::Function>(), custom_callback,
+                                 binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -624,7 +623,7 @@
     // Test a function call resulting in an error that does not have an
     // associated callback callback. The error should be logged.
     request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
+        context, kMethod, base::Value::List(),
         binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
         v8::Local<v8::Function>(), binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
@@ -651,10 +650,10 @@
         };
     v8::Local<v8::Function> callback =
         FunctionFromString(context, kReportExposedLastError);
-    request_handler.StartRequest(
-        context, kMethod, std::make_unique<base::Value::List>(),
-        binding::AsyncResponseType::kCallback, callback,
-        v8::Local<v8::Function>(), base::BindLambdaForTesting(result_modifier));
+    request_handler.StartRequest(context, kMethod, base::Value::List(),
+                                 binding::AsyncResponseType::kCallback,
+                                 callback, v8::Local<v8::Function>(),
+                                 base::BindLambdaForTesting(result_modifier));
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -842,7 +841,7 @@
   EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
 
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
+      context, kMethod, base::Value::List(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
       v8::Local<v8::Function>(), binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
@@ -872,7 +871,7 @@
   EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
 
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
-      context, kMethod, std::make_unique<base::Value::List>(),
+      context, kMethod, base::Value::List(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
       v8::Local<v8::Function>(), binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
diff --git a/extensions/renderer/bindings/api_signature.cc b/extensions/renderer/bindings/api_signature.cc
index 4a44a59c2..d8e504d 100644
--- a/extensions/renderer/bindings/api_signature.cc
+++ b/extensions/renderer/bindings/api_signature.cc
@@ -440,8 +440,7 @@
   if (!ParseArgumentsImpl(signature_has_callback)) {
     result.error = TakeError();
   } else {
-    result.arguments_list =
-        std::make_unique<base::Value::List>(std::move(list_value_));
+    result.arguments_list = std::move(list_value_);
     result.callback = callback_;
     result.async_type = async_type();
   }
@@ -662,7 +661,7 @@
     json.Append(base::Value::FromUniquePtrValue(std::move(converted)));
   }
 
-  result.arguments_list = std::make_unique<base::Value::List>(std::move(json));
+  result.arguments_list = std::move(json);
   return result;
 }
 
diff --git a/extensions/renderer/bindings/api_signature.h b/extensions/renderer/bindings/api_signature.h
index af09d98..0b31663e 100644
--- a/extensions/renderer/bindings/api_signature.h
+++ b/extensions/renderer/bindings/api_signature.h
@@ -100,12 +100,12 @@
     JSONParseResult(JSONParseResult&& other);
     JSONParseResult& operator=(JSONParseResult&& other);
 
-    bool succeeded() const { return !!arguments_list; }
+    bool succeeded() const { return arguments_list.has_value(); }
 
     // The parsed JSON arguments, with null-filled optional arguments filled in.
     // Populated if parsing was successful. Does not include the callback (if
     // any).
-    std::unique_ptr<base::Value::List> arguments_list;
+    absl::optional<base::Value::List> arguments_list;
 
     // The callback, if one was provided.
     v8::Local<v8::Function> callback;
diff --git a/extensions/renderer/bindings/declarative_event.cc b/extensions/renderer/bindings/declarative_event.cc
index 565bc203e..913be228 100644
--- a/extensions/renderer/bindings/declarative_event.cc
+++ b/extensions/renderer/bindings/declarative_event.cc
@@ -202,7 +202,7 @@
   DCHECK_NE(binding::AsyncResponseType::kPromise, parse_result.async_type);
 
   request_handler_->StartRequest(
-      context, request_name, std::move(parse_result.arguments_list),
+      context, request_name, std::move(*parse_result.arguments_list),
       parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
       binding::ResultModifierFunction());
 }
diff --git a/extensions/renderer/chrome_setting.cc b/extensions/renderer/chrome_setting.cc
index 85d7fd5..bc089e9a 100644
--- a/extensions/renderer/chrome_setting.cc
+++ b/extensions/renderer/chrome_setting.cc
@@ -179,7 +179,7 @@
                                       base::Value(pref_name_));
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
-      context, full_name, std::move(parse_result.arguments_list),
+      context, full_name, std::move(*parse_result.arguments_list),
       parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
       binding::ResultModifierFunction());
   if (!promise.IsEmpty())
diff --git a/extensions/renderer/content_setting.cc b/extensions/renderer/content_setting.cc
index 4e78bde..7482a10 100644
--- a/extensions/renderer/content_setting.cc
+++ b/extensions/renderer/content_setting.cc
@@ -216,7 +216,7 @@
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
       context, "contentSettings." + method_name,
-      std::move(parse_result.arguments_list), parse_result.async_type,
+      std::move(*parse_result.arguments_list), parse_result.async_type,
       parse_result.callback, v8::Local<v8::Function>(),
       binding::ResultModifierFunction());
   if (!promise.IsEmpty())
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc
index cb105b2..4ae0777d 100644
--- a/extensions/renderer/native_extension_bindings_system.cc
+++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -850,7 +850,7 @@
 
   auto params = mojom::RequestParams::New();
   params->name = request->method_name;
-  params->arguments = std::move(*request->arguments_list);
+  params->arguments = std::move(request->arguments_list);
   params->extension_id = script_context->GetExtensionID();
   params->source_url = url;
   params->request_id = request->request_id;
diff --git a/extensions/renderer/storage_area.cc b/extensions/renderer/storage_area.cc
index d58db618..359f21f4 100644
--- a/extensions/renderer/storage_area.cc
+++ b/extensions/renderer/storage_area.cc
@@ -307,7 +307,7 @@
                                       base::Value(name_));
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
-      context, full_method_name, std::move(parse_result.arguments_list),
+      context, full_method_name, std::move(*parse_result.arguments_list),
       parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
       binding::ResultModifierFunction());
 
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
index f6b6ae7..14a2731 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
@@ -124,7 +124,7 @@
     base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback) {
   // TODO(crbug.com/1063094): Implement permission status subscription. It's
   // used in blink to emit PermissionStatus.onchange notifications.
-  NOTIMPLEMENTED_LOG_ONCE() << ": " << static_cast<int>(permission);
+  NOTIMPLEMENTED_LOG_ONCE();
   return SubscriptionId();
 }
 
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index d42eda4f..d6b2d402 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1584,6 +1584,7 @@
     const gpu::Mailbox& source_mailbox,
     GLenum source_target,
     GrSurfaceOrigin source_origin,
+    const gfx::Size& source_size,
     const gfx::Point& source_starting_point,
     const SkImageInfo& dst_info,
     GLuint dst_row_bytes,
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index 3b13709..bc4446e 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -175,6 +175,7 @@
       const gpu::Mailbox& source_mailbox,
       GLenum source_target,
       GrSurfaceOrigin source_origin,
+      const gfx::Size& source_size,
       const gfx::Point& source_starting_point,
       const SkImageInfo& dst_info,
       GLuint dst_row_bytes,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index e89179f..7d95c363 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -264,6 +264,7 @@
     const gpu::Mailbox& source_mailbox,
     GLenum source_target,
     GrSurfaceOrigin src_origin,
+    const gfx::Size& source_size,
     const gfx::Point& source_starting_point,
     const SkImageInfo& dst_info,
     GLuint dst_row_bytes,
@@ -282,17 +283,29 @@
   // Convert bottom-left GL coordinates to top-left coordinates expected
   // by RI clients.
   bool flip_y;
+  gfx::Point starting_point(source_starting_point);
   switch (src_origin) {
     case kTopLeft_GrSurfaceOrigin:
       flip_y = false;
       break;
     case kBottomLeft_GrSurfaceOrigin:
+      // Since RI clients always expect top-left origin, two things need to be
+      // done when texture's origin is bottom-left.
+
+      // 1. Rows in the output buffer need to be switched vertically.
       flip_y = true;
+
+      // 2. Starting of a target rectangle needs to be adjusted from top-left
+      //    to bottom-left. That's how glReadPixels expects it.
+      // It's okay if we accidentally go negative here, glReadPixels checks
+      // its input.
+      starting_point.set_y(source_size.height() - starting_point.y() -
+                           dst_gfx_size.height());
       break;
   }
 
   GetGLHelper()->ReadbackTextureAsync(
-      texture_id, source_target, source_starting_point, dst_gfx_size, out,
+      texture_id, source_target, starting_point, dst_gfx_size, out,
       dst_row_bytes, flip_y, format,
       base::BindOnce(&RasterImplementationGLES::OnReadARGBPixelsAsync,
                      weak_ptr_factory_.GetWeakPtr(), texture_id,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index c0c40b9a..f0426407 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -122,6 +122,7 @@
       const gpu::Mailbox& source_mailbox,
       GLenum source_target,
       GrSurfaceOrigin source_origin,
+      const gfx::Size& source_size,
       const gfx::Point& source_starting_point,
       const SkImageInfo& dst_info,
       GLuint dst_row_bytes,
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index ba575a3..a9da90f 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -135,12 +135,15 @@
   // kBGRA_8888_SkColorType color types.
   // |out| must remain valid  until |readback_done| is called with
   // a bool indicating if the readback was successful.
+  // |source_size| describes dimensions of the |source_mailbox| texture.
+  // |dst_info| |source_starting_point| describe subregion that needs to be read
   // On success |out| will contain the pixel data copied back from the GPU
   // process.
   virtual void ReadbackARGBPixelsAsync(
       const gpu::Mailbox& source_mailbox,
       GLenum source_target,
       GrSurfaceOrigin source_origin,
+      const gfx::Size& source_size,
       const gfx::Point& source_starting_point,
       const SkImageInfo& dst_info,
       GLuint dst_row_bytes,
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
index fd88161c..dbff3db 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
@@ -637,6 +637,14 @@
 #endif  // BUILDFLAG(USE_DAWN)
 }
 
+std::unique_ptr<VideoDecodeImageRepresentation>
+D3DImageBacking::ProduceVideoDecode(SharedImageManager* manager,
+                                    MemoryTypeTracker* tracker,
+                                    VideoDecodeDevice device) {
+  return std::make_unique<D3D11VideoDecodeImageRepresentation>(
+      manager, this, tracker, d3d11_texture_);
+}
+
 void D3DImageBacking::OnMemoryDump(
     const std::string& dump_name,
     base::trace_event::MemoryAllocatorDumpGuid client_guid,
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing.h b/gpu/command_buffer/service/shared_image/d3d_image_backing.h
index 7e34f93..172ba3d 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing.h
@@ -164,6 +164,11 @@
       MemoryTypeTracker* tracker,
       scoped_refptr<SharedContextState> context_state) override;
 
+  std::unique_ptr<VideoDecodeImageRepresentation> ProduceVideoDecode(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      VideoDecodeDevice device) override;
+
  private:
 #if BUILDFLAG(USE_DAWN)
   struct DawnExternalImageState {
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_representation.cc b/gpu/command_buffer/service/shared_image/d3d_image_representation.cc
index c8c4d81..9a83343f 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_representation.cc
@@ -140,4 +140,33 @@
   return gl_image_.get();
 }
 
+D3D11VideoDecodeImageRepresentation::D3D11VideoDecodeImageRepresentation(
+    SharedImageManager* manager,
+    SharedImageBacking* backing,
+    MemoryTypeTracker* tracker,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture)
+    : VideoDecodeImageRepresentation(manager, backing, tracker),
+      texture_(std::move(texture)) {}
+
+D3D11VideoDecodeImageRepresentation::~D3D11VideoDecodeImageRepresentation() =
+    default;
+
+bool D3D11VideoDecodeImageRepresentation::BeginWriteAccess() {
+  D3DImageBacking* d3d_image_backing = static_cast<D3DImageBacking*>(backing());
+  if (!d3d_image_backing->BeginAccessD3D11(/*write_access=*/true))
+    return false;
+
+  return true;
+}
+
+void D3D11VideoDecodeImageRepresentation::EndWriteAccess() {
+  D3DImageBacking* d3d_image_backing = static_cast<D3DImageBacking*>(backing());
+  d3d_image_backing->EndAccessD3D11();
+}
+
+Microsoft::WRL::ComPtr<ID3D11Texture2D>
+D3D11VideoDecodeImageRepresentation::GetD3D11Texture() const {
+  return texture_;
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_representation.h b/gpu/command_buffer/service/shared_image/d3d_image_representation.h
index 3f94469..a059977 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_representation.h
+++ b/gpu/command_buffer/service/shared_image/d3d_image_representation.h
@@ -85,5 +85,23 @@
   scoped_refptr<gl::GLImage> gl_image_;
 };
 
+class D3D11VideoDecodeImageRepresentation
+    : public VideoDecodeImageRepresentation {
+ public:
+  D3D11VideoDecodeImageRepresentation(
+      SharedImageManager* manager,
+      SharedImageBacking* backing,
+      MemoryTypeTracker* tracker,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> texture);
+  ~D3D11VideoDecodeImageRepresentation() override;
+
+ private:
+  bool BeginWriteAccess() override;
+  void EndWriteAccess() override;
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> GetD3D11Texture() const override;
+
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture_;
+};
+
 }  // namespace gpu
 #endif  // GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_D3D_IMAGE_REPRESENTATION_H_
diff --git a/gpu/command_buffer/service/shared_image/shared_image_backing.cc b/gpu/command_buffer/service/shared_image/shared_image_backing.cc
index ad5aac0..ae2547a 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_backing.cc
@@ -197,6 +197,13 @@
   return nullptr;
 }
 
+std::unique_ptr<VideoDecodeImageRepresentation>
+SharedImageBacking::ProduceVideoDecode(SharedImageManager* manager,
+                                       MemoryTypeTracker* tracker,
+                                       VideoDecodeDevice device) {
+  return nullptr;
+}
+
 #if BUILDFLAG(IS_ANDROID)
 std::unique_ptr<LegacyOverlayImageRepresentation>
 SharedImageBacking::ProduceLegacyOverlay(SharedImageManager* manager,
diff --git a/gpu/command_buffer/service/shared_image/shared_image_backing.h b/gpu/command_buffer/service/shared_image/shared_image_backing.h
index 3759c734..c5e3665 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_backing.h
@@ -29,6 +29,11 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_pixmap.h"
 
+#if BUILDFLAG(IS_WIN)
+#include <d3d11.h>
+#include <wrl/client.h>
+#endif
+
 namespace base {
 namespace trace_event {
 class ProcessMemoryDump;
@@ -53,6 +58,7 @@
 class VaapiImageRepresentation;
 class RasterImageRepresentation;
 class MemoryTracker;
+class VideoDecodeImageRepresentation;
 class MemoryTypeTracker;
 class SharedImageFactory;
 class VaapiDependenciesFactory;
@@ -78,6 +84,13 @@
   kDXGISwapChain = 17,
 };
 
+#if BUILDFLAG(IS_WIN)
+using VideoDecodeDevice = Microsoft::WRL::ComPtr<ID3D11Device>;
+#else
+// This parameter is only used on Windows so null is expected.
+using VideoDecodeDevice = void*;
+#endif  // BUILDFLAG(IS_WIN)
+
 // Represents the actual storage (GL texture, VkImage, GMB) for a SharedImage.
 // Should not be accessed directly, instead is accessed through a
 // SharedImageRepresentation.
@@ -243,6 +256,13 @@
   virtual std::unique_ptr<RasterImageRepresentation> ProduceRaster(
       SharedImageManager* manager,
       MemoryTypeTracker* tracker);
+  // Take void* device for resource generated from different devices. E.g  video
+  // decoder starts using its own device on a separate thread.
+  virtual std::unique_ptr<VideoDecodeImageRepresentation> ProduceVideoDecode(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      VideoDecodeDevice device);
+
 #if BUILDFLAG(IS_ANDROID)
   virtual std::unique_ptr<LegacyOverlayImageRepresentation>
   ProduceLegacyOverlay(SharedImageManager* manager, MemoryTypeTracker* tracker);
diff --git a/gpu/command_buffer/service/shared_image/shared_image_manager.cc b/gpu/command_buffer/service/shared_image/shared_image_manager.cc
index 9a9f15d..283e6e3 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_manager.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_manager.cc
@@ -319,6 +319,26 @@
   return (*found)->ProduceRaster(this, tracker);
 }
 
+std::unique_ptr<VideoDecodeImageRepresentation>
+SharedImageManager::ProduceVideoDecode(VideoDecodeDevice device,
+                                       const Mailbox& mailbox,
+                                       MemoryTypeTracker* tracker) {
+  CALLED_ON_VALID_THREAD();
+
+  AutoLock autolock(this);
+  auto found = images_.find(mailbox);
+  if (found == images_.end()) {
+    LOG(ERROR)
+        << "SharedImageManager::ProduceVideoDecode: Trying to Produce a D3D"
+           "representation from a non-existent mailbox.";
+    return nullptr;
+  }
+
+  // This is expected to fail based on the SharedImageBacking type, so don't log
+  // error here. Caller is expected to handle nullptr.
+  return (*found)->ProduceVideoDecode(this, tracker, device);
+}
+
 #if BUILDFLAG(IS_ANDROID)
 std::unique_ptr<LegacyOverlayImageRepresentation>
 SharedImageManager::ProduceLegacyOverlay(const Mailbox& mailbox,
diff --git a/gpu/command_buffer/service/shared_image/shared_image_manager.h b/gpu/command_buffer/service/shared_image/shared_image_manager.h
index a06638ff..38fac07 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_manager.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_manager.h
@@ -16,6 +16,10 @@
 #include "gpu/gpu_gles2_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if BUILDFLAG(IS_WIN)
+#include <d3d11.h>
+#endif
+
 namespace gpu {
 class DXGISharedHandleManager;
 class SharedImageRepresentationFactoryRef;
@@ -88,6 +92,10 @@
   std::unique_ptr<RasterImageRepresentation> ProduceRaster(
       const Mailbox& mailbox,
       MemoryTypeTracker* ref);
+  std::unique_ptr<VideoDecodeImageRepresentation> ProduceVideoDecode(
+      VideoDecodeDevice device,
+      const Mailbox& mailbox,
+      MemoryTypeTracker* ref);
 
 #if BUILDFLAG(IS_ANDROID)
   std::unique_ptr<LegacyOverlayImageRepresentation> ProduceLegacyOverlay(
diff --git a/gpu/command_buffer/service/shared_image/shared_image_representation.cc b/gpu/command_buffer/service/shared_image/shared_image_representation.cc
index 3e8d7a3..87d5fc5 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_representation.cc
@@ -547,4 +547,30 @@
                        surface_props, clear_color, visible));
 }
 
+VideoDecodeImageRepresentation::VideoDecodeImageRepresentation(
+    SharedImageManager* manager,
+    SharedImageBacking* backing,
+    MemoryTypeTracker* tracker)
+    : SharedImageRepresentation(manager, backing, tracker) {}
+
+VideoDecodeImageRepresentation::~VideoDecodeImageRepresentation() = default;
+
+VideoDecodeImageRepresentation::ScopedWriteAccess::ScopedWriteAccess(
+    base::PassKey<VideoDecodeImageRepresentation> /* pass_key */,
+    VideoDecodeImageRepresentation* representation)
+    : ScopedAccessBase(representation) {}
+
+VideoDecodeImageRepresentation::ScopedWriteAccess::~ScopedWriteAccess() {
+  representation()->EndWriteAccess();
+}
+
+std::unique_ptr<VideoDecodeImageRepresentation::ScopedWriteAccess>
+VideoDecodeImageRepresentation::BeginScopedWriteAccess() {
+  if (!BeginWriteAccess())
+    return nullptr;
+
+  return std::make_unique<ScopedWriteAccess>(
+      base::PassKey<VideoDecodeImageRepresentation>(), this);
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/shared_image_representation.h b/gpu/command_buffer/service/shared_image/shared_image_representation.h
index 396c7a4..56d7012 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_representation.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_representation.h
@@ -43,6 +43,11 @@
 extern "C" typedef struct AHardwareBuffer AHardwareBuffer;
 #endif
 
+#if BUILDFLAG(IS_WIN)
+#include <d3d11.h>
+#include <wrl/client.h>
+#endif
+
 typedef unsigned int GLenum;
 class GrBackendSurfaceMutableState;
 class SkPromiseImageTexture;
@@ -791,6 +796,38 @@
   virtual void EndWriteAccess(base::OnceClosure callback) = 0;
 };
 
+class GPU_GLES2_EXPORT VideoDecodeImageRepresentation
+    : public SharedImageRepresentation {
+ public:
+  class GPU_GLES2_EXPORT ScopedWriteAccess
+      : public ScopedAccessBase<VideoDecodeImageRepresentation> {
+   public:
+    ScopedWriteAccess(base::PassKey<VideoDecodeImageRepresentation> pass_key,
+                      VideoDecodeImageRepresentation* representation);
+    ~ScopedWriteAccess();
+
+#if BUILDFLAG(IS_WIN)
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> GetD3D11Texture() const {
+      return representation()->GetD3D11Texture();
+    }
+#endif  // BUILDFLAG(IS_WIN)
+  };
+
+  VideoDecodeImageRepresentation(SharedImageManager* manager,
+                                 SharedImageBacking* backing,
+                                 MemoryTypeTracker* tracker);
+  ~VideoDecodeImageRepresentation() override;
+
+  virtual std::unique_ptr<ScopedWriteAccess> BeginScopedWriteAccess();
+
+ protected:
+#if BUILDFLAG(IS_WIN)
+  virtual Microsoft::WRL::ComPtr<ID3D11Texture2D> GetD3D11Texture() const = 0;
+#endif  // BUILDFLAG(IS_WIN)
+  virtual bool BeginWriteAccess() = 0;
+  virtual void EndWriteAccess() = 0;
+};
+
 }  // namespace gpu
 
 #endif  // GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_SHARED_IMAGE_REPRESENTATION_H_
diff --git a/headless/lib/browser/headless_window_tree_host.cc b/headless/lib/browser/headless_window_tree_host.cc
index 777c918..6ced9c9 100644
--- a/headless/lib/browser/headless_window_tree_host.cc
+++ b/headless/lib/browser/headless_window_tree_host.cc
@@ -104,7 +104,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 std::string HeadlessWindowTreeHost::GetUniqueId() const {
-  NOTIMPLEMENTED_LOG_ONCE() << "Headless does not have a unique ID";
+  // Headless does not have a unique ID
+  NOTIMPLEMENTED_LOG_ONCE();
   return std::string();
 }
 #endif
diff --git a/infra/config/generated/builders/ci/linux-ubsan-fyi-rel/properties.json b/infra/config/generated/builders/ci/linux-ubsan-fyi-rel/properties.json
new file mode 100644
index 0000000..ccf9595
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-ubsan-fyi-rel/properties.json
@@ -0,0 +1,59 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "linux-ubsan-fyi-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.memory.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "linux-ubsan-fyi-rel",
+          "project": "chromium"
+        }
+      ],
+      "mirroring_builder_group_and_names": [
+        {
+          "builder": "linux-ubsan-fyi-rel",
+          "group": "tryserver.chromium.linux"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.memory.fyi",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-ubsan-fyi-rel/properties.json b/infra/config/generated/builders/try/linux-ubsan-fyi-rel/properties.json
new file mode 100644
index 0000000..cba2008
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-ubsan-fyi-rel/properties.json
@@ -0,0 +1,53 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "linux-ubsan-fyi-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.memory.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "linux-ubsan-fyi-rel",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "jobs": 150,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.linux",
+  "recipe": "chromium_trybot"
+}
\ No newline at end of file
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 380378b5..e9c094d 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -2747,6 +2747,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/linux-ubsan-fyi-rel"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/linux-viz-rel"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 35ecd19..0558e84 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -40708,6 +40708,95 @@
       }
     }
     builders {
+      name: "linux-ubsan-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/linux-ubsan-fyi-rel/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.memory.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.omit_python2"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "linux-ubsan-vptr"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -81008,6 +81097,116 @@
       }
     }
     builders {
+      name: "linux-ubsan-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/linux-ubsan-fyi-rel/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.linux",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "enable_weetbix_queries"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.omit_python2"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
+        key: "weetbix.enable_weetbix_exonerations"
+        value: 100
+      }
+      experiments {
+        key: "weetbix.retry_weak_exonerations"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "linux-viz-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index bf0e7810..98dbe8ca 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -12591,6 +12591,340 @@
   }
 }
 consoles {
+  id: "chromium.memory.fyi"
+  name: "chromium.memory.fyi"
+  repo_url: "https://chromium.googlesource.com/chromium/src"
+  refs: "regexp:refs/heads/main"
+  manifest_name: "REVISION"
+  builders {
+    name: "buildbucket/luci.chromium.ci/linux-ubsan-fyi-rel"
+    category: "linux|ubsan"
+    short_name: "fyi"
+  }
+  header {
+    oncalls {
+      name: "Chromium"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-build-sheriff"
+    }
+    oncalls {
+      name: "Android"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-android-sheriff"
+    }
+    oncalls {
+      name: "iOS"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-ios"
+    }
+    oncalls {
+      name: "ChromeOS"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chromeos-gardeners"
+    }
+    oncalls {
+      name: "Fuchsia"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chrome-fuchsia-gardener"
+    }
+    oncalls {
+      name: "GPU"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-gpu-pixel-wrangler-weekly"
+    }
+    oncalls {
+      name: "ANGLE"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:angle-wrangler"
+    }
+    oncalls {
+      name: "Perf"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chromium-perf-regression-sheriff"
+    }
+    oncalls {
+      name: "Perfbot"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/grotation:chromium-perf-bot-sheriff"
+    }
+    oncalls {
+      name: "Trooper"
+      url: "https://chrome-ops-rotation-proxy.appspot.com/current/oncallator:chrome-ops-client-infra"
+      show_primary_secondary_labels: true
+    }
+    links {
+      name: "Builds"
+      links {
+        text: "continuous"
+        url: "https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html"
+        alt: "Continuous browser snapshots"
+      }
+      links {
+        text: "symbols"
+        url: "https://www.chromium.org/developers/how-tos/debugging-on-windows"
+        alt: "Windows Symbols"
+      }
+      links {
+        text: "status"
+        url: "https://chromium-status.appspot.com/"
+        alt: "Current tree status"
+      }
+    }
+    links {
+      name: "Dashboards"
+      links {
+        text: "perf"
+        url: "https://chromeperf.appspot.com/"
+        alt: "Chrome perf dashboard"
+      }
+      links {
+        text: "LUCI Analysis"
+        url: "https://luci-analysis.appspot.com"
+        alt: "New flake portal"
+      }
+    }
+    links {
+      name: "Chromium"
+      links {
+        text: "source"
+        url: "https://chromium.googlesource.com/chromium/src"
+        alt: "Chromium source code repository"
+      }
+      links {
+        text: "reviews"
+        url: "https://chromium-review.googlesource.com"
+        alt: "Chromium code review tool"
+      }
+      links {
+        text: "bugs"
+        url: "https://crbug.com"
+        alt: "Chromium bug tracker"
+      }
+      links {
+        text: "coverage"
+        url: "https://analysis.chromium.org/coverage/p/chromium"
+        alt: "Chromium code coverage dashboard"
+      }
+      links {
+        text: "dev"
+        url: "https://dev.chromium.org/Home"
+        alt: "Chromium developer home page"
+      }
+      links {
+        text: "support"
+        url: "https://support.google.com/chrome/#topic=7438008"
+        alt: "Google Chrome help center"
+      }
+    }
+    links {
+      name: "Consoles"
+      links {
+        text: "android"
+        url: "/p/chromium/g/chromium.android"
+        alt: "Chromium Android console"
+      }
+      links {
+        text: "clang"
+        url: "/p/chromium/g/chromium.clang"
+        alt: "Chromium Clang console"
+      }
+      links {
+        text: "dawn"
+        url: "/p/chromium/g/chromium.dawn"
+        alt: "Chromium Dawn console"
+      }
+      links {
+        text: "fuzz"
+        url: "/p/chromium/g/chromium.fuzz"
+        alt: "Chromium Fuzz console"
+      }
+      links {
+        text: "fuchsia"
+        url: "/p/chromium/g/chromium.fuchsia"
+        alt: "Chromium Fuchsia console"
+      }
+      links {
+        text: "fyi"
+        url: "/p/chromium/g/chromium.fyi"
+        alt: "Chromium FYI console"
+      }
+      links {
+        text: "gpu"
+        url: "/p/chromium/g/chromium.gpu"
+        alt: "Chromium GPU console"
+      }
+      links {
+        text: "packager"
+        url: "/p/chromium/g/chromium.packager"
+        alt: "Chromium Packager console"
+      }
+      links {
+        text: "perf"
+        url: "/p/chrome/g/chrome.perf/console"
+        alt: "Chromium Perf console"
+      }
+      links {
+        text: "perf.fyi"
+        url: "/p/chrome/g/chrome.perf.fyi/console"
+        alt: "Chromium Perf FYI console"
+      }
+      links {
+        text: "angle"
+        url: "/p/chromium/g/chromium.angle"
+        alt: "Chromium ANGLE console"
+      }
+      links {
+        text: "swangle"
+        url: "/p/chromium/g/chromium.swangle"
+        alt: "Chromium SWANGLE console"
+      }
+      links {
+        text: "updater"
+        url: "/p/chromium/g/chromium.updater"
+        alt: "Chromium Updater console"
+      }
+      links {
+        text: "webrtc"
+        url: "/p/chromium/g/chromium.webrtc"
+        alt: "Chromium WebRTC console"
+      }
+      links {
+        text: "chromiumos"
+        url: "/p/chromium/g/chromium.chromiumos"
+        alt: "ChromiumOS console"
+      }
+      links {
+        text: "flakiness"
+        url: "/p/chromium/g/chromium.flakiness"
+        alt: "Chromium Flakiness console"
+      }
+    }
+    links {
+      name: "Branch Consoles"
+      links {
+        text: "m102"
+        url: "/p/chromium-m102/g/main/console"
+      }
+      links {
+        text: "m104"
+        url: "/p/chromium-m104/g/main/console"
+      }
+      links {
+        text: "m105"
+        url: "/p/chromium-m105/g/main/console"
+      }
+      links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
+        text: "m107"
+        url: "/p/chromium-m107/g/main/console"
+      }
+      links {
+        text: "m108"
+        url: "/p/chromium-m108/g/main/console"
+      }
+      links {
+        text: "m109"
+        url: "/p/chromium-m109/g/main/console"
+      }
+      links {
+        text: "trunk"
+        url: "/p/chromium/g/main/console"
+        alt: "Trunk (ToT) console"
+      }
+    }
+    links {
+      name: "Tryservers"
+      links {
+        text: "android"
+        url: "/p/chromium/g/tryserver.chromium.android/builders"
+        alt: "Android"
+      }
+      links {
+        text: "angle"
+        url: "/p/chromium/g/tryserver.chromium.angle/builders"
+        alt: "Angle"
+      }
+      links {
+        text: "blink"
+        url: "/p/chromium/g/tryserver.blink/builders"
+        alt: "Blink"
+      }
+      links {
+        text: "chrome"
+        url: "/p/chrome/g/tryserver.chrome/builders"
+        alt: "Chrome"
+      }
+      links {
+        text: "chromiumos"
+        url: "/p/chromium/g/tryserver.chromium.chromiumos/builders"
+        alt: "ChromiumOS"
+      }
+      links {
+        text: "fuchsia"
+        url: "/p/chromium/g/tryserver.chromium.fuchsia/builders"
+        alt: "Fuchsia"
+      }
+      links {
+        text: "linux"
+        url: "/p/chromium/g/tryserver.chromium.linux/builders"
+        alt: "Linux"
+      }
+      links {
+        text: "mac"
+        url: "/p/chromium/g/tryserver.chromium.mac/builders"
+        alt: "Mac"
+      }
+      links {
+        text: "swangle"
+        url: "/p/chromium/g/tryserver.chromium.swangle/builders"
+        alt: "SWANGLE"
+      }
+      links {
+        text: "tricium"
+        url: "/p/chromium/g/tryserver.chromium.tricium/builders"
+        alt: "Tricium"
+      }
+      links {
+        text: "win"
+        url: "/p/chromium/g/tryserver.chromium.win/builders"
+        alt: "Win"
+      }
+    }
+    links {
+      name: "Navigate"
+      links {
+        text: "about"
+        url: "http://dev.chromium.org/developers/testing/chromium-build-infrastructure/tour-of-the-chromium-buildbot"
+        alt: "Tour of the console"
+      }
+      links {
+        text: "customize"
+        url: "https://chromium.googlesource.com/chromium/src/+/refs/heads/main/infra/config/generated/luci/luci-milo.cfg"
+        alt: "Customize this console"
+      }
+    }
+    console_groups {
+      title {
+        text: "Tree Closers"
+        url: "https://chromium-status.appspot.com/"
+      }
+      console_ids: "chromium/chromium"
+      console_ids: "chromium/chromium.win"
+      console_ids: "chromium/chromium.mac"
+      console_ids: "chromium/chromium.linux"
+      console_ids: "chromium/chromium.chromiumos"
+      console_ids: "chromium/chromium.fuchsia"
+      console_ids: "chrome/chrome"
+      console_ids: "chromium/chromium.memory"
+      console_ids: "chromium/chromium.gpu"
+    }
+    console_groups {
+      console_ids: "chromium/chromium.android"
+      console_ids: "chrome/chrome.perf"
+      console_ids: "chromium/chromium.fuchsia.fyi"
+      console_ids: "chromium/chromium.gpu.fyi"
+      console_ids: "chromium/chromium.angle"
+      console_ids: "chromium/chromium.swangle"
+      console_ids: "chromium/chromium.fuzz"
+    }
+    tree_status_host: "chromium-status.appspot.com"
+  }
+}
+consoles {
   id: "chromium.packager"
   name: "chromium.packager"
   repo_url: "https://chromium.googlesource.com/chromium/src"
@@ -17225,6 +17559,9 @@
     name: "buildbucket/luci.chromium.try/linux-swangle-try-x64"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-ubsan-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-viz-rel"
   }
   builders {
@@ -18258,6 +18595,9 @@
     name: "buildbucket/luci.chromium.try/linux-rel-ml"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-ubsan-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-viz-rel"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index d65c4397..35ff79bb 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -5053,6 +5053,16 @@
   }
 }
 job {
+  id: "linux-ubsan-fyi-rel"
+  realm: "ci"
+  schedule: "with 12h interval"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-ubsan-fyi-rel"
+  }
+}
+job {
   id: "linux-ubsan-vptr"
   realm: "ci"
   buildbucket {
@@ -6243,6 +6253,7 @@
   triggers: "linux-swangle-chromium-x64"
   triggers: "linux-swangle-tot-swiftshader-x64"
   triggers: "linux-swangle-x64"
+  triggers: "linux-ubsan-fyi-rel"
   triggers: "linux-ubsan-vptr"
   triggers: "linux-upload-perfetto"
   triggers: "linux-win_cross-rel"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 43e185a..1228a595 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -160,6 +160,7 @@
 exec("./ci/chromium.linux.star")
 exec("./ci/chromium.mac.star")
 exec("./ci/chromium.memory.star")
+exec("./ci/chromium.memory.fyi.star")
 exec("./ci/chromium.packager.star")
 exec("./ci/chromium.rust.star")
 exec("./ci/chromium.swangle.star")
diff --git a/infra/config/subprojects/chromium/ci/chromium.memory.fyi.star b/infra/config/subprojects/chromium/ci/chromium.memory.fyi.star
new file mode 100644
index 0000000..250ad00
--- /dev/null
+++ b/infra/config/subprojects/chromium/ci/chromium.memory.fyi.star
@@ -0,0 +1,53 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Definitions of builders in the chromium.memory.fyi builder group."""
+
+load("//lib/branches.star", "branches")
+load("//lib/builder_config.star", "builder_config")
+load("//lib/builders.star", "os", "reclient")
+load("//lib/ci.star", "ci")
+load("//lib/consoles.star", "consoles")
+
+ci.defaults.set(
+    builder_group = "chromium.memory.fyi",
+    cores = 8,
+    executable = ci.DEFAULT_EXECUTABLE,
+    execution_timeout = ci.DEFAULT_EXECUTION_TIMEOUT,
+    os = os.LINUX_DEFAULT,
+    pool = ci.DEFAULT_POOL,
+    priority = ci.DEFAULT_FYI_PRIORITY,
+    reclient_instance = reclient.instance.DEFAULT_TRUSTED,
+    reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CI,
+    service_account = ci.DEFAULT_SERVICE_ACCOUNT,
+)
+
+consoles.console_view(
+    name = "chromium.memory.fyi",
+    branch_selector = branches.STANDARD_MILESTONE,
+)
+
+# TODO(crbug.com/1394755): Remove this builder after burning down failures
+# and measuring performance to see if we can roll UBSan into ASan.
+ci.builder(
+    name = "linux-ubsan-fyi-rel",
+    branch_selector = branches.MAIN,
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = ["mb"],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+        ),
+    ),
+    console_view_entry = consoles.console_view_entry(
+        category = "linux|ubsan",
+        short_name = "fyi",
+    ),
+    builderless = 1,
+    schedule = "with 12h interval",
+    reclient_jobs = reclient.jobs.DEFAULT,
+)
diff --git a/infra/config/subprojects/chromium/ci/chromium.packager.star b/infra/config/subprojects/chromium/ci/chromium.packager.star
index 21945e2..83f215b 100644
--- a/infra/config/subprojects/chromium/ci/chromium.packager.star
+++ b/infra/config/subprojects/chromium/ci/chromium.packager.star
@@ -14,9 +14,6 @@
     os = os.LINUX_DEFAULT,
     pool = ci.DEFAULT_POOL,
     service_account = "chromium-cipd-builder@chops-service-accounts.iam.gserviceaccount.com",
-
-    # TODO(crbug.com/1362440): remove this.
-    omit_python2 = False,
 )
 
 consoles.console_view(
@@ -50,9 +47,6 @@
     # Every 6 hours starting at 5am UTC.
     schedule = "0 5/6 * * * *",
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
 
 ci.builder(
@@ -75,9 +69,6 @@
     # TODO(crbug.com/1267449): Trigger builds routinely once works fine.
     schedule = "triggered",
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
 
 ci.builder(
@@ -91,9 +82,6 @@
     schedule = "0 7,14,22 * * * *",
     sheriff_rotations = sheriff_rotations.ANDROID,
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
 
 ci.builder(
@@ -135,9 +123,6 @@
     # https://luci-scheduler.appspot.com/jobs/chromium/android-avd-packager
     schedule = "triggered",
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
 
 ci.builder(
@@ -284,9 +269,6 @@
     },
     schedule = "0 7 * * *",
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
 
 ci.builder(
@@ -308,7 +290,4 @@
     ],
     schedule = "0 9 * * *",  # at 1AM or 2AM PT (depending on DST), once a day.
     triggered_by = [],
-
-    # TODO(crbug.com/1366968): Default omit_python2 to True for all builders
-    omit_python2 = True,
 )
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index 12a0fee7..eb0fed49 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -219,6 +219,18 @@
     main_list_view = "try",
 )
 
+# TODO(crbug.com/1394755): Remove this builder after burning down failures
+# and measuring performance to see if we can roll UBSan into ASan.
+try_.builder(
+    name = "linux-ubsan-fyi-rel",
+    branch_selector = branches.MAIN,
+    mirrors = [
+        "ci/linux-ubsan-fyi-rel",
+    ],
+    goma_backend = None,
+    reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CQ,
+)
+
 try_.orchestrator_builder(
     name = "linux-wayland-rel-inverse-fyi",
     compilator = "linux-wayland-rel-compilator",
diff --git a/ios/chrome/browser/crash_report/crash_helper_unittest.mm b/ios/chrome/browser/crash_report/crash_helper_unittest.mm
index 19db142..5c48302 100644
--- a/ios/chrome/browser/crash_report/crash_helper_unittest.mm
+++ b/ios/chrome/browser/crash_report/crash_helper_unittest.mm
@@ -37,12 +37,6 @@
   void TearDown() override {
     crash_reporter::SetCrashpadRunning(false);
     crash_helper::SetEnabled(false);
-
-    // Clear the CrashReporterBreadcrumbObserver singleton state to
-    // avoid polluting other tests.
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
-
     PlatformTest::TearDown();
   }
 
@@ -77,8 +71,8 @@
 
   // Set a max-length breadcrumbs string.
   std::string breadcrumbs(breadcrumbs::kMaxDataLength, 'A');
-  breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-      .SetPreviousSessionEvents({breadcrumbs});
+  breadcrumbs::BreadcrumbManager::GetInstance().SetPreviousSessionEvents(
+      {breadcrumbs});
 }
 
 TEST_F(CrashHelperTest, IsUploadingEnabled) {
diff --git a/ios/chrome/browser/crash_report/crash_report_helper.mm b/ios/chrome/browser/crash_report/crash_report_helper.mm
index c8cd20a..c1196c1 100644
--- a/ios/chrome/browser/crash_report/crash_report_helper.mm
+++ b/ios/chrome/browser/crash_report/crash_report_helper.mm
@@ -16,7 +16,6 @@
 #import "base/strings/sys_string_conversions.h"
 #import "base/time/time.h"
 #import "components/breadcrumbs/core/breadcrumb_manager.h"
-#import "components/breadcrumbs/core/crash_reporter_breadcrumb_observer.h"
 #import "components/upload_list/crash_upload_list.h"
 #import "ios/chrome/browser/crash_report/crash_helper.h"
 #import "ios/chrome/browser/crash_report/crash_keys_helper.h"
@@ -235,8 +234,8 @@
 }
 
 void SetPreviousSessionEvents(const std::vector<std::string>& events) {
-  breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-      .SetPreviousSessionEvents(events);
+  breadcrumbs::BreadcrumbManager::GetInstance().SetPreviousSessionEvents(
+      events);
 }
 
 }  // namespace breakpad
diff --git a/ios/chrome/browser/crash_report/crash_reporter_breadcrumb_observer_unittest.mm b/ios/chrome/browser/crash_report/crash_reporter_breadcrumb_observer_unittest.mm
index dc4bada..e4db98b 100644
--- a/ios/chrome/browser/crash_report/crash_reporter_breadcrumb_observer_unittest.mm
+++ b/ios/chrome/browser/crash_report/crash_reporter_breadcrumb_observer_unittest.mm
@@ -84,12 +84,6 @@
 
   void TearDown() override {
     [[mock_breakpad_controller_ stub] stop];
-
-    // Clear the CrashReporterBreadcrumbObserver singleton state to
-    // avoid polluting other tests.
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
-
     crash_helper::SetEnabled(false);
     PlatformTest::TearDown();
   }
diff --git a/ios/chrome/browser/optimization_guide/optimization_guide_service.mm b/ios/chrome/browser/optimization_guide/optimization_guide_service.mm
index 6f68187..7a5561d 100644
--- a/ios/chrome/browser/optimization_guide/optimization_guide_service.mm
+++ b/ios/chrome/browser/optimization_guide/optimization_guide_service.mm
@@ -130,9 +130,11 @@
         optimization_guide::kOptimizationGuidePredictionModelDownloads);
   }
   if (optimization_guide::features::IsOptimizationTargetPredictionEnabled()) {
+    // TODO(crbug.com/1284363): Support the new prediction model store.
     prediction_manager_ =
         std::make_unique<optimization_guide::PredictionManager>(
-            prediction_model_and_features_store, url_loader_factory,
+            prediction_model_and_features_store,
+            /*prediction_model_store=*/nullptr, url_loader_factory,
             pref_service, off_the_record_, application_locale, models_dir,
             optimization_guide_logger_.get(),
             std::move(background_download_service_provider),
diff --git a/ios/chrome/browser/overlays/public/web_content_area/BUILD.gn b/ios/chrome/browser/overlays/public/web_content_area/BUILD.gn
index 86206be..17ec60a1 100644
--- a/ios/chrome/browser/overlays/public/web_content_area/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/web_content_area/BUILD.gn
@@ -12,6 +12,8 @@
     "http_auth_overlay.mm",
     "java_script_dialog_overlay.h",
     "java_script_dialog_overlay.mm",
+    "java_script_dialog_overlay_utils.h",
+    "java_script_dialog_overlay_utils.mm",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -44,6 +46,7 @@
     "app_launcher_overlay_unittest.mm",
     "http_auth_overlay_unittest.mm",
     "java_script_dialog_overlay_unittest.mm",
+    "java_script_dialog_overlay_utils_unittest.mm",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -56,6 +59,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/overlays",
     "//ios/chrome/browser/ui/dialogs",
+    "//ios/chrome/browser/ui/dialogs:java_script_blocking_fake_web_state",
     "//ios/chrome/browser/ui/elements",
     "//ios/web/public/test/fakes",
     "//testing/gtest",
diff --git a/ios/chrome/browser/overlays/public/web_content_area/DEPS b/ios/chrome/browser/overlays/public/web_content_area/DEPS
index 6fdc9b6..1d7973b 100644
--- a/ios/chrome/browser/overlays/public/web_content_area/DEPS
+++ b/ios/chrome/browser/overlays/public/web_content_area/DEPS
@@ -7,6 +7,8 @@
     "+ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state.h",
     "+ios/chrome/browser/ui/elements/text_field_configuration.h",
   ],
-
+  "^java_script_dialog_overlay_utils.mm": [
+    "+ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state.h",
+  ],
 }
 
diff --git a/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.h b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.h
new file mode 100644
index 0000000..1f435996
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.h
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_WEB_CONTENT_AREA_JAVA_SCRIPT_DIALOG_OVERLAY_UTILS_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_WEB_CONTENT_AREA_JAVA_SCRIPT_DIALOG_OVERLAY_UTILS_H_
+
+#import "ios/chrome/browser/overlays/public/web_content_area/alert_overlay.h"
+
+namespace web {
+class WebState;
+}  // namespace web
+
+namespace java_script_dialog_overlay {
+
+// The index of the OK button in the alert button array.
+constexpr size_t kButtonIndexOk = 0;
+
+// Whether the dialog blocking button should be added for an overlay from
+// `web_state`.
+bool ShouldAddBlockDialogsButton(web::WebState* web_state);
+
+// Returns the button configuration for the button to block further JavaScript
+// dialogs.
+alert_overlays::ButtonConfig BlockDialogsButtonConfig();
+
+// Returns the dialog title for a JavaScript dialog with `config_message`.
+// `is_main_frame` is true iff the dialog is being presented by the main
+// frame.
+NSString* DialogTitle(bool is_main_frame, NSString* config_message);
+
+// Returns the dialog message for a JavaScript dialog with `config_message`.
+// `is_main_frame` is true iff the dialog is being presented by the main frame.
+NSString* DialogMessage(bool is_main_frame, NSString* config_message);
+
+}  // namespace java_script_dialog_overlay
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_WEB_CONTENT_AREA_JAVA_SCRIPT_DIALOG_OVERLAY_UTILS_H_
diff --git a/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.mm b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.mm
new file mode 100644
index 0000000..694924f
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.mm
@@ -0,0 +1,57 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.h"
+
+#import "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ios/web/public/web_state.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using alert_overlays::ButtonConfig;
+
+namespace java_script_dialog_overlay {
+
+bool ShouldAddBlockDialogsButton(web::WebState* web_state) {
+  if (!web_state)
+    return false;
+  JavaScriptDialogBlockingState* blocking_state =
+      JavaScriptDialogBlockingState::FromWebState(web_state);
+  return blocking_state && blocking_state->show_blocking_option();
+}
+
+alert_overlays::ButtonConfig BlockDialogsButtonConfig() {
+  NSString* action_title =
+      l10n_util::GetNSString(IDS_IOS_JAVA_SCRIPT_DIALOG_BLOCKING_BUTTON_TEXT);
+  return ButtonConfig(action_title, UIAlertActionStyleDestructive);
+}
+
+NSString* DialogTitle(bool is_main_frame, NSString* config_message) {
+  // If the dialog is from the main frame, use the message text as the alert's
+  // title.
+  if (is_main_frame) {
+    return config_message;
+  }
+  // Otherwise, use a title indicating that the dialog is from an iframe.
+  return l10n_util::GetNSString(
+      IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME);
+}
+
+NSString* DialogMessage(bool is_main_frame, NSString* config_message) {
+  // If the dialog is from the main frame, the message is the alert's title and
+  // no dialog message is necessary.
+  if (is_main_frame) {
+    return nil;
+  }
+  // If the dialog is from an iframe, a message indicating such is the title, so
+  // the message from the JavaScript is returned here.
+  return config_message;
+}
+
+}  // namespace java_script_dialog_overlay
diff --git a/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils_unittest.mm b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils_unittest.mm
new file mode 100644
index 0000000..2df985a
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils_unittest.mm
@@ -0,0 +1,69 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/overlays/public/web_content_area/java_script_dialog_overlay_utils.h"
+
+#import "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/overlays/public/web_content_area/alert_overlay.h"
+#import "ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h"
+#import "ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using java_script_dialog_overlay::BlockDialogsButtonConfig;
+using java_script_dialog_overlay::DialogMessage;
+using java_script_dialog_overlay::DialogTitle;
+using java_script_dialog_overlay::ShouldAddBlockDialogsButton;
+
+typedef PlatformTest JavaScriptDialogOverlayUtilsTest;
+
+// Tests that ShouldAddBlockDialogsButton returns the expected values.
+TEST_F(JavaScriptDialogOverlayUtilsTest, ShouldAddBlockDialogsButton) {
+  EXPECT_FALSE(ShouldAddBlockDialogsButton(/*web_state=*/nullptr));
+
+  JavaScriptBlockingFakeWebState web_state;
+  // Dialogs shouldn't be blocked if there is no JavaScriptDialogBlockingState
+  // associated with `web_state`.
+  ASSERT_FALSE(ShouldAddBlockDialogsButton(&web_state));
+
+  JavaScriptDialogBlockingState::CreateForWebState(&web_state);
+  // Blocking option if not shown if no dialog has been shown.
+  ASSERT_FALSE(ShouldAddBlockDialogsButton(&web_state));
+  JavaScriptDialogBlockingState::FromWebState(&web_state)
+      ->JavaScriptDialogWasShown();
+  ASSERT_TRUE(ShouldAddBlockDialogsButton(&web_state));
+}
+
+// Tests that BlockDialogsButtonConfig returns the expected button config.
+TEST_F(JavaScriptDialogOverlayUtilsTest, BlockDialogsButtonConfig) {
+  alert_overlays::ButtonConfig config = BlockDialogsButtonConfig();
+  EXPECT_EQ(UIAlertActionStyleDestructive, config.style);
+  EXPECT_NSEQ(
+      l10n_util::GetNSString(IDS_IOS_JAVA_SCRIPT_DIALOG_BLOCKING_BUTTON_TEXT),
+      config.title);
+}
+
+// Tests that DialogTitle and DialogMessage return the expected values.
+TEST_F(JavaScriptDialogOverlayUtilsTest, DialogTitleAndMessage) {
+  NSString* dialog_message = @"message";
+
+  EXPECT_NSEQ(dialog_message, DialogTitle(
+                                  /*is_main_frame=*/true, dialog_message));
+  EXPECT_NSEQ(nil, DialogMessage(
+                       /*is_main_frame=*/true, dialog_message));
+
+  NSString* iframe_title = l10n_util::GetNSString(
+      IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME);
+  EXPECT_NSEQ(iframe_title,
+              DialogTitle(/*is_main_frame=*/false, dialog_message));
+  EXPECT_NSEQ(dialog_message, DialogMessage(
+                                  /*is_main_frame=*/false, dialog_message));
+}
diff --git a/ios/chrome/browser/ui/dialogs/BUILD.gn b/ios/chrome/browser/ui/dialogs/BUILD.gn
index ffb6c90..375af4b6 100644
--- a/ios/chrome/browser/ui/dialogs/BUILD.gn
+++ b/ios/chrome/browser/ui/dialogs/BUILD.gn
@@ -23,6 +23,7 @@
 
   deps = [
     ":dialogs",
+    ":java_script_blocking_fake_web_state",
     "//ios/web",
     "//ios/web/public/test/fakes",
     "//testing/gtest",
@@ -31,6 +32,22 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("java_script_blocking_fake_web_state") {
+  testonly = true
+
+  sources = [
+    "java_script_blocking_fake_web_state.h",
+    "java_script_blocking_fake_web_state.mm",
+  ]
+
+  deps = [
+    "//ios/web",
+    "//ios/web/public/test/fakes",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 source_set("dialogs_internal") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
diff --git a/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h b/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h
new file mode 100644
index 0000000..c6850d7
--- /dev/null
+++ b/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_DIALOGS_JAVA_SCRIPT_BLOCKING_FAKE_WEB_STATE_H_
+#define IOS_CHROME_BROWSER_UI_DIALOGS_JAVA_SCRIPT_BLOCKING_FAKE_WEB_STATE_H_
+
+#import <memory>
+
+#import "ios/web/public/test/fakes/fake_web_state.h"
+
+namespace web {
+class NavigationItem;
+class FakeNavigationManager;
+}  // namespace web
+
+// FakeWebState subclass that allows simulating page loads.
+class JavaScriptBlockingFakeWebState : public web::FakeWebState {
+ public:
+  JavaScriptBlockingFakeWebState();
+  ~JavaScriptBlockingFakeWebState() override;
+
+  // Simulates a navigation by sending a WebStateObserver callback.
+  void SimulateNavigationStarted(bool renderer_initiated,
+                                 bool same_document,
+                                 ui::PageTransition transition,
+                                 bool change_last_committed_item);
+
+ private:
+  web::FakeNavigationManager* manager_ = nullptr;
+  std::unique_ptr<web::NavigationItem> last_committed_item_;
+};
+
+#endif  // IOS_CHROME_BROWSER_UI_DIALOGS_JAVA_SCRIPT_BLOCKING_FAKE_WEB_STATE_H_
diff --git a/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.mm b/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.mm
new file mode 100644
index 0000000..b2b1477c
--- /dev/null
+++ b/ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.mm
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h"
+
+#import "ios/web/public/navigation/navigation_item.h"
+#import "ios/web/public/test/fakes/fake_navigation_context.h"
+#import "ios/web/public/test/fakes/fake_navigation_manager.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+JavaScriptBlockingFakeWebState::JavaScriptBlockingFakeWebState()
+    : web::FakeWebState() {
+  last_committed_item_ = web::NavigationItem::Create();
+  auto manager = std::make_unique<web::FakeNavigationManager>();
+  manager->SetLastCommittedItem(last_committed_item_.get());
+  manager_ = manager.get();
+  SetNavigationManager(std::move(manager));
+}
+
+JavaScriptBlockingFakeWebState::~JavaScriptBlockingFakeWebState() {}
+
+void JavaScriptBlockingFakeWebState::SimulateNavigationStarted(
+    bool renderer_initiated,
+    bool same_document,
+    ui::PageTransition transition,
+    bool change_last_committed_item) {
+  if (change_last_committed_item) {
+    last_committed_item_ = web::NavigationItem::Create();
+    manager_->SetLastCommittedItem(last_committed_item_.get());
+  }
+  web::FakeNavigationContext context;
+  context.SetIsRendererInitiated(renderer_initiated);
+  context.SetIsSameDocument(same_document);
+  OnNavigationStarted(&context);
+}
diff --git a/ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state_unittest.mm b/ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state_unittest.mm
index d60cd2c10..430e163 100644
--- a/ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state_unittest.mm
+++ b/ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state_unittest.mm
@@ -4,13 +4,7 @@
 
 #import "ios/chrome/browser/ui/dialogs/java_script_dialog_blocking_state.h"
 
-#import <memory>
-
-#import "ios/web/public/navigation/navigation_item.h"
-#import "ios/web/public/test/fakes/fake_navigation_context.h"
-#import "ios/web/public/test/fakes/fake_navigation_manager.h"
-#import "ios/web/public/test/fakes/fake_web_state.h"
-#import "ios/web/public/web_state_observer.h"
+#import "ios/chrome/browser/ui/dialogs/java_script_blocking_fake_web_state.h"
 #import "testing/gtest/include/gtest/gtest.h"
 #import "testing/platform_test.h"
 
@@ -18,39 +12,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-// FakeWebState subclass that allows simulating page loads.
-class JavaScriptBlockingFakeWebState : public web::FakeWebState {
- public:
-  JavaScriptBlockingFakeWebState() : web::FakeWebState() {
-    last_committed_item_ = web::NavigationItem::Create();
-    auto manager = std::make_unique<web::FakeNavigationManager>();
-    manager->SetLastCommittedItem(last_committed_item_.get());
-    manager_ = manager.get();
-    SetNavigationManager(std::move(manager));
-  }
-
-  // Simulates a navigation by sending a WebStateObserver callback.
-  void SimulateNavigationStarted(bool renderer_initiated,
-                                 bool same_document,
-                                 ui::PageTransition transition,
-                                 bool change_last_committed_item) {
-    if (change_last_committed_item) {
-      last_committed_item_ = web::NavigationItem::Create();
-      manager_->SetLastCommittedItem(last_committed_item_.get());
-    }
-    web::FakeNavigationContext context;
-    context.SetIsRendererInitiated(renderer_initiated);
-    context.SetIsSameDocument(same_document);
-    OnNavigationStarted(&context);
-  }
-
- private:
-  web::FakeNavigationManager* manager_ = nullptr;
-  std::unique_ptr<web::NavigationItem> last_committed_item_;
-};
-}  // namespace
-
 // Test fixture for testing JavaScriptDialogBlockingState.
 class JavaScriptDialogBlockingStateTest : public PlatformTest {
  protected:
diff --git a/ios/chrome/test/ios_chrome_unit_test_suite.mm b/ios/chrome/test/ios_chrome_unit_test_suite.mm
index 536eca7..c4cf89dc 100644
--- a/ios/chrome/test/ios_chrome_unit_test_suite.mm
+++ b/ios/chrome/test/ios_chrome_unit_test_suite.mm
@@ -63,8 +63,6 @@
     chrome_browser_provider_.reset();
 
     breadcrumbs::BreadcrumbManager::GetInstance().ResetForTesting();
-    breadcrumbs::CrashReporterBreadcrumbObserver::GetInstance()
-        .ResetForTesting();
   }
 
  private:
diff --git a/ipc/ipc_message_unittest.cc b/ipc/ipc_message_unittest.cc
index 80dbdf1..9c7c35d7 100644
--- a/ipc/ipc_message_unittest.cc
+++ b/ipc/ipc_message_unittest.cc
@@ -84,16 +84,16 @@
   expect_value_equals(base::Value(base::Value::BlobStorage({'a', 'b', 'c'})));
 
   {
-    base::Value dict(base::Value::Type::DICTIONARY);
-    dict.SetIntKey("key1", 42);
-    dict.SetStringKey("key2", "hi");
-    expect_value_equals(dict);
+    base::Value::Dict dict;
+    dict.Set("key1", 42);
+    dict.Set("key2", "hi");
+    expect_value_equals(base::Value(std::move(dict)));
   }
   {
-    base::Value list(base::Value::Type::LIST);
-    list.GetList().Append(42);
-    list.GetList().Append("hello");
-    expect_value_equals(list);
+    base::Value::List list;
+    list.Append(42);
+    list.Append("hello");
+    expect_value_equals(base::Value(std::move(list)));
   }
 
   // Also test the corrupt case.
@@ -104,29 +104,29 @@
   EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output));
 }
 
-TEST(IPCMessageTest, DictionaryValue) {
-  base::DictionaryValue input;
-  input.SetKey("null", base::Value());
-  input.SetBoolean("bool", true);
-  input.GetDict().Set("int", 42);
-  input.SetKey("int.with.dot", base::Value(43));
+TEST(IPCMessageTest, ValueDict) {
+  base::Value::Dict input;
+  input.Set("null", base::Value());
+  input.Set("bool", true);
+  input.Set("int", 42);
+  input.Set("int.with.dot", 43);
 
-  base::DictionaryValue subdict;
-  subdict.SetString("str", "forty two");
-  subdict.SetBoolean("bool", false);
+  base::Value::Dict subdict;
+  subdict.Set("str", "forty two");
+  subdict.Set("bool", false);
 
-  base::ListValue sublist;
-  sublist.GetList().Append(42.42);
-  sublist.GetList().Append("forty");
-  sublist.GetList().Append("two");
-  subdict.SetKey("list", std::move(sublist));
+  base::Value::List sublist;
+  sublist.Append(42.42);
+  sublist.Append("forty");
+  sublist.Append("two");
+  subdict.Set("list", std::move(sublist));
 
-  input.SetKey("dict", std::move(subdict));
+  input.Set("dict", std::move(subdict));
 
   IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
   IPC::WriteParam(&msg, input);
 
-  base::DictionaryValue output;
+  base::Value::Dict output;
   base::PickleIterator iter(msg);
   EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
 
diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc
index 21a3837..9307f0a 100644
--- a/ipc/ipc_message_utils.cc
+++ b/ipc/ipc_message_utils.cc
@@ -515,24 +515,6 @@
   }
 }
 
-void ParamTraits<base::DictionaryValue>::Write(base::Pickle* m,
-                                               const param_type& p) {
-  WriteDictValue(p.GetDict(), 0, m);
-}
-
-bool ParamTraits<base::DictionaryValue>::Read(const base::Pickle* m,
-                                              base::PickleIterator* iter,
-                                              param_type* r) {
-  return ReadDictValue(m, iter, 0, &(r->GetDict()));
-}
-
-void ParamTraits<base::DictionaryValue>::Log(const param_type& p,
-                                             std::string* l) {
-  std::string json;
-  base::JSONWriter::Write(p, &json);
-  l->append(json);
-}
-
 void ParamTraits<base::Value::Dict>::Write(base::Pickle* m,
                                            const param_type& p) {
   WriteDictValue(p, 0, m);
diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h
index 7a6422c..4754614 100644
--- a/ipc/ipc_message_utils.h
+++ b/ipc/ipc_message_utils.h
@@ -545,16 +545,6 @@
 // Base ParamTraits ------------------------------------------------------------
 
 template <>
-struct COMPONENT_EXPORT(IPC) ParamTraits<base::DictionaryValue> {
-  typedef base::DictionaryValue param_type;
-  static void Write(base::Pickle* m, const param_type& p);
-  static bool Read(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   param_type* r);
-  static void Log(const param_type& p, std::string* l);
-};
-
-template <>
 struct COMPONENT_EXPORT(IPC) ParamTraits<base::Value::Dict> {
   typedef base::Value::Dict param_type;
   static void Write(base::Pickle* m, const param_type& p);
diff --git a/ipc/ipc_message_utils_unittest.cc b/ipc/ipc_message_utils_unittest.cc
index 5eaff72..63f0dfc 100644
--- a/ipc/ipc_message_utils_unittest.cc
+++ b/ipc/ipc_message_utils_unittest.cc
@@ -233,25 +233,6 @@
   EXPECT_EQ(input, output);
 }
 
-TEST(IPCMessageUtilsTest, LegacyDictValueConversion) {
-  base::DictionaryValue dict_value;
-  dict_value.GetDict().Set("path1", 42);
-  dict_value.GetDict().Set("path2", 84);
-  base::ListValue subvalue;
-  subvalue.Append(1234);
-  subvalue.Append(5678);
-  dict_value.SetKey("path3", std::move(subvalue));
-
-  IPC::Message message;
-  ParamTraits<base::DictionaryValue>::Write(&message, dict_value);
-
-  base::PickleIterator iter(message);
-  base::DictionaryValue read_value;
-  ASSERT_TRUE(
-      ParamTraits<base::DictionaryValue>::Read(&message, &iter, &read_value));
-  EXPECT_EQ(dict_value, read_value);
-}
-
 TEST(IPCMessageUtilsTest, DictValueConversion) {
   base::Value::Dict dict_value;
   dict_value.Set("path1", 42);
diff --git a/media/gpu/test/video_frame_file_writer.cc b/media/gpu/test/video_frame_file_writer.cc
index 12d6c08e..9cfe516f 100644
--- a/media/gpu/test/video_frame_file_writer.cc
+++ b/media/gpu/test/video_frame_file_writer.cc
@@ -208,8 +208,7 @@
 #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
   if (video_frame->storage_type() == VideoFrame::STORAGE_DMABUFS) {
     CHECK(video_frame_mapper_);
-    mapped_frame = video_frame_mapper_->Map(std::move(video_frame),
-                                            PROT_READ | PROT_WRITE);
+    mapped_frame = video_frame_mapper_->Map(std::move(video_frame), PROT_READ);
   }
 #endif  // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
 
@@ -252,8 +251,7 @@
 #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
   if (video_frame->storage_type() == VideoFrame::STORAGE_DMABUFS) {
     CHECK(video_frame_mapper_);
-    mapped_frame = video_frame_mapper_->Map(std::move(video_frame),
-                                            PROT_READ | PROT_WRITE);
+    mapped_frame = video_frame_mapper_->Map(std::move(video_frame), PROT_READ);
   }
 #endif
   if (!mapped_frame) {
diff --git a/media/gpu/test/video_frame_validator.cc b/media/gpu/test/video_frame_validator.cc
index 2beab41091..c1ccb518 100644
--- a/media/gpu/test/video_frame_validator.cc
+++ b/media/gpu/test/video_frame_validator.cc
@@ -169,7 +169,7 @@
       ASSERT_TRUE(video_frame_mapper_) << "Failed to create VideoFrameMapper";
     }
 
-    frame = video_frame_mapper_->Map(std::move(frame), PROT_READ | PROT_WRITE);
+    frame = video_frame_mapper_->Map(std::move(frame), PROT_READ);
     if (!frame) {
       LOG(ERROR) << "Failed to map video frame";
       return;
diff --git a/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn b/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
index d9ed2d9..a96c872 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
+++ b/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
@@ -4,4 +4,6 @@
 
 shared_library("fake_drv_video") {
   sources = [ "fake_drv_video.cc" ]
+
+  deps = [ "//base" ]
 }
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc b/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
index c0298d1..85c7331 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
@@ -3,10 +3,11 @@
 // found in the LICENSE file.
 
 #include <stdbool.h>
-
 #include <va/va.h>
 #include <va/va_backend.h>
 
+#include <set>
+
 VAStatus FakeTerminate(VADriverContextP ctx) {
   return VA_STATUS_SUCCESS;
 }
@@ -148,16 +149,16 @@
                                  VAProfile* profile_list,
                                  int* num_profiles) {
   int i = 0;
-  // TODO(crbug.com/1080871): consider extracting the profiles from
-  // kCapabilities; would need to remove duplicates.
-  profile_list[i++] = VAProfileJPEGBaseline;
-  profile_list[i++] = VAProfileH264ConstrainedBaseline;
-  profile_list[i++] = VAProfileH264Main;
-  profile_list[i++] = VAProfileH264High;
-  profile_list[i++] = VAProfileNone;  // For video processing (e.g. cropping).
-  profile_list[i++] = VAProfileVP8Version0_3;
-  profile_list[i++] = VAProfileVP9Profile0;
-  profile_list[i++] = VAProfileVP9Profile2;
+
+  std::set<VAProfile> unique_profiles;
+  for (auto& capability : kCapabilities)
+    unique_profiles.insert(capability.profile);
+
+  for (auto profile : unique_profiles) {
+    profile_list[i] = profile;
+    i++;
+  }
+
   *num_profiles = i;
 
   return VA_STATUS_SUCCESS;
@@ -202,9 +203,11 @@
   // its |value|.
   bool profile_found = false;
   for (const auto& capability : kCapabilities) {
-    profile_found = capability.profile == profile;
-    if (!(profile_found && capability.entry_point == entrypoint))
+    profile_found = capability.profile == profile || profile_found;
+    if (!(capability.profile == profile &&
+          capability.entry_point == entrypoint)) {
       continue;
+    }
 
     // Clear the |attrib_list|: sometimes it's not initialized.
     for (int attrib = 0; attrib < num_attribs; attrib++)
@@ -231,23 +234,43 @@
                           int num_attribs,
                           VAConfigID* config_id) {
   *config_id = VA_INVALID_ID;
+  bool profile_found = false;
   for (size_t i = 0; i < kCapabilitiesSize; ++i) {
+    profile_found = kCapabilities[i].profile == profile || profile_found;
     if (!(kCapabilities[i].profile == profile &&
           kCapabilities[i].entry_point == entrypoint)) {
       continue;
     }
-    // TODO(crbug.com/1080871): make sure that all entries in |attrib_list| are
-    // also supported by kCapabilities[i].attrib_list.
+    // Checks that the attrib_list is supported by the profile. Assumes the
+    // attributes can be in any order.
+    for (int k = 0; k < num_attribs; k++) {
+      bool attrib_supported = false;
+      for (int j = 0; j < kCapabilities[i].num_attribs; j++) {
+        if (kCapabilities[i].attrib_list[j].type != attrib_list[k].type)
+          continue;
+        // Note that it's not enough to AND the value in |kCapabilities| against
+        // the value provided by the application. We also need to allow for
+        // equality. The reason is that there are some attributes that allow a
+        // value of 0 (e.g., VA_ENC_PACKED_HEADER_NONE for
+        // VAConfigAttribEncPackedHeaders).
+        attrib_supported =
+            (kCapabilities[i].attrib_list[j].value & attrib_list[k].value) ||
+            (kCapabilities[i].attrib_list[j].value == attrib_list[k].value);
+        if (attrib_supported)
+          break;
+      }
+      if (!attrib_supported) {
+        return VA_STATUS_ERROR_ATTR_NOT_SUPPORTED;
+      }
+    }
 
     // |config_id| is also the index in kCapabilities, to simplify things.
     *config_id = i;
     return VA_STATUS_SUCCESS;
   }
 
-  // TODO(crbug.com/1080871): return here VA_STATUS_ERROR_UNSUPPORTED_PROFILE /
-  // VA_STATUS_ERROR_ATTR_NOT_SUPPORTED / VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT
-  // depending on what has happened in the previous for loop.
-  return VA_STATUS_ERROR_ALLOCATION_FAILED;
+  return profile_found ? VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT
+                       : VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
 }
 
 /**
diff --git a/net/cert/pki/DEPS b/net/cert/pki/DEPS
index 3a1afb4d..1149878c 100644
--- a/net/cert/pki/DEPS
+++ b/net/cert/pki/DEPS
@@ -4,7 +4,6 @@
   # TODO(crbug.com/1322914): Remove exceptions.
   "-base",
   "+base/base_paths.h",
-  "+base/callback_forward.h",
   "+base/files/file_util.h",
   "+base/numerics/clamped_math.h",
   "+base/path_service.h",
diff --git a/net/cert/pki/path_builder_unittest.cc b/net/cert/pki/path_builder_unittest.cc
index efe50c6..932f0a6 100644
--- a/net/cert/pki/path_builder_unittest.cc
+++ b/net/cert/pki/path_builder_unittest.cc
@@ -7,7 +7,6 @@
 #include <algorithm>
 
 #include "base/base_paths.h"
-#include "base/callback_forward.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "build/build_config.h"
diff --git a/remoting/protocol/video_channel_state_observer.h b/remoting/protocol/video_channel_state_observer.h
index 6eb0686..21b17c4 100644
--- a/remoting/protocol/video_channel_state_observer.h
+++ b/remoting/protocol/video_channel_state_observer.h
@@ -12,17 +12,8 @@
 
 class VideoChannelStateObserver {
  public:
-  virtual void OnKeyFrameRequested() = 0;
-  virtual void OnTargetBitrateChanged(int bitrate_kbps) = 0;
   virtual void OnTargetFramerateChanged(int framerate) = 0;
 
-  // Called when the encoder has finished encoding a frame, and before it is
-  // passed to WebRTC's registered callback. |frame| may be null if encoding
-  // failed.
-  virtual void OnFrameEncoded(
-      WebrtcVideoEncoder::EncodeResult encode_result,
-      const WebrtcVideoEncoder::EncodedFrame* frame) = 0;
-
   // Called after the encoded frame is sent via the WebRTC registered callback.
   // The result contains the frame ID assigned by WebRTC if successfully sent.
   // This is only called if the encoder successfully returned a non-null
diff --git a/remoting/protocol/webrtc_video_encoder_factory.cc b/remoting/protocol/webrtc_video_encoder_factory.cc
index 5bcc14b..de4c78b 100644
--- a/remoting/protocol/webrtc_video_encoder_factory.cc
+++ b/remoting/protocol/webrtc_video_encoder_factory.cc
@@ -9,10 +9,7 @@
 #include "base/task/thread_pool.h"
 #include "remoting/protocol/video_channel_state_observer.h"
 #include "remoting/protocol/webrtc_video_encoder_wrapper.h"
-#include "third_party/webrtc/api/video_codecs/av1_profile.h"
-#include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_codec.h"
-#include "third_party/webrtc/api/video_codecs/vp9_profile.h"
 
 #if defined(USE_H264_ENCODER)
 #include "remoting/codec/webrtc_video_encoder_gpu.h"
@@ -22,22 +19,12 @@
 
 WebrtcVideoEncoderFactory::WebrtcVideoEncoderFactory()
     : main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
-  formats_.emplace_back("VP8");
-  formats_.emplace_back("VP9");
-  formats_.emplace_back(webrtc::SdpVideoFormat(
-      "VP9", {{webrtc::kVP9FmtpProfileId,
-               webrtc::VP9ProfileToString(webrtc::VP9Profile::kProfile1)}}));
-  formats_.emplace_back("AV1");
-  formats_.emplace_back(webrtc::SdpVideoFormat(
-      "AV1",
-      {{webrtc::kAV1FmtpProfile,
-        webrtc::AV1ProfileToString(webrtc::AV1Profile::kProfile1).data()}}));
 #if defined(USE_H264_ENCODER)
   // This call will query the underlying media classes to determine whether
   // hardware encoding is supported or not. We use a default resolution and
   // framerate so the call doesn't fail due to invalid params.
   if (WebrtcVideoEncoderGpu::IsSupportedByH264({{1920, 1080}, 30})) {
-    formats_.emplace_back("H264");
+    supported_formats_.emplace_back("H264");
   }
 #endif
 }
@@ -57,7 +44,7 @@
 
 std::vector<webrtc::SdpVideoFormat>
 WebrtcVideoEncoderFactory::GetSupportedFormats() const {
-  return formats_;
+  return supported_formats_;
 }
 
 void WebrtcVideoEncoderFactory::SetVideoChannelStateObserver(
diff --git a/remoting/protocol/webrtc_video_encoder_factory.h b/remoting/protocol/webrtc_video_encoder_factory.h
index dad6bfb..d00ab8c 100644
--- a/remoting/protocol/webrtc_video_encoder_factory.h
+++ b/remoting/protocol/webrtc_video_encoder_factory.h
@@ -12,7 +12,10 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "remoting/base/session_options.h"
+#include "third_party/webrtc/api/video_codecs/av1_profile.h"
+#include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_encoder_factory.h"
+#include "third_party/webrtc/api/video_codecs/vp9_profile.h"
 #include "third_party/webrtc/modules/video_coding/include/video_codec_interface.h"
 
 namespace remoting::protocol {
@@ -40,7 +43,19 @@
  private:
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
 
-  std::vector<webrtc::SdpVideoFormat> formats_;
+  std::vector<webrtc::SdpVideoFormat> supported_formats_{
+      webrtc::SdpVideoFormat("VP8"),
+      webrtc::SdpVideoFormat("VP9"),
+      webrtc::SdpVideoFormat(
+          "VP9",
+          {{webrtc::kVP9FmtpProfileId,
+            webrtc::VP9ProfileToString(webrtc::VP9Profile::kProfile1)}}),
+      webrtc::SdpVideoFormat("AV1"),
+      webrtc::SdpVideoFormat(
+          "AV1",
+          {{webrtc::kAV1FmtpProfile,
+            webrtc::AV1ProfileToString(webrtc::AV1Profile::kProfile1)
+                .data()}})};
 
   SessionOptions session_options_;
 
diff --git a/remoting/protocol/webrtc_video_encoder_wrapper.cc b/remoting/protocol/webrtc_video_encoder_wrapper.cc
index 9cde4d3..472ddb48 100644
--- a/remoting/protocol/webrtc_video_encoder_wrapper.cc
+++ b/remoting/protocol/webrtc_video_encoder_wrapper.cc
@@ -389,14 +389,7 @@
     const RateControlParameters& parameters) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  int bitrate_kbps = parameters.bitrate.get_sum_kbps();
-  if (bitrate_kbps_ != bitrate_kbps) {
-    bitrate_kbps_ = bitrate_kbps;
-    main_task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&VideoChannelStateObserver::OnTargetBitrateChanged,
-                       video_channel_state_observer_, bitrate_kbps));
-  }
+  bitrate_kbps_ = parameters.bitrate.get_sum_kbps();
 }
 
 void WebrtcVideoEncoderWrapper::OnRttUpdate(int64_t rtt_ms) {
@@ -497,16 +490,14 @@
     frame_stats_->encode_ended_time = base::TimeTicks::Now();
     frame_stats_->rtt_estimate = rtt_estimate_;
     frame_stats_->bandwidth_estimate_kbps = bitrate_kbps_;
+    // WebrtcFrameSchedulerConstantRate cannot estimate this delay. Set it to 0
+    // so the client can still calculate the derived stats.
+    frame_stats_->send_pending_delay = base::TimeDelta();
     frame->stats = std::move(frame_stats_);
 
     frame->rtp_timestamp = rtp_timestamp_;
   }
 
-  main_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&VideoChannelStateObserver::OnFrameEncoded,
-                                video_channel_state_observer_, encode_result,
-                                frame.get()));
-
   if (encode_result != WebrtcVideoEncoder::EncodeResult::SUCCEEDED) {
     // TODO(crbug.com/1192865): Store this error and communicate it to WebRTC
     // via the next call to Encode(). The VPX encoders are never expected to
diff --git a/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc b/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
index 86db9b90..80f2a95 100644
--- a/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
+++ b/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
@@ -113,15 +113,8 @@
   MockVideoChannelStateObserver() = default;
   ~MockVideoChannelStateObserver() override = default;
 
-  MOCK_METHOD(void, OnKeyFrameRequested, (), (override));
-  MOCK_METHOD(void, OnTargetBitrateChanged, (int bitrate_kbps), (override));
   MOCK_METHOD(void, OnTargetFramerateChanged, (int framerate), (override));
   MOCK_METHOD(void,
-              OnFrameEncoded,
-              (WebrtcVideoEncoder::EncodeResult encode_result,
-               const WebrtcVideoEncoder::EncodedFrame* frame),
-              (override));
-  MOCK_METHOD(void,
               OnEncodedFrameSent,
               (EncodedImageCallback::Result result,
                const WebrtcVideoEncoder::EncodedFrame& frame),
@@ -237,14 +230,6 @@
   PostQuitAndRun();
 }
 
-TEST_F(WebrtcVideoEncoderWrapperTest, NotifiesOnBitrateChanged) {
-  EXPECT_CALL(observer_, OnTargetBitrateChanged(kBitrateBps / 1000));
-
-  auto encoder = InitEncoder(GetVp9Format(), GetVp9Codec());
-
-  PostQuitAndRun();
-}
-
 TEST_F(WebrtcVideoEncoderWrapperTest, NotifiesOnFramerateChanged) {
   EXPECT_CALL(observer_, OnTargetFramerateChanged(42));
 
@@ -280,8 +265,6 @@
                                                  kVideoCodecVP9)))
       .WillOnce(Return(kResultOk));
   EXPECT_CALL(observer_,
-              OnFrameEncoded(WebrtcVideoEncoder::EncodeResult::SUCCEEDED, _));
-  EXPECT_CALL(observer_,
               OnEncodedFrameSent(Field(&EncodedImageCallback::Result::error,
                                        EncodedImageCallback::Result::OK),
                                  _));
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index 6d35bee..c9a377ea 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -379,14 +379,6 @@
                      base::Unretained(core_.get()), position));
 }
 
-void WebrtcVideoStream::OnKeyFrameRequested() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-}
-
-void WebrtcVideoStream::OnTargetBitrateChanged(int bitrate_kbps) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-}
-
 void WebrtcVideoStream::OnTargetFramerateChanged(int framerate) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK_GT(framerate, 0);
@@ -411,19 +403,6 @@
   DCHECK(result.ok()) << "SetParameters() failed: " << result.message();
 }
 
-void WebrtcVideoStream::OnFrameEncoded(
-    WebrtcVideoEncoder::EncodeResult encode_result,
-    const WebrtcVideoEncoder::EncodedFrame* frame) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  // Setting this here allows us to skip a PostTask roundtrip to the scheduler.
-  if (frame && frame->stats) {
-    // WebrtcFrameSchedulerConstantRate cannot estimate this delay. Set it to 0
-    // so the client can still calculate the derived stats.
-    frame->stats->send_pending_delay = base::TimeDelta();
-  }
-}
-
 void WebrtcVideoStream::OnEncodedFrameSent(
     webrtc::EncodedImageCallback::Result result,
     const WebrtcVideoEncoder::EncodedFrame& frame) {
@@ -435,61 +414,62 @@
     return;
   }
 
-  // Send FrameStats message.
-  if (video_stats_dispatcher_ && video_stats_dispatcher_->is_connected()) {
-    // The down-cast is safe, because the |stats| object was originally created
-    // by this class and attached to the frame.
-    const auto* current_frame_stats =
-        static_cast<const FrameStats*>(frame.stats.get());
-    DCHECK(current_frame_stats);
-
-    HostFrameStats stats;
-    stats.bandwidth_estimate_kbps =
-        current_frame_stats->bandwidth_estimate_kbps;
-    stats.rtt_estimate = current_frame_stats->rtt_estimate;
-    stats.send_pending_delay = current_frame_stats->send_pending_delay;
-
-    stats.frame_size = frame.data->size();
-
-    if (!current_frame_stats->input_event_timestamps.is_null()) {
-      stats.capture_pending_delay =
-          current_frame_stats->capture_started_time -
-          current_frame_stats->input_event_timestamps.host_timestamp;
-      stats.latest_event_timestamp =
-          current_frame_stats->input_event_timestamps.client_timestamp;
-    }
-
-    stats.capture_delay = current_frame_stats->capture_delay;
-
-    // Total overhead time for IPC and threading when capturing frames.
-    stats.capture_overhead_delay = (current_frame_stats->capture_ended_time -
-                                    current_frame_stats->capture_started_time) -
-                                   stats.capture_delay;
-
-    stats.encode_pending_delay = current_frame_stats->encode_started_time -
-                                 current_frame_stats->capture_ended_time;
-
-    stats.encode_delay = current_frame_stats->encode_ended_time -
-                         current_frame_stats->encode_started_time;
-
-    stats.capturer_id = current_frame_stats->capturer_id;
-
-    // Convert the frame quantizer to a measure of frame quality between 0 and
-    // 100, for a simple visualization of quality over time. The quantizer from
-    // VP8/VP9 encoder lies within 0-63, with 0 representing a lossless frame.
-    // TODO(crbug.com/891571): Remove |quantizer| from the WebrtcVideoEncoder
-    // interface, and move this logic to the encoders.
-    stats.frame_quality = (63 - frame.quantizer) * 100 / 63;
-
-    stats.screen_id = current_frame_stats->screen_id;
-
-    stats.codec = VideoCodecToProtoEnum(frame.codec);
-    stats.profile = frame.profile;
-    stats.encoded_rect_width = frame.encoded_rect_width;
-    stats.encoded_rect_height = frame.encoded_rect_height;
-
-    video_stats_dispatcher_->OnVideoFrameStats(result.frame_id, stats);
+  // Exit early if we aren't able to send a FrameStats message.
+  if (!video_stats_dispatcher_ || !video_stats_dispatcher_->is_connected()) {
+    return;
   }
+
+  // The down-cast is safe, because the |stats| object was originally created by
+  // this class and attached to the frame.
+  const auto* current_frame_stats =
+      static_cast<const FrameStats*>(frame.stats.get());
+  DCHECK(current_frame_stats);
+
+  HostFrameStats stats;
+  stats.bandwidth_estimate_kbps = current_frame_stats->bandwidth_estimate_kbps;
+  stats.rtt_estimate = current_frame_stats->rtt_estimate;
+  stats.send_pending_delay = current_frame_stats->send_pending_delay;
+
+  stats.frame_size = frame.data->size();
+
+  if (!current_frame_stats->input_event_timestamps.is_null()) {
+    stats.capture_pending_delay =
+        current_frame_stats->capture_started_time -
+        current_frame_stats->input_event_timestamps.host_timestamp;
+    stats.latest_event_timestamp =
+        current_frame_stats->input_event_timestamps.client_timestamp;
+  }
+
+  stats.capture_delay = current_frame_stats->capture_delay;
+
+  // Total overhead time for IPC and threading when capturing frames.
+  stats.capture_overhead_delay = (current_frame_stats->capture_ended_time -
+                                  current_frame_stats->capture_started_time) -
+                                 stats.capture_delay;
+
+  stats.encode_pending_delay = current_frame_stats->encode_started_time -
+                               current_frame_stats->capture_ended_time;
+
+  stats.encode_delay = current_frame_stats->encode_ended_time -
+                       current_frame_stats->encode_started_time;
+
+  stats.capturer_id = current_frame_stats->capturer_id;
+
+  // Convert the frame quantizer to a measure of frame quality between 0 and
+  // 100, for a simple visualization of quality over time. The quantizer from
+  // VP8/VP9 encoder lies within 0-63, with 0 representing a lossless frame.
+  // TODO(crbug.com/891571): Remove |quantizer| from the WebrtcVideoEncoder
+  // interface, and move this logic to the encoders.
+  stats.frame_quality = (63 - frame.quantizer) * 100 / 63;
+
+  stats.screen_id = current_frame_stats->screen_id;
+
+  stats.codec = VideoCodecToProtoEnum(frame.codec);
+  stats.profile = frame.profile;
+  stats.encoded_rect_width = frame.encoded_rect_width;
+  stats.encoded_rect_height = frame.encoded_rect_height;
+
+  video_stats_dispatcher_->OnVideoFrameStats(result.frame_id, stats);
 }
 
 void WebrtcVideoStream::OnSinkAddedOrUpdated(const rtc::VideoSinkWants& wants) {
diff --git a/remoting/protocol/webrtc_video_stream.h b/remoting/protocol/webrtc_video_stream.h
index c122e0d..be2493c 100644
--- a/remoting/protocol/webrtc_video_stream.h
+++ b/remoting/protocol/webrtc_video_stream.h
@@ -71,11 +71,7 @@
   void SetMouseCursorPosition(const webrtc::DesktopVector& position) override;
 
   // VideoChannelStateObserver interface.
-  void OnKeyFrameRequested() override;
-  void OnTargetBitrateChanged(int bitrate_kbps) override;
   void OnTargetFramerateChanged(int framerate) override;
-  void OnFrameEncoded(WebrtcVideoEncoder::EncodeResult encode_result,
-                      const WebrtcVideoEncoder::EncodedFrame* frame) override;
   void OnEncodedFrameSent(
       webrtc::EncodedImageCallback::Result result,
       const WebrtcVideoEncoder::EncodedFrame& frame) override;
diff --git a/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
index d85f940..b8ec997c 100644
--- a/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
+++ b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
@@ -4,7 +4,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <sys/inotify.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -16,19 +15,13 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/files/file.h"
 #include "base/files/file_path.h"
-#include "base/files/file_path_watcher.h"
-#include "base/files/file_path_watcher_inotify.h"
-#include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/posix/eintr_wrapper.h"
-#include "base/run_loop.h"
 #include "base/test/bind.h"
-#include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
 #include "sandbox/linux/bpf_dsl/policy.h"
@@ -268,7 +261,6 @@
   virtual int Mkdir(const char* pathname, mode_t mode) = 0;
   virtual int Rmdir(const char* path) = 0;
   virtual int Unlink(const char* path) = 0;
-  virtual int InotifyAddWatch(int fd, const char* path, uint32_t mask) = 0;
 };
 
 class IPCSyscaller : public Syscaller {
@@ -315,11 +307,6 @@
     return broker_->GetBrokerClientSignalBased()->Unlink(path);
   }
 
-  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
-    return broker_->GetBrokerClientSignalBased()->InotifyAddWatch(fd, path,
-                                                                  mask);
-  }
-
  private:
   raw_ptr<BrokerProcess> broker_;
 };
@@ -398,13 +385,6 @@
       return -errno;
     return ret;
   }
-
-  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
-    int ret = syscall(__NR_inotify_add_watch, fd, path, mask);
-    if (ret < 0)
-      return -errno;
-    return ret;
-  }
 };
 #endif  // defined(DIRECT_SYSCALLER_ENABLED)
 
@@ -468,21 +448,9 @@
       return -errno;
     return ret;
   }
-
-  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
-    int ret = inotify_add_watch(fd, path, mask);
-    if (ret < 0)
-      return -errno;
-    return ret;
-  }
 };
 
-enum class SyscallerType {
-  IPCSyscaller = 0,
-  DirectSyscaller,
-  LibcSyscaller,
-  NoSyscaller
-};
+enum class SyscallerType { IPCSyscaller = 0, DirectSyscaller, LibcSyscaller };
 
 // The testing infrastructure for the broker integration tests is built on the
 // same infrastructure that BPF_TEST or SANDBOX_TEST uses. Each individual test
@@ -631,9 +599,6 @@
       case SyscallerType::LibcSyscaller:
         syscaller_ = std::make_unique<LibcSyscaller>();
         break;
-      case SyscallerType::NoSyscaller:
-        syscaller_ = nullptr;
-        break;
     }
   }
 
@@ -1160,7 +1125,7 @@
     // Create a conflict for the temp filename.
     base::ScopedFD fd(
         open(existing_temp_file_str_.c_str(), O_RDWR | O_CREAT, 0600));
-    ASSERT_GE(fd.get(), 0);
+    BPF_ASSERT_GE(fd.get(), 0);
   }
 
   BrokerParams ChildSetUpPreSandbox() override {
@@ -2570,276 +2535,5 @@
   RunAllBrokerTests<UnlinkFileRWCPermissionsDelegate>();
 }
 
-// Parent class for the inotify_add_watch() tests.
-class InotifyAddWatchDelegate : public BrokerTestDelegate {
- public:
-  const uint32_t kBadMask =
-      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
-  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
-
-  static constexpr char kNestedTempDirName[] = "nested_temp_dir";
-  static constexpr char kBadPrefixName[] = "nested_t";
-
-  void ParentSetUp() override {
-    // Create two nested temp dirs.
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDirUnderPath(
-        base::FilePath(kTempDirForTests)));
-    temp_dir_str_ = temp_dir_.GetPath().MaybeAsASCII();
-    ASSERT_FALSE(temp_dir_str_.empty());
-
-    ASSERT_TRUE(nested_temp_dir_.Set(
-        temp_dir_.GetPath().AppendASCII(kNestedTempDirName)));
-    nested_temp_dir_str_ = nested_temp_dir_.GetPath().MaybeAsASCII();
-    ASSERT_FALSE(nested_temp_dir_str_.empty());
-
-    temp_file_ = base::CreateAndOpenTemporaryFileInDir(
-        nested_temp_dir_.GetPath(), &temp_file_path_);
-    temp_file_path_str_ = temp_file_path_.MaybeAsASCII();
-    ASSERT_FALSE(temp_file_path_str_.empty());
-  }
-
- protected:
-  // Parent temp directory.
-  base::ScopedTempDir temp_dir_;
-  std::string temp_dir_str_;
-
-  // A directory nested under |temp_dir_|.
-  base::ScopedTempDir nested_temp_dir_;
-  std::string nested_temp_dir_str_;
-
-  // In |nested_temp_dir_|.
-  base::FilePath temp_file_path_;
-  std::string temp_file_path_str_;
-  base::File temp_file_;
-};
-
-// Try inotify_add_watch() without the relevant broker command.
-class InotifyAddWatchNoCommandDelegate final : public InotifyAddWatchDelegate {
- public:
-  BrokerParams ChildSetUpPreSandbox() override {
-    BrokerParams params;
-    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({});
-    params.permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            temp_file_path_str_),
-        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
-                                                       "/")};
-    return params;
-  }
-
-  void RunTestInSandboxedChild(Syscaller* syscaller) override {
-    base::ScopedFD inotify_instance(inotify_init());
-    BPF_ASSERT(inotify_instance.is_valid());
-    BPF_ASSERT_EQ(
-        -kFakeErrnoSentinel,
-        syscaller->InotifyAddWatch(inotify_instance.get(),
-                                   nested_temp_dir_str_.c_str(), kGoodMask));
-  }
-};
-
-TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoCommand) {
-  RunAllBrokerTests<InotifyAddWatchNoCommandDelegate>();
-}
-
-// Try inotify_add_watch() without the relevant permissions.
-class InotifyAddWatchNoPermissionsDelegate final
-    : public InotifyAddWatchDelegate {
- public:
-  BrokerParams ChildSetUpPreSandbox() override {
-    BrokerParams params;
-    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
-        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH});
-    params.permissions = {};
-    return params;
-  }
-
-  void RunTestInSandboxedChild(Syscaller* syscaller) override {
-    base::ScopedFD inotify_instance(inotify_init());
-    BPF_ASSERT(inotify_instance.is_valid());
-    BPF_ASSERT_EQ(
-        -kFakeErrnoSentinel,
-        syscaller->InotifyAddWatch(inotify_instance.get(),
-                                   nested_temp_dir_str_.c_str(), kGoodMask));
-  }
-};
-
-TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoPermissions) {
-  RunAllBrokerTests<InotifyAddWatchNoPermissionsDelegate>();
-}
-
-// Try inotify_add_watch() with a variety of bad arguments.
-class InotifyAddWatchBadArgumentsDelegate final
-    : public InotifyAddWatchDelegate {
- public:
-  void ParentSetUp() override {
-    InotifyAddWatchDelegate::ParentSetUp();
-
-    ASSERT_TRUE(
-        other_temp_dir_.Set(temp_dir_.GetPath().AppendASCII("other_temp_dir")));
-    other_temp_dir_str_ = other_temp_dir_.GetPath().MaybeAsASCII();
-    ASSERT_FALSE(other_temp_dir_str_.empty());
-
-    base::FilePath bad_prefix = temp_dir_.GetPath().AppendASCII(kBadPrefixName);
-    bad_prefix_str_ = bad_prefix.MaybeAsASCII();
-    ASSERT_FALSE(bad_prefix_str_.empty());
-  }
-
-  BrokerParams ChildSetUpPreSandbox() override {
-    BrokerParams params;
-    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
-        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH});
-    params.permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            temp_file_path_str_)};
-    return params;
-  }
-
-  void RunTestInSandboxedChild(Syscaller* syscaller) override {
-    base::ScopedFD inotify_instance(inotify_init());
-    BPF_ASSERT(inotify_instance.is_valid());
-    // Watch the correct directory with bad flags.
-    BPF_ASSERT_EQ(
-        -kFakeErrnoSentinel,
-        syscaller->InotifyAddWatch(inotify_instance.get(),
-                                   nested_temp_dir_str_.c_str(), kBadMask));
-
-    // Try to watch an unintended directory, should fail.
-    BPF_ASSERT_EQ(
-        -kFakeErrnoSentinel,
-        syscaller->InotifyAddWatch(inotify_instance.get(),
-                                   other_temp_dir_str_.c_str(), kGoodMask));
-
-    // Try to access a prefix that isn't a full directory.
-    BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->InotifyAddWatch(
-                                           inotify_instance.get(),
-                                           bad_prefix_str_.c_str(), kGoodMask));
-  }
-
- protected:
-  // Another directory nested under |temp_dir_| with no sandbox permissions.
-  base::ScopedTempDir other_temp_dir_;
-  std::string other_temp_dir_str_;
-
-  // A prefix of |nested_temp_dir_| that doesn't match a valid directory.
-  std::string bad_prefix_str_;
-};
-
-TEST(BrokerProcessIntegrationTest, InotifyAddWatchBadArguments) {
-  RunAllBrokerTests<InotifyAddWatchBadArgumentsDelegate>();
-}
-
-// Use inottify_add_watch() successfully and verify it actually works.
-class InotifyAddWatchSuccessDelegate final : public InotifyAddWatchDelegate {
- public:
-  BrokerParams ChildSetUpPreSandbox() override {
-    BrokerParams params;
-    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
-        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH,
-         syscall_broker::COMMAND_UNLINK});
-    params.permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            temp_file_path_str_),
-        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
-                                                       "/")};
-    return params;
-  }
-
-  void RunTestInSandboxedChild(Syscaller* syscaller) override {
-    base::ScopedFD inotify_instance(inotify_init());
-    BPF_ASSERT(inotify_instance.is_valid());
-    // This inotify_add_watch() call should succeed.
-    int wd = syscaller->InotifyAddWatch(
-        inotify_instance.get(), nested_temp_dir_str_.c_str(), kGoodMask);
-    BPF_ASSERT_GE(wd, 0);
-
-    // Unlinking the file generates an inotify notification.
-    BPF_ASSERT_GE(unlink(temp_file_path_str_.c_str()), 0);
-
-    // Read one inotify message and verify it names the correct watch descriptor
-    // |wd|. The test will timeout if no inotify notifications are ever
-    // generated.
-    std::vector<char> buf(4096);
-    BPF_ASSERT_GE(read(inotify_instance.get(), buf.data(), buf.size()), 0);
-    struct inotify_event* event =
-        reinterpret_cast<struct inotify_event*>(buf.data());
-    BPF_ASSERT_EQ(event->wd, wd);
-
-    // Removing the watch should succeed.
-    BPF_ASSERT_GE(inotify_rm_watch(inotify_instance.get(), wd), 0);
-  }
-};
-
-TEST(BrokerProcessIntegrationTest, InotifyAddWatchSuccess) {
-  RunAllBrokerTests<InotifyAddWatchSuccessDelegate>();
-}
-
-// Tests base::FilePathWatcher which uses inotify on Linux.
-// This is used in the network service sandbox.
-class BaseFilePathWatcherDelegate final : public InotifyAddWatchDelegate {
- public:
-  BrokerParams ChildSetUpPreSandbox() override {
-    // Prewarm file accesses.
-    base::GetMaxNumberOfInotifyWatches();
-
-    BrokerParams params;
-    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
-        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH,
-         syscall_broker::COMMAND_OPEN});
-    params.permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            temp_file_path_str_),
-        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
-                                                       "/")};
-    return params;
-  }
-
-  void RunTestInSandboxedChild(Syscaller* syscaller) override {
-    base::test::SingleThreadTaskEnvironment task_environment(
-        base::test::TaskEnvironment::MainThreadType::IO);
-
-    // Watch the file and wait for a notification about that file from
-    // FilePathWatcher.
-    base::RunLoop run_loop;
-    base::FilePathWatcher file_watcher_;
-    BPF_ASSERT(file_watcher_.Watch(
-        temp_file_path_, base::FilePathWatcher::Type::kNonRecursive,
-        base::BindLambdaForTesting([&](const base::FilePath& path, bool error) {
-          BPF_ASSERT_EQ(temp_file_path_, path);
-          run_loop.Quit();
-        })));
-
-    // Our inotify file path watcher requires a file to be opened for writing
-    // *after* adding the watch, and then closed, in order to generate a
-    // notification. The actual call to Write() isn't even strictly necessary.
-    // Another way to generate a notification is
-    // base::DeleteFile(temp_file_path_).
-    base::File temp_file_again(temp_file_path_, base::File::FLAG_OPEN |
-                                                    base::File::FLAG_READ |
-                                                    base::File::FLAG_WRITE);
-    char buf2[] = "a";
-    BPF_ASSERT_EQ(temp_file_again.Write(0, buf2, sizeof(buf2)), sizeof(buf2));
-    temp_file_again.Flush();
-    temp_file_again.Close();
-    // Wait until we receive a notification about the file modification.
-    // Failure results in a test timeout.
-    run_loop.Run();
-  }
-};
-
-TEST(BrokerProcessIntegrationTest, BaseFilePathWatcherInotifyTest) {
-  const std::vector<BrokerTestConfiguration> inotify_test_configs = {
-      {"FastCheckInClient_NoSyscaller", true, SyscallerType::NoSyscaller,
-       BrokerType::SIGNAL_BASED},
-      {"NoFastCheckInClient_NoSyscaller", false, SyscallerType::NoSyscaller,
-       BrokerType::SIGNAL_BASED},
-  };
-
-  for (const BrokerTestConfiguration& test_config : inotify_test_configs) {
-    SCOPED_TRACE(test_config.test_name);
-    auto test_delegate = std::make_unique<BaseFilePathWatcherDelegate>();
-    RunSingleBrokerTest(test_delegate.get(), test_config);
-  }
-}
-
 #endif  // !defined(THREAD_SANITIZER)
 }  // namespace sandbox
diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc
index 0aace79..05d99ba5 100644
--- a/sandbox/linux/syscall_broker/broker_client.cc
+++ b/sandbox/linux/syscall_broker/broker_client.cc
@@ -207,38 +207,6 @@
   return PathOnlySyscall(COMMAND_UNLINK, path);
 }
 
-int BrokerClient::InotifyAddWatch(int fd,
-                                  const char* pathname,
-                                  uint32_t mask) const {
-  if (!pathname)
-    return -EFAULT;
-
-  if (fast_check_in_client_ &&
-      !CommandInotifyAddWatchIsSafe(policy_->allowed_command_set,
-                                    *policy_->file_permissions, pathname, mask,
-                                    nullptr)) {
-    return -policy_->file_permissions->denied_errno();
-  }
-
-  BrokerSimpleMessage message;
-  RAW_CHECK(message.AddIntToMessage(COMMAND_INOTIFY_ADD_WATCH));
-  RAW_CHECK(message.AddStringToMessage(pathname));
-  RAW_CHECK(message.AddIntToMessage(mask));
-
-  BrokerSimpleMessage reply;
-  ssize_t msg_len = message.SendRecvMsgWithFlagsMultipleFds(
-      ipc_channel_.get(), 0, base::span<const int>(&fd, 1), {}, &reply);
-
-  if (msg_len < 0)
-    return msg_len;
-
-  int return_value = -1;
-  if (!reply.ReadInt(&return_value))
-    return -ENOMEM;
-
-  return return_value;
-}
-
 int BrokerClient::PathOnlySyscall(BrokerCommand syscall_type,
                                   const char* pathname) const {
   BrokerSimpleMessage message;
diff --git a/sandbox/linux/syscall_broker/broker_client.h b/sandbox/linux/syscall_broker/broker_client.h
index 9d0fdee..f96e77e 100644
--- a/sandbox/linux/syscall_broker/broker_client.h
+++ b/sandbox/linux/syscall_broker/broker_client.h
@@ -68,9 +68,6 @@
              bool follow_links,
              struct kernel_stat64* sb) const override;
   int Unlink(const char* unlink) const override;
-  int InotifyAddWatch(int fd,
-                      const char* pathname,
-                      uint32_t mask) const override;
 
   const BrokerSandboxConfig& policy() const { return *policy_; }
 
diff --git a/sandbox/linux/syscall_broker/broker_command.cc b/sandbox/linux/syscall_broker/broker_command.cc
index 79d27b0d..340305d 100644
--- a/sandbox/linux/syscall_broker/broker_command.cc
+++ b/sandbox/linux/syscall_broker/broker_command.cc
@@ -95,15 +95,5 @@
                                            filename_to_use, nullptr);
 }
 
-bool CommandInotifyAddWatchIsSafe(const BrokerCommandSet& command_set,
-                                  const BrokerPermissionList& policy,
-                                  const char* requested_filename,
-                                  uint32_t mask,
-                                  const char** filename_to_use) {
-  return command_set.test(COMMAND_INOTIFY_ADD_WATCH) &&
-         policy.GetFileNameIfAllowedToInotifyAddWatch(requested_filename, mask,
-                                                      filename_to_use);
-}
-
 }  // namespace syscall_broker
 }  // namespace sandbox
diff --git a/sandbox/linux/syscall_broker/broker_command.h b/sandbox/linux/syscall_broker/broker_command.h
index 468d9056..514c5f6 100644
--- a/sandbox/linux/syscall_broker/broker_command.h
+++ b/sandbox/linux/syscall_broker/broker_command.h
@@ -42,10 +42,9 @@
   COMMAND_STAT,
   COMMAND_STAT64,
   COMMAND_UNLINK,
-  COMMAND_INOTIFY_ADD_WATCH,
 
   // NOTE: update when adding new commands.
-  COMMAND_MAX = COMMAND_INOTIFY_ADD_WATCH
+  COMMAND_MAX = COMMAND_UNLINK
 };
 
 using BrokerCommandSet = std::bitset<COMMAND_MAX + 1>;
@@ -107,12 +106,6 @@
                          const char* requested_filename,
                          const char** filename_to_use);
 
-bool CommandInotifyAddWatchIsSafe(const BrokerCommandSet& command_set,
-                                  const BrokerPermissionList& policy,
-                                  const char* requested_filename,
-                                  uint32_t mask,
-                                  const char** filename_to_use);
-
 }  // namespace syscall_broker
 }  // namespace sandbox
 
diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc
index 02fbf8b6..b399455 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission.cc
+++ b/sandbox/linux/syscall_broker/broker_file_permission.cc
@@ -7,7 +7,6 @@
 #include <fcntl.h>
 #include <stddef.h>
 #include <string.h>
-#include <sys/inotify.h>
 #include <unistd.h>
 
 #include <ostream>
@@ -207,9 +206,8 @@
   return true;
 }
 
-bool BrokerFilePermission::CheckStatWithIntermediates(
-    const char* requested_filename,
-    const char** file_to_access) const {
+bool BrokerFilePermission::CheckStat(const char* requested_filename,
+                                     const char** file_to_access) const {
   if (!ValidatePath(requested_filename))
     return false;
 
@@ -221,64 +219,26 @@
   if (!(allow_create() || allow_stat_with_intermediates()))
     return false;
 
-  // |allow_stat_with_intermediates()| can match on the full path, and
-  // |allow_create()| only matches a leading directory.
-  if (!CheckIntermediates(
-          requested_filename,
-          /*can_match_full_path=*/allow_stat_with_intermediates()))
-    return false;
-
-  if (file_to_access)
-    *file_to_access = requested_filename;
-
-  return true;
-}
-
-bool BrokerFilePermission::CheckInotifyAddWatchWithIntermediates(
-    const char* requested_filename,
-    uint32_t mask,
-    const char** file_to_inotify_add_watch) const {
-  if (!allow_inotify_add_watch_with_intermediates())
-    return false;
-
-  if (!ValidatePath(requested_filename))
-    return false;
-
-  // Allow only this exact mask as it is used by
-  // base/files/file_path_watcher_inotify.cc.
-  if (mask != (IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE |
-               IN_ONLYDIR))
-    return false;
-
-  if (!CheckIntermediates(requested_filename,
-                          /*can_match_full_path=*/true))
-    return false;
-
-  if (file_to_inotify_add_watch)
-    *file_to_inotify_add_watch = requested_filename;
-
-  return true;
-}
-
-bool BrokerFilePermission::CheckIntermediates(const char* requested_filename,
-                                              bool can_match_full_path) const {
-  // NOTE: ValidatePath proves requested_length != 0 and |requested_filename| is
-  // absolute.
+  // NOTE: ValidatePath proved requested_length != 0;
   size_t requested_length = strlen(requested_filename);
   CHECK(requested_length);
-  CHECK(requested_filename[0] == '/');
 
   // Special case for root: only one slash, otherwise must have a second
   // slash in the right spot to avoid substring matches.
-  return (requested_length == 1 && requested_filename[0] == '/') ||
-         // If this permission can match the full path, compare directly to the
-         // requested filename.
-         (can_match_full_path && path_ == requested_filename) ||
-         // Check whether |requested_filename| matches a leading directory of
-         // |path_|.
-         (requested_length < path_.length() &&
-          memcmp(path_.c_str(), requested_filename, requested_length) == 0 &&
-          path_.c_str()[requested_length] == '/');
+  // |allow_stat_with_intermediates()| can match on the full path, and
+  // |allow_create()| only matches a leading directory.
+  if ((requested_length == 1 && requested_filename[0] == '/') ||
+      (allow_stat_with_intermediates() && path_ == requested_filename) ||
+      (requested_length < path_.length() &&
+       memcmp(path_.c_str(), requested_filename, requested_length) == 0 &&
+       path_.c_str()[requested_length] == '/')) {
+    if (file_to_access)
+      *file_to_access = requested_filename;
+
+    return true;
+  }
+
+  return false;
 }
 
 const char* BrokerFilePermission::GetErrorMessageForTests() {
@@ -296,10 +256,9 @@
   if (temporary_only())
     CHECK(allow_create()) << GetErrorMessageForTests();
 
-  // Recursive paths must have a trailing slash, absolutes must not (except
-  // root).
-  const char last_char = path_.back();
-  if (recursive() || path_.length() == 1)
+  // Recursive paths must have a trailing slash, absolutes must not.
+  const char last_char = *(path_.rbegin());
+  if (recursive())
     CHECK(last_char == '/') << GetErrorMessageForTests();
   else
     CHECK(last_char != '/') << GetErrorMessageForTests();
@@ -317,8 +276,7 @@
     ReadPermission read_perm,
     WritePermission write_perm,
     CreatePermission create_perm,
-    StatWithIntermediatesPermission stat_perm,
-    InotifyAddWatchWithIntermediatesPermission inotify_perm)
+    StatWithIntermediatesPermission stat_perm)
     : path_(std::move(path)) {
   flags_[kRecursiveBitPos] = recurse_opt == RecursionOption::kRecursive;
   flags_[kTemporaryOnlyBitPos] =
@@ -328,9 +286,6 @@
   flags_[kAllowCreateBitPos] = create_perm == CreatePermission::kAllowCreate;
   flags_[kAllowStatWithIntermediatesBitPos] =
       stat_perm == StatWithIntermediatesPermission::kAllowStatWithIntermediates;
-  flags_[kAllowInotifyAddWatchWithIntermediates] =
-      inotify_perm == InotifyAddWatchWithIntermediatesPermission::
-                          kAllowInotifyAddWatchWithIntermediates;
 
   DieOnInvalidPermission();
 }
diff --git a/sandbox/linux/syscall_broker/broker_file_permission.h b/sandbox/linux/syscall_broker/broker_file_permission.h
index a9577adf..b167ffb0 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission.h
+++ b/sandbox/linux/syscall_broker/broker_file_permission.h
@@ -25,10 +25,6 @@
   kBlockStatWithIntermediates = 0,
   kAllowStatWithIntermediates
 };
-enum class InotifyAddWatchWithIntermediatesPermission {
-  kBlockInotifyAddWatchWithIntermediates = 0,
-  kAllowInotifyAddWatchWithIntermediates
-};
 
 // BrokerFilePermission defines a path for allowlisting.
 // Pick the correct static factory method to create a permission.
@@ -50,9 +46,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission ReadOnlyRecursive(const std::string& path) {
@@ -60,9 +54,7 @@
         path, RecursionOption::kRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission WriteOnly(const std::string& path) {
@@ -70,9 +62,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kBlockRead, WritePermission::kAllowWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission ReadWrite(const std::string& path) {
@@ -80,9 +70,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreate(const std::string& path) {
@@ -90,9 +78,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreateRecursive(
@@ -101,9 +87,7 @@
         path, RecursionOption::kRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   // Temporary files must always be newly created and do not confer rights to
@@ -114,9 +98,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kTemporaryOnly,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreateTemporaryRecursive(
@@ -125,9 +107,7 @@
         path, RecursionOption::kRecursive, PersistenceOption::kTemporaryOnly,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
   }
 
   static BrokerFilePermission StatOnlyWithIntermediateDirs(
@@ -136,20 +116,7 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kBlockRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kAllowStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kBlockInotifyAddWatchWithIntermediates);
-  }
-
-  static BrokerFilePermission InotifyAddWatchWithIntermediateDirs(
-      const std::string& path) {
-    return BrokerFilePermission(
-        path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
-        ReadPermission::kBlockRead, WritePermission::kBlockWrite,
-        CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
-        InotifyAddWatchWithIntermediatesPermission::
-            kAllowInotifyAddWatchWithIntermediates);
+        StatWithIntermediatesPermission::kAllowStatWithIntermediates);
   }
 
   // Returns true if |requested_filename| is allowed to be accessed
@@ -182,29 +149,13 @@
   // by this permission as per stat(2). Differs from CheckAccess()
   // in that if create permission is granted to a file, we permit
   // stat() on all of its leading components.
-  // If |file_to_access| is not NULL, it is set to point to either
+  // If |file_to_open| is not NULL, it is set to point to either
   // the |requested_filename| in the case of a recursive match,
   // or a pointer to the matched path in the allowlist if an absolute
   // match.
   // Async signal safe if |file_to_access| is NULL
-  bool CheckStatWithIntermediates(const char* requested_filename,
-                                  const char** file_to_access) const;
-
-  // Returns true if |requested_filename| is allowed by this permission to be
-  // added to an inotify instance's watch list by inotify_add_watch(2), with the
-  // specific |mask|. Differs from CheckAccess() in that if inotify_add_watch
-  // permission is granted to a file, we permit inotify_add_watch() on all of
-  // its leading components.
-  //
-  // If |file_to_inotify_add_watch| is not NULL, it is set to point to either
-  // the |requested_filename| in the case of a recursive match, or a pointer to
-  // the matched path in the allowlist if an absolute match.
-  //
-  // Async signal safe if |file_to_inotify_add_watch| is NULL
-  bool CheckInotifyAddWatchWithIntermediates(
-      const char* requested_filename,
-      uint32_t mask,
-      const char** file_to_inotify_add_watch) const;
+  bool CheckStat(const char* requested_filename,
+                 const char** file_to_access) const;
 
  private:
   friend class BrokerFilePermissionTester;
@@ -216,9 +167,8 @@
     kAllowWriteBitPos,
     kAllowCreateBitPos,
     kAllowStatWithIntermediatesBitPos,
-    kAllowInotifyAddWatchWithIntermediates,
 
-    kMaxValueBitPos = kAllowInotifyAddWatchWithIntermediates
+    kMaxValueBitPos = kAllowStatWithIntermediatesBitPos
   };
 
   // NOTE: Validates the permission and dies if invalid!
@@ -228,8 +178,7 @@
                        ReadPermission read_perm,
                        WritePermission write_perm,
                        CreatePermission create_perm,
-                       StatWithIntermediatesPermission stat_perm,
-                       InotifyAddWatchWithIntermediatesPermission inotify_perm);
+                       StatWithIntermediatesPermission stat_perm);
 
   // Allows construction from the raw bitset.
   BrokerFilePermission(std::string path, uint64_t flags);
@@ -253,10 +202,6 @@
     return flags_.test(kAllowStatWithIntermediatesBitPos);
   }
 
-  bool allow_inotify_add_watch_with_intermediates() const {
-    return flags_.test(kAllowInotifyAddWatchWithIntermediates);
-  }
-
   // ValidatePath checks |path| and returns true if these conditions are met
   // * Greater than 0 length
   // * Is an absolute path
@@ -273,14 +218,6 @@
                            int mode,
                            const char** file_to_access) const;
 
-  // Helper routine for CheckStatWithIntermediates() and
-  // CheckInotifyAddWatchWithIntermediates() to return true if one of the
-  // following is true:
-  // 1. |requested_filename| matches a leading directory of |path_|.
-  // 2. |can_match_full_path| is true and |path_| == |requested_filename|.
-  bool CheckIntermediates(const char* requested_filename,
-                          bool can_match_full_path) const;
-
   // Used in by BrokerFilePermissionTester for tests.
   static const char* GetErrorMessageForTests();
 
diff --git a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
index 65d3d640..dd3df5a5 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
+++ b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <fcntl.h>
 #include <string.h>
-#include <sys/inotify.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -266,65 +265,23 @@
   const char kLeading1[] = "/";
   const char kLeading2[] = "/tmp";
   const char kLeading3[] = "/tmp/good/path";
-  const char kBadPrefix[] = "/tmp/good/pa";
   const char kTrailing[] = "/tmp/good/path/bad";
 
   BrokerFilePermission perm =
       BrokerFilePermission::StatOnlyWithIntermediateDirs(kPath);
   // No open or access permission.
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, nullptr));
-  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, nullptr));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, NULL, NULL));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, NULL, NULL));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, NULL, NULL));
+  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, NULL));
+  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, NULL));
 
   // Stat for all leading paths, but not trailing paths.
-  ASSERT_TRUE(perm.CheckStatWithIntermediates(kPath, nullptr));
-  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading1, nullptr));
-  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading2, nullptr));
-  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading3, nullptr));
-  ASSERT_FALSE(perm.CheckStatWithIntermediates(kBadPrefix, nullptr));
-  ASSERT_FALSE(perm.CheckStatWithIntermediates(kTrailing, nullptr));
-}
-
-TEST(BrokerFilePermission, InotifyAddWatchWithIntermediateDirs) {
-  const char kPath[] = "/tmp/good/path";
-  const char kLeading1[] = "/";
-  const char kLeading2[] = "/tmp";
-  const char kLeading3[] = "/tmp/good/path";
-  const char kBadPrefix[] = "/tmp/good/pa";
-  const char kTrailing[] = "/tmp/good/path/bad";
-
-  const uint32_t kBadMask =
-      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
-  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
-
-  BrokerFilePermission perm =
-      BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(kPath);
-  // No open or access permission.
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, nullptr, nullptr));
-  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, nullptr));
-  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, nullptr));
-
-  // Inotify_add_watch for all leading paths, but not trailing paths.
-  ASSERT_TRUE(
-      perm.CheckInotifyAddWatchWithIntermediates(kPath, kGoodMask, nullptr));
-  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading1, kGoodMask,
-                                                         nullptr));
-  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading2, kGoodMask,
-                                                         nullptr));
-  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading3, kGoodMask,
-                                                         nullptr));
-  ASSERT_FALSE(perm.CheckInotifyAddWatchWithIntermediates(kBadPrefix, kGoodMask,
-                                                          nullptr));
-  ASSERT_FALSE(perm.CheckInotifyAddWatchWithIntermediates(kTrailing, kGoodMask,
-                                                          nullptr));
-
-  // Fails without correct mask.
-  ASSERT_FALSE(
-      perm.CheckInotifyAddWatchWithIntermediates(kPath, kBadMask, nullptr));
+  ASSERT_TRUE(perm.CheckStat(kPath, NULL));
+  ASSERT_TRUE(perm.CheckStat(kLeading1, NULL));
+  ASSERT_TRUE(perm.CheckStat(kLeading2, NULL));
+  ASSERT_TRUE(perm.CheckStat(kLeading3, NULL));
+  ASSERT_FALSE(perm.CheckStat(kTrailing, NULL));
 }
 
 TEST(BrokerFilePermission, ValidatePath) {
diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc
index 99b5fa7..de86b08 100644
--- a/sandbox/linux/syscall_broker/broker_host.cc
+++ b/sandbox/linux/syscall_broker/broker_host.cc
@@ -8,14 +8,12 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <stddef.h>
-#include <sys/inotify.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <array>
 #include <string>
 #include <utility>
 
@@ -53,12 +51,12 @@
 // permission_list. Write the syscall return value (-errno) to |reply|.
 void AccessFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const char* requested_filename,
+                      const std::string& requested_filename,
                       int mode,
                       BrokerSimpleMessage* reply) {
   const char* file_to_access = NULL;
   if (!CommandAccessIsSafe(allowed_command_set, permission_list,
-                           requested_filename, mode, &file_to_access)) {
+                           requested_filename.c_str(), mode, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -72,16 +70,16 @@
   RAW_CHECK(reply->AddIntToMessage(0));
 }
 
-// Performs mkdir(2) on |requested_filename| with mode |mode| if allowed by our
+// Performs mkdir(2) on |filename| with mode |mode| if allowed by our
 // permission_list. Write the syscall return value (-errno) to |reply|.
 void MkdirFileForIPC(const BrokerCommandSet& allowed_command_set,
                      const BrokerPermissionList& permission_list,
-                     const char* requested_filename,
+                     const std::string& filename,
                      int mode,
                      BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandMkdirIsSafe(allowed_command_set, permission_list,
-                          requested_filename, &file_to_access)) {
+                          filename.c_str(), &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -97,14 +95,14 @@
 // file descriptor in the |opened_file| if relevant.
 void OpenFileForIPC(const BrokerCommandSet& allowed_command_set,
                     const BrokerPermissionList& permission_list,
-                    const char* requested_filename,
+                    const std::string& requested_filename,
                     int flags,
                     BrokerSimpleMessage* reply,
                     base::ScopedFD* opened_file) {
-  const char* file_to_open = nullptr;
+  const char* file_to_open = NULL;
   bool unlink_after_open = false;
   if (!CommandOpenIsSafe(allowed_command_set, permission_list,
-                         requested_filename, flags, &file_to_open,
+                         requested_filename.c_str(), flags, &file_to_open,
                          &unlink_after_open)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
@@ -127,14 +125,14 @@
 // result to |return_val|.
 void RenameFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const char* old_filename,
-                      const char* new_filename,
+                      const std::string& old_filename,
+                      const std::string& new_filename,
                       BrokerSimpleMessage* reply) {
   const char* old_file_to_access = nullptr;
   const char* new_file_to_access = nullptr;
-  if (!CommandRenameIsSafe(allowed_command_set, permission_list, old_filename,
-                           new_filename, &old_file_to_access,
-                           &new_file_to_access)) {
+  if (!CommandRenameIsSafe(allowed_command_set, permission_list,
+                           old_filename.c_str(), new_filename.c_str(),
+                           &old_file_to_access, &new_file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -148,11 +146,11 @@
 // Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes.
 void ReadlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
                         const BrokerPermissionList& permission_list,
-                        const char* requested_filename,
+                        const std::string& filename,
                         BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandReadlinkIsSafe(allowed_command_set, permission_list,
-                             requested_filename, &file_to_access)) {
+                             filename.c_str(), &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -168,11 +166,11 @@
 
 void RmdirFileForIPC(const BrokerCommandSet& allowed_command_set,
                      const BrokerPermissionList& permission_list,
-                     const char* requested_filename,
+                     const std::string& requested_filename,
                      BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandRmdirIsSafe(allowed_command_set, permission_list,
-                          requested_filename, &file_to_access)) {
+                          requested_filename.c_str(), &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -188,12 +186,12 @@
 void StatFileForIPC(const BrokerCommandSet& allowed_command_set,
                     const BrokerPermissionList& permission_list,
                     BrokerCommand command_type,
-                    const char* requested_filename,
+                    const std::string& requested_filename,
                     bool follow_links,
                     BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandStatIsSafe(allowed_command_set, permission_list,
-                         requested_filename, &file_to_access)) {
+                         requested_filename.c_str(), &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -234,11 +232,11 @@
 
 void UnlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const char* requested_filename,
+                      const std::string& requested_filename,
                       BrokerSimpleMessage* message) {
   const char* file_to_access = nullptr;
   if (!CommandUnlinkIsSafe(allowed_command_set, permission_list,
-                           requested_filename, &file_to_access)) {
+                           requested_filename.c_str(), &file_to_access)) {
     RAW_CHECK(message->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -249,34 +247,11 @@
   RAW_CHECK(message->AddIntToMessage(0));
 }
 
-void InotifyAddWatchForIPC(const BrokerCommandSet& allowed_command_set,
-                           const BrokerPermissionList& permission_list,
-                           base::ScopedFD inotify_fd,
-                           const char* requested_filename,
-                           uint32_t mask,
-                           BrokerSimpleMessage* message) {
-  const char* file_to_access = nullptr;
-  if (!CommandInotifyAddWatchIsSafe(allowed_command_set, permission_list,
-                                    requested_filename, mask,
-                                    &file_to_access)) {
-    RAW_CHECK(message->AddIntToMessage(-permission_list.denied_errno()));
-    return;
-  }
-
-  int wd = inotify_add_watch(inotify_fd.get(), file_to_access, mask);
-  if (wd < 0) {
-    RAW_CHECK(message->AddIntToMessage(-errno));
-    return;
-  }
-  RAW_CHECK(message->AddIntToMessage(wd));
-}
-
 // Handle a |command_type| request contained in |iter| and write the reply
 // to |reply|.
 bool HandleRemoteCommand(const BrokerCommandSet& allowed_command_set,
                          const BrokerPermissionList& permission_list,
                          BrokerSimpleMessage* message,
-                         base::span<base::ScopedFD> recv_fds,
                          BrokerSimpleMessage* reply,
                          base::ScopedFD* opened_file) {
   // Message structure:
@@ -365,20 +340,6 @@
                        reply);
       break;
     }
-    case COMMAND_INOTIFY_ADD_WATCH: {
-      const char* requested_filename;
-      if (!message->ReadString(&requested_filename))
-        return false;
-      int mask;
-      if (!message->ReadInt(&mask))
-        return false;
-      if (!recv_fds[0].is_valid())
-        return false;
-      InotifyAddWatchForIPC(allowed_command_set, permission_list,
-                            std::move(recv_fds[0]), requested_filename, mask,
-                            reply);
-      break;
-    }
     default:
       LOG(ERROR) << "Invalid IPC command";
       return false;
@@ -403,33 +364,26 @@
     BrokerSimpleMessage message;
     errno = 0;
     base::ScopedFD temporary_ipc;
-    std::array<base::ScopedFD, 2> recv_fds_arr;
-    base::span<base::ScopedFD> recv_fds(recv_fds_arr);
     const ssize_t msg_len =
-        message.RecvMsgWithFlagsMultipleFds(ipc_channel_.get(), 0, recv_fds);
+        message.RecvMsgWithFlags(ipc_channel_.get(), 0, &temporary_ipc);
 
     if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) {
       // EOF from the client, or the client died, we should finish looping.
       return;
     }
 
-    // This indicates an error occurred in IPC. For example, too many fds were
-    // sent along with the message.
-    if (msg_len < 0 || !recv_fds[0].is_valid()) {
-      if (!recv_fds[0].is_valid()) {
-        errno = EBADF;
-      }
+    // The client sends exactly one file descriptor, on which we
+    // will write the reply.
+    if (msg_len < 0) {
       PLOG(ERROR) << "Error reading message from the client";
       continue;
     }
 
-    temporary_ipc = std::move(recv_fds[0]);
-
     BrokerSimpleMessage reply;
     base::ScopedFD opened_file;
     if (!HandleRemoteCommand(policy_->allowed_command_set,
-                             *policy_->file_permissions, &message,
-                             recv_fds.subspan(1), &reply, &opened_file)) {
+                             *policy_->file_permissions, &message, &reply,
+                             &opened_file)) {
       // Does not exit if we received a malformed message.
       LOG(ERROR) << "Received malformed message from the client";
       continue;
diff --git a/sandbox/linux/syscall_broker/broker_permission_list.cc b/sandbox/linux/syscall_broker/broker_permission_list.cc
index 3d28d7f..f949797 100644
--- a/sandbox/linux/syscall_broker/broker_permission_list.cc
+++ b/sandbox/linux/syscall_broker/broker_permission_list.cc
@@ -111,23 +111,7 @@
     return false;
 
   for (size_t i = 0; i < num_of_permissions_; i++) {
-    if (permissions_array_[i].CheckStatWithIntermediates(requested_filename,
-                                                         file_to_stat))
-      return true;
-  }
-  return false;
-}
-
-bool BrokerPermissionList::GetFileNameIfAllowedToInotifyAddWatch(
-    const char* requested_filename,
-    uint32_t mask,
-    const char** file_to_inotify_add_watch) const {
-  if (!CheckCallerArgs(file_to_inotify_add_watch))
-    return false;
-
-  for (size_t i = 0; i < num_of_permissions_; i++) {
-    if (permissions_array_[i].CheckInotifyAddWatchWithIntermediates(
-            requested_filename, mask, file_to_inotify_add_watch))
+    if (permissions_array_[i].CheckStat(requested_filename, file_to_stat))
       return true;
   }
   return false;
diff --git a/sandbox/linux/syscall_broker/broker_permission_list.h b/sandbox/linux/syscall_broker/broker_permission_list.h
index 8524df5..98580ed 100644
--- a/sandbox/linux/syscall_broker/broker_permission_list.h
+++ b/sandbox/linux/syscall_broker/broker_permission_list.h
@@ -46,10 +46,11 @@
   // If |file_to_open| is not NULL, a pointer to the path will be returned.
   // In the case of a recursive match, this will be the requested_filename,
   // otherwise it will return the matching pointer from the
-  // allowlist. A caller should then use |file_to_access|. See
-  // GetFileNameIfAllowedToOpen() for more explanation. return true if calling
-  // access() on this file should be allowed, false otherwise. Async signal safe
-  // if and only if |file_to_access| is NULL.
+  // allowlist. For paranoia a caller should then use |file_to_access|. See
+  // GetFileNameIfAllowedToOpen() for more explanation.
+  // return true if calling access() on this file should be allowed, false
+  // otherwise.
+  // Async signal safe if and only if |file_to_access| is NULL.
   bool GetFileNameIfAllowedToAccess(const char* requested_filename,
                                     int requested_mode,
                                     const char** file_to_access) const;
@@ -58,8 +59,8 @@
   // If |file_to_open| is not NULL, a pointer to the path will be returned.
   // In the case of a recursive match, this will be the requested_filename,
   // otherwise it will return the matching pointer from the
-  // allowlist. A caller should then use |file_to_open| rather than
-  // |requested_filename|, so that it never attempts to open an
+  // allowlist. For paranoia, a caller should then use |file_to_open| rather
+  // than |requested_filename|, so that it never attempts to open an
   // attacker-controlled file name, even if an attacker managed to fool the
   // string comparison mechanism.
   // |unlink_after_open| if not NULL will be set to point to true if the
@@ -76,25 +77,11 @@
   // similar to GetFileNameIfAllowedToAccess(), except that if we have
   // create permission on file, we permit stat() on all its leading
   // components, otherwise checking for missing intermediate directories
-  // can't happen properly during a base::CreateDirectory() call.
-  // Async signal safe if and only if |file_to_access| is NULL.
+  // can't happen proplery during a base::CreateDirectory() call.
+  // Async signal safe if and only if |file_to_open| is NULL.
   bool GetFileNameIfAllowedToStat(const char* requested_filename,
                                   const char** file_to_access) const;
 
-  // Check if |requested_filename| can be watched with mask |mask|.
-  // If |file_to_inotify_add_watch| is not NULL, a pointer to the validated path
-  // will be returned. In the case of a recursive match, this will be the
-  // requested_filename, otherwise it will return the matching pointer from the
-  // allowlist. A caller should then use |file_to_inotify_add_watch| rather than
-  // |requested_filename|, so that it never attempts to open an
-  // attacker-controlled file name, even if an attacker managed to fool the
-  // string comparison mechanism. Async signal safe if and only if
-  // |file_to_inotify_add_watch| is NULL.
-  bool GetFileNameIfAllowedToInotifyAddWatch(
-      const char* requested_filename,
-      uint32_t mask,
-      const char** file_to_inotify_add_watch) const;
-
   int denied_errno() const { return denied_errno_; }
 
  private:
diff --git a/sandbox/linux/syscall_broker/broker_process.cc b/sandbox/linux/syscall_broker/broker_process.cc
index ab66dfed..ed5b6f2 100644
--- a/sandbox/linux/syscall_broker/broker_process.cc
+++ b/sandbox/linux/syscall_broker/broker_process.cc
@@ -26,10 +26,8 @@
 #include "build/build_config.h"
 #include "sandbox/linux/syscall_broker/broker_channel.h"
 #include "sandbox/linux/syscall_broker/broker_client.h"
-#include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/syscall_broker/broker_host.h"
 #include "sandbox/linux/syscall_broker/broker_permission_list.h"
-#include "sandbox/linux/system_headers/linux_syscalls.h"
 
 namespace sandbox {
 
@@ -189,9 +187,7 @@
       // If rmdir() doesn't exist, unlinkat is used with AT_REMOVEDIR.
       return !fast_check || policy_->allowed_command_set.test(COMMAND_RMDIR) ||
              policy_->allowed_command_set.test(COMMAND_UNLINK);
-    case __NR_inotify_add_watch:
-      return !fast_check ||
-             policy_->allowed_command_set.test(COMMAND_INOTIFY_ADD_WATCH);
+
     default:
       return false;
   }
diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc
index fbe4ea31..526574ec 100644
--- a/sandbox/linux/syscall_broker/broker_process_unittest.cc
+++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc
@@ -8,7 +8,6 @@
 #include <fcntl.h>
 #include <poll.h>
 #include <stddef.h>
-#include <sys/inotify.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -26,7 +25,6 @@
 #include "base/containers/flat_set.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
-#include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/posix/unix_domain_socket.h"
@@ -1750,169 +1748,6 @@
   TestUnlinkHelper(false);
 }
 
-void TestInotifyAddWatchHelper(bool fast_check_in_client) {
-  const uint32_t kBadMask =
-      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
-  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
-
-  // Create two nested temp dirs.
-  base::ScopedTempDir temp_dir;
-  ASSERT_TRUE(
-      temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTempDirForTests)));
-  std::string temp_dir_str = temp_dir.GetPath().MaybeAsASCII();
-  ASSERT_FALSE(temp_dir_str.empty());
-
-  base::ScopedTempDir nested_temp_dir;
-  ASSERT_TRUE(
-      nested_temp_dir.Set(temp_dir.GetPath().AppendASCII("nested_temp_dir")));
-  std::string nested_temp_dir_str = nested_temp_dir.GetPath().MaybeAsASCII();
-  ASSERT_FALSE(nested_temp_dir_str.empty());
-
-  base::FilePath bad_prefix = temp_dir.GetPath().AppendASCII("nested_t");
-  std::string bad_prefix_str = bad_prefix.MaybeAsASCII();
-  ASSERT_FALSE(bad_prefix_str.empty());
-
-  {
-    // Try to watch a directory without COMMAND_INOTIFY_ADD_WATCH
-    std::vector<BrokerFilePermission> permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            nested_temp_dir_str)};
-    auto policy = absl::make_optional<BrokerSandboxConfig>(
-        BrokerCommandSet(), permissions, kFakeErrnoSentinel);
-    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
-                              fast_check_in_client);
-
-    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
-
-    base::ScopedFD inotify_instance(inotify_init());
-    ASSERT_TRUE(inotify_instance.is_valid());
-    EXPECT_EQ(
-        -kFakeErrnoSentinel,
-        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-            inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask));
-  }
-
-  BrokerCommandSet command_set;
-  command_set.set(COMMAND_INOTIFY_ADD_WATCH);
-
-  {
-    // Try to watch a directory with no permission.
-    std::vector<BrokerFilePermission> permissions;
-    auto policy = absl::make_optional<BrokerSandboxConfig>(
-        command_set, permissions, kFakeErrnoSentinel);
-    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
-                              fast_check_in_client);
-
-    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
-
-    base::ScopedFD inotify_instance(inotify_init());
-    ASSERT_TRUE(inotify_instance.is_valid());
-    EXPECT_EQ(
-        -kFakeErrnoSentinel,
-        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-            inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask));
-  }
-  {
-    // Try to watch a directory with permission, but bad flags.
-    std::vector<BrokerFilePermission> permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            nested_temp_dir_str)};
-    auto policy = absl::make_optional<BrokerSandboxConfig>(
-        command_set, permissions, kFakeErrnoSentinel);
-    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
-                              fast_check_in_client);
-
-    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
-
-    base::ScopedFD inotify_instance(inotify_init());
-    ASSERT_TRUE(inotify_instance.is_valid());
-    EXPECT_EQ(
-        -kFakeErrnoSentinel,
-        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-            inotify_instance.get(), nested_temp_dir_str.c_str(), kBadMask));
-  }
-  {
-    // Add a directory with permissions and make sure it does not give watch
-    // permission to uintended directories or files.
-    std::vector<BrokerFilePermission> permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            nested_temp_dir_str)};
-    auto policy = absl::make_optional<BrokerSandboxConfig>(
-        command_set, permissions, kFakeErrnoSentinel);
-    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
-                              fast_check_in_client);
-
-    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
-
-    base::ScopedTempDir other_directory;
-    ASSERT_TRUE(
-        other_directory.CreateUniqueTempDirUnderPath(temp_dir.GetPath()));
-    std::string other_directory_str = other_directory.GetPath().MaybeAsASCII();
-    ASSERT_FALSE(other_directory_str.empty());
-
-    base::ScopedFD inotify_instance(inotify_init());
-    ASSERT_TRUE(inotify_instance.is_valid());
-
-    // Try to watch an unintended directory, should fail.
-    ASSERT_EQ(
-        -kFakeErrnoSentinel,
-        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-            inotify_instance.get(), other_directory_str.c_str(), kGoodMask));
-
-    // Try to access a prefix that isn't a full directory.
-    ASSERT_EQ(-kFakeErrnoSentinel,
-              open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-                  inotify_instance.get(), bad_prefix_str.c_str(), kGoodMask));
-  }
-  {
-    // Try to watch a directory with permission, and good flags.
-    std::vector<BrokerFilePermission> permissions = {
-        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
-            nested_temp_dir_str)};
-    auto policy = absl::make_optional<BrokerSandboxConfig>(
-        command_set, permissions, kFakeErrnoSentinel);
-    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
-                              fast_check_in_client);
-
-    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
-
-    base::ScopedFD inotify_instance(inotify_init());
-    ASSERT_TRUE(inotify_instance.is_valid());
-
-    // Try to watch the directory, which should succeed.
-    int wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-        inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask);
-    // The returned watch descriptor should be valid.
-    ASSERT_LE(0, wd);
-    // Removing the watch should succeed.
-    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
-
-    // Now try watching a leading directory, which should succeed.
-    wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-        inotify_instance.get(), temp_dir_str.c_str(), kGoodMask);
-    // The returned watch descriptor should be valid.
-    ASSERT_LE(0, wd);
-    // Removing the watch should succeed.
-    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
-
-    // Watching root should succeed with any valid permission.
-    wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
-        inotify_instance.get(), "/", kGoodMask);
-    // The returned watch descriptor should be valid.
-    ASSERT_LE(0, wd);
-    // Removing the watch should succeed.
-    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
-  }
-}
-
-TEST(BrokerProcess, InotifyAddWatchClient) {
-  TestInotifyAddWatchHelper(true);
-}
-
-TEST(BrokerProcess, InotifyAddWatchHost) {
-  TestInotifyAddWatchHelper(false);
-}
-
 TEST(BrokerProcess, IsSyscallAllowed) {
   const base::flat_map<BrokerCommand, base::flat_set<int>> kSysnosForCommand = {
       {COMMAND_ACCESS,
@@ -1980,12 +1815,6 @@
 #if defined(__NR_lstat64)
            __NR_lstat64,
 #endif
-       }},
-      {COMMAND_INOTIFY_ADD_WATCH,
-       {
-#if defined(__NR_inotify_add_watch)
-           __NR_inotify_add_watch
-#endif
        }}};
 
   // First gather up all the syscalls numbers we want to test.
diff --git a/sandbox/linux/syscall_broker/broker_simple_message.h b/sandbox/linux/syscall_broker/broker_simple_message.h
index 91bc057e..ea0a467 100644
--- a/sandbox/linux/syscall_broker/broker_simple_message.h
+++ b/sandbox/linux/syscall_broker/broker_simple_message.h
@@ -83,16 +83,16 @@
   // This returns a pointer to the next available data buffer in |data|. The
   // pointer is owned by |this| class. The resulting buffer is a string and
   // terminated with a '\0' character.
-  [[nodiscard]] bool ReadString(const char** string);
+  bool ReadString(const char** string);
 
   // This returns a pointer to the next available data buffer in the message
   // in |data|, and the length of the buffer in |length|. The buffer is owned
   // by |this| class.
-  [[nodiscard]] bool ReadData(const char** data, size_t* length);
+  bool ReadData(const char** data, size_t* length);
 
   // This reads the next available int from the message and stores it in
   // |result|.
-  [[nodiscard]] bool ReadInt(int* result);
+  bool ReadInt(int* result);
 
   // The maximum length of a message in the fixed size buffer.
   static constexpr size_t kMaxMessageLength = 4096;
diff --git a/sandbox/linux/syscall_broker/syscall_dispatcher.cc b/sandbox/linux/syscall_broker/syscall_dispatcher.cc
index 1f20d42..6d9782cf 100644
--- a/sandbox/linux/syscall_broker/syscall_dispatcher.cc
+++ b/sandbox/linux/syscall_broker/syscall_dispatcher.cc
@@ -195,12 +195,6 @@
     case __NR_unlinkat:
       return PerformUnlinkat(args);
 #endif  // defined(__NR_unlinkat)
-#if defined(__NR_inotify_add_watch)
-    case __NR_inotify_add_watch:
-      return InotifyAddWatch(static_cast<int>(args.args[0]),
-                             reinterpret_cast<const char*>(args.args[1]),
-                             static_cast<uint32_t>(args.args[2]));
-#endif
     default:
       RAW_CHECK(false);
       return -ENOSYS;
diff --git a/sandbox/linux/syscall_broker/syscall_dispatcher.h b/sandbox/linux/syscall_broker/syscall_dispatcher.h
index 906c37d..3c5b26034 100644
--- a/sandbox/linux/syscall_broker/syscall_dispatcher.h
+++ b/sandbox/linux/syscall_broker/syscall_dispatcher.h
@@ -53,11 +53,6 @@
   // Emulates unlink()/unlinkat().
   virtual int Unlink(const char* unlink) const = 0;
 
-  // Emulates inotify_add_watch().
-  virtual int InotifyAddWatch(int fd,
-                              const char* pathname,
-                              uint32_t mask) const = 0;
-
   // Different architectures use a different syscall from the stat family by
   // default in glibc. E.g. 32-bit systems use *stat*64() and fill out struct
   // kernel_stat64, whereas 64-bit systems use *stat*() and fill out struct
diff --git a/sandbox/linux/tests/scoped_temporary_file.cc b/sandbox/linux/tests/scoped_temporary_file.cc
index 4527a30d..35f53eb 100644
--- a/sandbox/linux/tests/scoped_temporary_file.cc
+++ b/sandbox/linux/tests/scoped_temporary_file.cc
@@ -9,21 +9,26 @@
 #include <unistd.h>
 
 #include "base/check_op.h"
-#include "base/files/file_util.h"
 #include "base/posix/eintr_wrapper.h"
 #include "build/build_config.h"
 
 namespace sandbox {
 
-ScopedTemporaryFile::ScopedTemporaryFile() {
-  static const char kFileNameTemplate[] = "ScopedTempFileXXXXXX";
-  full_file_name_ = std::string(kTempDirForTests) + kFileNameTemplate;
-  fd_ = mkstemp(full_file_name_.data());
+ScopedTemporaryFile::ScopedTemporaryFile() : fd_(-1) {
+#if BUILDFLAG(IS_ANDROID)
+  static const char file_template[] = "/data/local/tmp/ScopedTempFileXXXXXX";
+#else
+  static const char file_template[] = "/tmp/ScopedTempFileXXXXXX";
+#endif  // BUILDFLAG(IS_ANDROID)
+  static_assert(sizeof(full_file_name_) >= sizeof(file_template),
+                "full_file_name is not large enough");
+  memcpy(full_file_name_, file_template, sizeof(file_template));
+  fd_ = mkstemp(full_file_name_);
   CHECK_LE(0, fd_);
 }
 
 ScopedTemporaryFile::~ScopedTemporaryFile() {
-  CHECK_EQ(0, unlink(full_file_name_.c_str()));
+  CHECK_EQ(0, unlink(full_file_name_));
   CHECK_EQ(0, IGNORE_EINTR(close(fd_)));
 }
 
diff --git a/sandbox/linux/tests/scoped_temporary_file.h b/sandbox/linux/tests/scoped_temporary_file.h
index 10787b9..69903a1 100644
--- a/sandbox/linux/tests/scoped_temporary_file.h
+++ b/sandbox/linux/tests/scoped_temporary_file.h
@@ -5,18 +5,7 @@
 #ifndef SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_
 #define SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_
 
-#include <string>
-
-#include "build/build_config.h"
-
 namespace sandbox {
-
-#if BUILDFLAG(IS_ANDROID)
-static const char kTempDirForTests[] = "/data/local/tmp/";
-#else
-static const char kTempDirForTests[] = "/tmp/";
-#endif  // BUILDFLAG(IS_ANDROID)
-
 // Creates and open a temporary file on creation and closes
 // and removes it on destruction.
 // Unlike base/ helpers, this does not require JNI on Android.
@@ -30,11 +19,11 @@
   ~ScopedTemporaryFile();
 
   int fd() const { return fd_; }
-  const char* full_file_name() const { return full_file_name_.c_str(); }
+  const char* full_file_name() const { return full_file_name_; }
 
  private:
-  int fd_ = -1;
-  std::string full_file_name_;
+  int fd_;
+  char full_file_name_[128];
 };
 
 }  // namespace sandbox
diff --git a/sandbox/policy/linux/bpf_broker_policy_linux.cc b/sandbox/policy/linux/bpf_broker_policy_linux.cc
index 87a0cc1fa..0dc454a 100644
--- a/sandbox/policy/linux/bpf_broker_policy_linux.cc
+++ b/sandbox/policy/linux/bpf_broker_policy_linux.cc
@@ -5,7 +5,6 @@
 #include "sandbox/policy/linux/bpf_broker_policy_linux.h"
 
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
-#include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 
 using sandbox::bpf_dsl::Allow;
@@ -147,14 +146,6 @@
       }
       break;
 #endif
-#if defined(__NR_inotify_add_watch)
-    case __NR_inotify_add_watch:
-      if (allowed_command_set_.test(
-              syscall_broker::COMMAND_INOTIFY_ADD_WATCH)) {
-        return Allow();
-      }
-      break;
-#endif
     default:
       break;
   }
diff --git a/services/device/geolocation/location_arbitrator.cc b/services/device/geolocation/location_arbitrator.cc
index 723328e..5335e0e 100644
--- a/services/device/geolocation/location_arbitrator.cc
+++ b/services/device/geolocation/location_arbitrator.cc
@@ -36,10 +36,7 @@
       main_task_runner_(main_task_runner),
       url_loader_factory_(url_loader_factory),
       api_key_(api_key),
-      position_provider_(nullptr),
-      is_permission_granted_(false),
-      position_cache_(std::move(position_cache)),
-      is_running_(false) {}
+      position_cache_(std::move(position_cache)) {}
 
 LocationArbitrator::~LocationArbitrator() {
   // Release the global wifi polling policy state.
diff --git a/services/device/geolocation/location_arbitrator.h b/services/device/geolocation/location_arbitrator.h
index d51a9ef10..c4073e6 100644
--- a/services/device/geolocation/location_arbitrator.h
+++ b/services/device/geolocation/location_arbitrator.h
@@ -108,20 +108,16 @@
   const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
   const std::string api_key_;
 
-  LocationProvider::LocationProviderUpdateCallback arbitrator_update_callback_;
-
-  std::vector<std::unique_ptr<LocationProvider>> providers_;
   bool enable_high_accuracy_;
+  bool is_permission_granted_ = false;
+  bool is_running_ = false;  // Tracks whether providers should be running.
+  LocationProvider::LocationProviderUpdateCallback arbitrator_update_callback_;
+  std::unique_ptr<PositionCache> position_cache_;  // must outlive `providers_`
+  std::vector<std::unique_ptr<LocationProvider>> providers_;
   // The provider which supplied the current |position_|
-  raw_ptr<const LocationProvider> position_provider_;
-  bool is_permission_granted_;
+  raw_ptr<const LocationProvider> position_provider_ = nullptr;
   // The current best estimate of our position.
   mojom::Geoposition position_;
-
-  std::unique_ptr<PositionCache> position_cache_;
-
-  // Tracks whether providers should be running.
-  bool is_running_;
 };
 
 // Factory functions for the various types of location provider to abstract
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy.cc b/services/network/public/cpp/content_security_policy/content_security_policy.cc
index 633c233..dae74b9 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy.cc
@@ -10,6 +10,7 @@
 #include "base/base64url.h"
 #include "base/containers/contains.h"
 #include "base/containers/flat_set.h"
+#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
@@ -22,6 +23,7 @@
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
 #include "services/network/public/cpp/content_security_policy/csp_source.h"
 #include "services/network/public/cpp/content_security_policy/csp_source_list.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
@@ -645,6 +647,28 @@
       continue;
     }
 
+    // Discussed at https://github.com/WICG/nav-speculation/pull/209, and merged
+    // to the speculationrules explainer,
+    // https://github.com/WICG/nav-speculation/blob/main/triggers.md#content-security-policy.
+    // TODO(https://crbug.com/1382361): Have a patch spec and merge it to the
+    // upstream CSP spec.
+    if (base::FeatureList::IsEnabled(
+            features::kPrerender2ContentSecurityPolicyExtensions) &&
+        base::EqualsCaseInsensitiveASCII(expression,
+                                         "'inline-speculation-rules'")) {
+      if (directive_name == CSPDirectiveName::ScriptSrc) {
+        directive->allow_inline_speculation_rules = true;
+        continue;
+      } else {
+        parsing_errors.emplace_back(base::StringPrintf(
+            "The Content-Security-Policy directive '%s' contains '%s' as a "
+            "source expression that is permitted only for 'script-src' "
+            "directive. It will be ignored.",
+            ToString(directive_name).c_str(), std::string(expression).c_str()));
+        continue;
+      }
+    }
+
     if (base::EqualsCaseInsensitiveASCII(expression, "'unsafe-eval'")) {
       directive->allow_eval = true;
       continue;
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
index de697b0..7ac380d40 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
@@ -8,8 +8,10 @@
 #include "base/memory/raw_ref.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -1583,6 +1585,37 @@
   }
 }
 
+TEST(ContentSecurityPolicy, ParseInlineSpeculationRules) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      features::kPrerender2ContentSecurityPolicyExtensions);
+  std::vector<mojom::ContentSecurityPolicyPtr> ok_policies =
+      ParseCSP("script-src 'inline-speculation-rules'");
+  ASSERT_EQ(1u, ok_policies.size());
+  ASSERT_EQ(1u, ok_policies[0]->directives.size());
+  ASSERT_TRUE(
+      ok_policies[0]->directives.contains(mojom::CSPDirectiveName::ScriptSrc));
+  EXPECT_TRUE(ok_policies[0]
+                  ->directives[mojom::CSPDirectiveName::ScriptSrc]
+                  ->allow_inline_speculation_rules);
+  EXPECT_EQ(0u, ok_policies[0]->parsing_errors.size());
+
+  std::vector<mojom::ContentSecurityPolicyPtr> ng_policies =
+      ParseCSP("img-src 'inline-speculation-rules'");
+  ASSERT_EQ(1u, ng_policies.size());
+  ASSERT_EQ(1u, ng_policies[0]->directives.size());
+  ASSERT_TRUE(
+      ng_policies[0]->directives.contains(mojom::CSPDirectiveName::ImgSrc));
+  EXPECT_FALSE(ng_policies[0]
+                   ->directives[mojom::CSPDirectiveName::ImgSrc]
+                   ->allow_inline_speculation_rules);
+  ASSERT_EQ(1u, ng_policies[0]->parsing_errors.size());
+  EXPECT_EQ(
+      "The Content-Security-Policy directive 'img-src' contains "
+      "''inline-speculation-rules'' as a source expression that is permitted "
+      "only for 'script-src' directive. It will be ignored.",
+      ng_policies[0]->parsing_errors[0]);
+}
+
 TEST(ContentSecurityPolicy, IsValidRequiredCSPAttr) {
   struct TestCase {
     const char* csp;
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index 3592212a..de04861b 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -327,4 +327,8 @@
              "PrefetchNoVarySearch",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kPrerender2ContentSecurityPolicyExtensions,
+             "Prerender2ContentSecurityPolicyExtensions",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace network::features
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index 4c10633..3f16f5f 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -114,6 +114,12 @@
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kPrefetchNoVarySearch);
 
+// Enables the `inline-speculation-rules` source support in the
+// Content-Security-Policy for Prerender2.
+// https://crbug.com/1382361
+COMPONENT_EXPORT(NETWORK_CPP)
+BASE_DECLARE_FEATURE(kPrerender2ContentSecurityPolicyExtensions);
+
 }  // namespace features
 }  // namespace network
 
diff --git a/services/network/public/mojom/content_security_policy.mojom b/services/network/public/mojom/content_security_policy.mojom
index ff437771..ddea1e4 100644
--- a/services/network/public/mojom/content_security_policy.mojom
+++ b/services/network/public/mojom/content_security_policy.mojom
@@ -82,6 +82,7 @@
   bool allow_star = false;
   bool allow_response_redirects = false;
   bool allow_inline = false;
+  bool allow_inline_speculation_rules = false;
   bool allow_eval = false;
   bool allow_wasm_eval = false;
   bool allow_wasm_unsafe_eval = false;
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index cb7c2a7..3fa0120 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1882,7 +1882,7 @@
       {
         "args": [],
         "cros_board": "dedede",
-        "cros_img": "dedede-release/R110-15270.0.0",
+        "cros_img": "dedede-release/R110-15274.0.0",
         "name": "lacros_all_tast_tests DEDEDE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -1946,7 +1946,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R110-15270.0.0",
+        "cros_img": "eve-release/R110-15274.0.0",
         "name": "lacros_all_tast_tests EVE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2089,7 +2089,7 @@
       {
         "args": [],
         "cros_board": "jacuzzi",
-        "cros_img": "jacuzzi-release/R110-15270.0.0",
+        "cros_img": "jacuzzi-release/R110-15274.0.0",
         "name": "lacros_all_tast_tests JACUZZI_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2194,7 +2194,7 @@
       {
         "args": [],
         "cros_board": "herobrine",
-        "cros_img": "herobrine-release/R110-15270.0.0",
+        "cros_img": "herobrine-release/R110-15274.0.0",
         "name": "lacros_fyi_tast_tests HEROBRINE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index f68eaeb..cb7cb42 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -7684,6 +7684,8 @@
           "--avd-config=../../tools/android/avd/proto/generic_android31.textpb",
           "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.content_browsertests.filter"
         ],
+        "ci_only": true,
+        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--bucket",
diff --git a/testing/buildbot/chromium.memory.fyi.json b/testing/buildbot/chromium.memory.fyi.json
new file mode 100644
index 0000000..e657def4
--- /dev/null
+++ b/testing/buildbot/chromium.memory.fyi.json
@@ -0,0 +1,2250 @@
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "linux-ubsan-fyi-rel": {
+    "gtest_tests": [
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "app_shell_unittests",
+        "test_id_prefix": "ninja://extensions/shell:app_shell_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_fuzzer_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_fuzzer_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--gtest_filter=-*UsingRealWebcam*"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_app_unittests",
+        "test_id_prefix": "ninja://chrome/test:chrome_app_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chromedriver_unittests",
+        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "color_unittests",
+        "test_id_prefix": "ninja://ui/color:color_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "dbus_unittests",
+        "test_id_prefix": "ninja://dbus:dbus_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "device_unittests",
+        "test_id_prefix": "ninja://device:device_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "extensions_browsertests",
+        "test_id_prefix": "ninja://extensions:extensions_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "extensions_unittests",
+        "test_id_prefix": "ninja://extensions:extensions_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "filesystem_service_unittests",
+        "test_id_prefix": "ninja://components/services/filesystem:filesystem_service_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "message_center_unittests",
+        "test_id_prefix": "ninja://ui/message_center:message_center_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_core_unittests",
+        "test_id_prefix": "ninja://mojo/core:mojo_core_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "nacl_loader_unittests",
+        "test_id_prefix": "ninja://components/nacl/loader:nacl_loader_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "openscreen_unittests",
+        "test_id_prefix": "ninja://chrome/browser/media/router:openscreen_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ozone_unittests",
+        "test_id_prefix": "ninja://ui/ozone:ozone_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ozone_x11_unittests",
+        "test_id_prefix": "ninja://ui/ozone:ozone_x11_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "pdf_unittests",
+        "test_id_prefix": "ninja://pdf:pdf_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ppapi_unittests",
+        "test_id_prefix": "ninja://ppapi:ppapi_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "printing_unittests",
+        "test_id_prefix": "ninja://printing:printing_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "remoting_unittests",
+        "test_id_prefix": "ninja://remoting:remoting_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sandbox_linux_unittests",
+        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sync_integration_tests",
+        "test_id_prefix": "ninja://chrome/test:sync_integration_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "views_unittests",
+        "test_id_prefix": "ninja://ui/views:views_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--enable-features=EnableOverlayPrioritization"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "overlay_prioritization_viz_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "vr_common_unittests",
+        "test_id_prefix": "ninja://chrome/browser/vr:vr_common_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "vr_pixeltests",
+        "test_id_prefix": "ninja://chrome/browser/vr:vr_pixeltests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wm_unittests",
+        "test_id_prefix": "ninja://ui/wm:wm_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "xr_browser_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "xr_browser_tests",
+        "test_id_prefix": "ninja://chrome/test:xr_browser_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "isolate_name": "blink_python_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "blink_python_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://:blink_python_tests/"
+      },
+      {
+        "isolate_name": "blink_pytype",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "blink_pytype",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/blink/tools:blink_pytype/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_web_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_web_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 5
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_wpt_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_wpt_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 7
+        },
+        "test_id_prefix": "ninja://:blink_wpt_tests/"
+      },
+      {
+        "args": [
+          "--test-type=integration"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "chromedriver_py_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "chromedriver_py_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_py_tests/"
+      },
+      {
+        "isolate_name": "chromedriver_replay_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "chromedriver_replay_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_replay_unittests/"
+      },
+      {
+        "isolate_name": "content_shell_crash_test",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "content_shell_crash_test",
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/shell:content_shell_crash_test/"
+      },
+      {
+        "isolate_name": "flatbuffers_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "flatbuffers_unittests",
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/flatbuffers:flatbuffers_unittests/"
+      },
+      {
+        "isolate_name": "fuchsia_pytype",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "fuchsia_pytype",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://testing:fuchsia_pytype/"
+      },
+      {
+        "isolate_name": "gold_common_pytype",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gold_common_pytype",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://build:gold_common_pytype/"
+      },
+      {
+        "isolate_name": "gpu_pytype",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gpu_pytype",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:gpu_pytype/"
+      },
+      {
+        "isolate_name": "grit_python_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "grit_python_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://tools/grit:grit_python_unittests/"
+      },
+      {
+        "args": [
+          "--flag-specific=highdpi",
+          "--skipped=always",
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_web_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "high_dpi_blink_web_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "--flag-specific=highdpi",
+          "--skipped=always",
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_wpt_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "high_dpi_blink_wpt_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test_id_prefix": "ninja://:blink_wpt_tests/"
+      },
+      {
+        "isolate_name": "mojo_python_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mojo_python_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://mojo/public/tools:mojo_python_unittests/"
+      },
+      {
+        "args": [
+          "--flag-specific=disable-site-isolation-trials",
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_web_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "not_site_per_process_blink_web_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "--flag-specific=disable-site-isolation-trials",
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_wpt_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "not_site_per_process_blink_wpt_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test_id_prefix": "ninja://:blink_wpt_tests/"
+      },
+      {
+        "isolate_name": "telemetry_gpu_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_gpu_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_unittests/"
+      },
+      {
+        "args": [
+          "--extra-browser-args=--enable-crashpad"
+        ],
+        "isolate_name": "telemetry_perf_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--extra-browser-args=--disable-gpu"
+        ],
+        "isolate_name": "telemetry_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "isolate_name": "testing_pytype",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "testing_pytype",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://testing:testing_pytype/"
+      },
+      {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
+        "isolate_name": "views_perftests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "views_perftests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://ui/views:views_perftests/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--skipped=always",
+          "--flag-specific=skia-vulkan-swiftshader",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_web_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "vulkan_swiftshader_blink_web_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "isolate_name": "webdriver_wpt_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "webdriver_tests_suite",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test_id_prefix": "ninja://:webdriver_wpt_tests/"
+      }
+    ]
+  }
+}
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 2eb9b2f0..d06e503 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1096,7 +1096,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R110-15270.0.0",
+        "cros_img": "octopus-release/R110-15274.0.0",
         "name": "lacros_fyi_tast_tests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1144,7 +1144,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R110-15270.0.0",
+        "cros_img": "octopus-release/R110-15274.0.0",
         "name": "ozone_unittests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1195,7 +1195,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R110-15265.0.0",
+        "cros_img": "hana-release/R110-15274.0.0",
         "name": "lacros_all_tast_tests HANA_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1243,7 +1243,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R110-15270.0.0",
+        "cros_img": "strongbad-release/R110-15274.0.0",
         "name": "lacros_all_tast_tests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1291,7 +1291,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R110-15265.0.0",
+        "cros_img": "hana-release/R110-15274.0.0",
         "name": "ozone_unittests HANA_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1335,7 +1335,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R110-15270.0.0",
+        "cros_img": "strongbad-release/R110-15274.0.0",
         "name": "ozone_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1379,7 +1379,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R110-15265.0.0",
+        "cros_img": "hana-release/R110-15274.0.0",
         "name": "viz_unittests HANA_RELEASE_LKGM",
         "swarming": {},
         "test": "viz_unittests",
@@ -1423,7 +1423,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R110-15270.0.0",
+        "cros_img": "strongbad-release/R110-15274.0.0",
         "name": "viz_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "viz_unittests",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 08c9ddc..9100f8f 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1677,6 +1677,10 @@
         },
       },
       'android-12-x64-rel': {
+        # TODO(crbug.com/1225851): Remove experiment and ci_only
+        # once the test suite is stable.
+        'ci_only': True,
+        'experiment_percentage': 100,
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.content_browsertests.filter',
         ],
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 52179ca..5e6ba109 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -470,8 +470,8 @@
   'CROS_DEDEDE_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'dedede',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'dedede-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'dedede-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'DEDEDE_RELEASE_LKGM',
@@ -506,8 +506,8 @@
   'CROS_EVE_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'eve-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'eve-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'EVE_RELEASE_LKGM',
@@ -552,8 +552,8 @@
   'CROS_HANA_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'hana',
-      'cros_chrome_version': '110.0.5447.0',
-      'cros_img': 'hana-release/R110-15265.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'hana-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'HANA_RELEASE_LKGM',
@@ -588,8 +588,8 @@
   'CROS_HEROBRINE_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'herobrine',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'herobrine-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'herobrine-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'HEROBRINE_RELEASE_LKGM',
@@ -597,8 +597,8 @@
   'CROS_JACUZZI_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'jacuzzi',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'jacuzzi-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'jacuzzi-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'JACUZZI_RELEASE_LKGM',
@@ -663,8 +663,8 @@
   'CROS_OCTOPUS_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'octopus',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'octopus-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'octopus-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_RELEASE_LKGM',
@@ -699,8 +699,8 @@
   'CROS_STRONGBAD_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'strongbad',
-      'cros_chrome_version': '110.0.5460.0',
-      'cros_img': 'strongbad-release/R110-15270.0.0',
+      'cros_chrome_version': '110.0.5464.0',
+      'cros_img': 'strongbad-release/R110-15274.0.0',
     },
     'enabled': True,
     'identifier': 'STRONGBAD_RELEASE_LKGM',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 127616c..5d343ee 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -5226,6 +5226,22 @@
     },
   },
   {
+    'name': 'chromium.memory.fyi',
+    'mixins': ['chromium-tester-service-account'],
+    'machines': {
+      'linux-ubsan-fyi-rel': {
+        'mixins': [
+          'isolate_profile_data',
+          'linux-bionic',
+        ],
+        'test_suites': {
+          'gtest_tests': 'chromium_linux_gtests',
+          'isolated_scripts': 'chromium_linux_rel_isolated_scripts',
+        },
+      },
+    },
+  },
+  {
     'name': 'chromium.reclient.fyi',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 8551006..1a79b3a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6424,6 +6424,25 @@
             ]
         }
     ],
+    "LauncherGameSearchStudy": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "enabled_override": "true"
+                    },
+                    "enable_features": [
+                        "AppProvisioningStatic",
+                        "LauncherGameSearch"
+                    ]
+                }
+            ]
+        }
+    ],
     "LayoutMediaNGContainer": [
         {
             "platforms": [
@@ -11040,6 +11059,21 @@
             ]
         }
     ],
+    "TerminalTmuxRollout": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "TerminalTmuxIntegration"
+                    ]
+                }
+            ]
+        }
+    ],
     "ThemeProviderColorProviderRedirection": [
         {
             "platforms": [
diff --git a/third_party/blink/public/common/attribution_reporting/mojom_traits.h b/third_party/blink/public/common/attribution_reporting/mojom_traits.h
index 2213592..a1f5821d 100644
--- a/third_party/blink/public/common/attribution_reporting/mojom_traits.h
+++ b/third_party/blink/public/common/attribution_reporting/mojom_traits.h
@@ -16,7 +16,6 @@
 #include "components/attribution_reporting/aggregation_keys.h"
 #include "components/attribution_reporting/event_trigger_data.h"
 #include "components/attribution_reporting/filters.h"
-#include "components/attribution_reporting/os_registration.h"
 #include "components/attribution_reporting/source_registration.h"
 #include "components/attribution_reporting/suitable_origin.h"
 #include "components/attribution_reporting/trigger_registration.h"
@@ -24,9 +23,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-shared.h"
-#include "url/gurl.h"
 #include "url/mojom/origin_mojom_traits.h"
-#include "url/mojom/url_gurl_mojom_traits.h"
 #include "url/origin.h"
 
 namespace mojo {
@@ -82,56 +79,6 @@
 
 template <>
 struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionOsSourceDataView,
-                 attribution_reporting::OsSource> {
-  static const GURL& url(const attribution_reporting::OsSource& os_source) {
-    return os_source.url();
-  }
-
-  // TODO(apaseltiner): Define this in a separate .cc file.
-  static bool Read(blink::mojom::AttributionOsSourceDataView data,
-                   attribution_reporting::OsSource* out) {
-    GURL url;
-    if (!data.ReadUrl(&url))
-      return false;
-
-    absl::optional<attribution_reporting::OsSource> os_source =
-        attribution_reporting::OsSource::Create(std::move(url));
-    if (!os_source.has_value())
-      return false;
-
-    *out = std::move(*os_source);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionOsTriggerDataView,
-                 attribution_reporting::OsTrigger> {
-  static const GURL& url(const attribution_reporting::OsTrigger& os_trigger) {
-    return os_trigger.url();
-  }
-
-  // TODO(apaseltiner): Define this in a separate .cc file.
-  static bool Read(blink::mojom::AttributionOsTriggerDataView data,
-                   attribution_reporting::OsTrigger* out) {
-    GURL url;
-    if (!data.ReadUrl(&url))
-      return false;
-
-    absl::optional<attribution_reporting::OsTrigger> os_trigger =
-        attribution_reporting::OsTrigger::Create(std::move(url));
-    if (!os_trigger.has_value())
-      return false;
-
-    *out = std::move(*os_trigger);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
     StructTraits<blink::mojom::AttributionFilterDataDataView,
                  attribution_reporting::FilterData> {
   static const attribution_reporting::FilterValues& filter_values(
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 42d2993..7e5e4d4 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -531,16 +531,6 @@
     {
       types = [
         {
-          mojom = "blink.mojom.AttributionOsSource"
-          cpp = "::attribution_reporting::OsSource"
-          move_only = true
-        },
-        {
-          mojom = "blink.mojom.AttributionOsTrigger"
-          cpp = "::attribution_reporting::OsTrigger"
-          move_only = true
-        },
-        {
           mojom = "blink.mojom.AttributionDebugKey"
           cpp = "uint64_t"
         },
diff --git a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
index 636431a2d..90b0a8cf 100644
--- a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
@@ -8,7 +8,6 @@
 import "mojo/public/mojom/base/int128.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "url/mojom/origin.mojom";
-import "url/mojom/url.mojom";
 
 struct AttributionDebugKey {
   uint64 value;
@@ -160,18 +159,6 @@
   aggregation_service.mojom.AggregationCoordinator aggregation_coordinator;
 };
 
-struct AttributionOsSource {
-  // The URL to which the OS will make a request. Must use HTTP or HTTPS and
-  // its origin must be potentially trustworthy.
-  url.mojom.Url url;
-};
-
-struct AttributionOsTrigger {
-  // The URL to which the OS will make a request. Must use HTTP or HTTPS and
-  // its origin must be potentially trustworthy.
-  url.mojom.Url url;
-};
-
 // Indicates which registrations an AttributionDataHost permits.
 enum AttributionRegistrationType {
   kSourceOrTrigger,
diff --git a/third_party/blink/public/platform/web_content_security_policy_struct.h b/third_party/blink/public/platform/web_content_security_policy_struct.h
index 1e49abb2..d8b8b367 100644
--- a/third_party/blink/public/platform/web_content_security_policy_struct.h
+++ b/third_party/blink/public/platform/web_content_security_policy_struct.h
@@ -60,6 +60,7 @@
   bool allow_star;
   bool allow_response_redirects;
   bool allow_inline;
+  bool allow_inline_speculation_rules;
   bool allow_eval;
   bool allow_wasm_eval;
   bool allow_wasm_unsafe_eval;
diff --git a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.cc b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.cc
index b3dc6c6..4465d97 100644
--- a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.cc
+++ b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.cc
@@ -44,8 +44,7 @@
 }
 
 ScopedBlinkAXEventIntent::~ScopedBlinkAXEventIntent() {
-  // If a conservative GC is required, |document_| may become nullptr.
-  if (!document_ || !document_->IsActive())
+  if (!document_->IsActive())
     return;
 
   if (AXObjectCache* cache = document_->ExistingAXObjectCache()) {
diff --git a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
index 71ef4857..e4b28d8 100644
--- a/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
+++ b/third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h
@@ -33,7 +33,7 @@
                            Document* document);
   ScopedBlinkAXEventIntent(const Vector<BlinkAXEventIntent>& intents,
                            Document* document);
-  virtual ~ScopedBlinkAXEventIntent();
+  ~ScopedBlinkAXEventIntent();
   ScopedBlinkAXEventIntent(const ScopedBlinkAXEventIntent& intent) = delete;
   ScopedBlinkAXEventIntent& operator=(const ScopedBlinkAXEventIntent& intent) =
       delete;
diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 90ee0fcc..ed3bc5f 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -138,7 +138,7 @@
   return selection_editor_->ComputeVisibleSelectionInFlatTree();
 }
 
-SelectionInDOMTree FrameSelection::GetSelectionInDOMTree() const {
+const SelectionInDOMTree& FrameSelection::GetSelectionInDOMTree() const {
   return selection_editor_->GetSelectionInDOMTree();
 }
 
@@ -851,8 +851,7 @@
   AXObjectCache* cache = GetDocument().ExistingAXObjectCache();
   if (!cache)
     return;
-  const Position& extent = GetSelectionInDOMTree().Extent();
-  Node* anchor = extent.ComputeContainerNode();
+  Node* anchor = GetSelectionInDOMTree().Extent().ComputeContainerNode();
   if (anchor) {
     cache->SelectionChanged(anchor);
   } else {
diff --git a/third_party/blink/renderer/core/editing/frame_selection.h b/third_party/blink/renderer/core/editing/frame_selection.h
index ade2439..f325c8b 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.h
+++ b/third_party/blink/renderer/core/editing/frame_selection.h
@@ -217,7 +217,7 @@
 
   void DidChangeFocus();
 
-  SelectionInDOMTree GetSelectionInDOMTree() const;
+  const SelectionInDOMTree& GetSelectionInDOMTree() const;
   bool IsDirectional() const;
 
   void DidAttachDocument(Document*);
diff --git a/third_party/blink/renderer/core/editing/selection_editor.cc b/third_party/blink/renderer/core/editing/selection_editor.cc
index 85b1689e..408a4b6 100644
--- a/third_party/blink/renderer/core/editing/selection_editor.cc
+++ b/third_party/blink/renderer/core/editing/selection_editor.cc
@@ -108,7 +108,7 @@
   return has_selection_bounds_;
 }
 
-SelectionInDOMTree SelectionEditor::GetSelectionInDOMTree() const {
+const SelectionInDOMTree& SelectionEditor::GetSelectionInDOMTree() const {
   AssertSelectionValid();
   return selection_;
 }
diff --git a/third_party/blink/renderer/core/editing/selection_editor.h b/third_party/blink/renderer/core/editing/selection_editor.h
index 4bc3ef6..9a18525 100644
--- a/third_party/blink/renderer/core/editing/selection_editor.h
+++ b/third_party/blink/renderer/core/editing/selection_editor.h
@@ -48,7 +48,7 @@
   virtual ~SelectionEditor();
   void Dispose();
 
-  SelectionInDOMTree GetSelectionInDOMTree() const;
+  const SelectionInDOMTree& GetSelectionInDOMTree() const;
 
   VisibleSelection ComputeVisibleSelectionInDOMTree() const;
   VisibleSelectionInFlatTree ComputeVisibleSelectionInFlatTree() const;
diff --git a/third_party/blink/renderer/core/editing/selection_template.cc b/third_party/blink/renderer/core/editing/selection_template.cc
index 62668cb..48ee19ce 100644
--- a/third_party/blink/renderer/core/editing/selection_template.cc
+++ b/third_party/blink/renderer/core/editing/selection_template.cc
@@ -56,7 +56,7 @@
 }
 
 template <typename Strategy>
-PositionTemplate<Strategy> SelectionTemplate<Strategy>::Base() const {
+const PositionTemplate<Strategy>& SelectionTemplate<Strategy>::Base() const {
   DCHECK(AssertValid());
   DCHECK(!base_.IsOrphan()) << base_;
   return base_;
@@ -69,7 +69,7 @@
 }
 
 template <typename Strategy>
-PositionTemplate<Strategy> SelectionTemplate<Strategy>::Extent() const {
+const PositionTemplate<Strategy>& SelectionTemplate<Strategy>::Extent() const {
   DCHECK(AssertValid());
   DCHECK(!extent_.IsOrphan()) << extent_;
   return extent_;
@@ -140,14 +140,14 @@
 #endif
 
 template <typename Strategy>
-PositionTemplate<Strategy> SelectionTemplate<Strategy>::ComputeEndPosition()
-    const {
+const PositionTemplate<Strategy>&
+SelectionTemplate<Strategy>::ComputeEndPosition() const {
   return IsBaseFirst() ? extent_ : base_;
 }
 
 template <typename Strategy>
-PositionTemplate<Strategy> SelectionTemplate<Strategy>::ComputeStartPosition()
-    const {
+const PositionTemplate<Strategy>&
+SelectionTemplate<Strategy>::ComputeStartPosition() const {
   return IsBaseFirst() ? base_ : extent_;
 }
 
diff --git a/third_party/blink/renderer/core/editing/selection_template.h b/third_party/blink/renderer/core/editing/selection_template.h
index 0627743..f3b06b6 100644
--- a/third_party/blink/renderer/core/editing/selection_template.h
+++ b/third_party/blink/renderer/core/editing/selection_template.h
@@ -102,8 +102,8 @@
   bool operator==(const SelectionTemplate&) const;
   bool operator!=(const SelectionTemplate&) const;
 
-  PositionTemplate<Strategy> Base() const;
-  PositionTemplate<Strategy> Extent() const;
+  const PositionTemplate<Strategy>& Base() const;
+  const PositionTemplate<Strategy>& Extent() const;
   TextAffinity Affinity() const { return affinity_; }
   bool IsBaseFirst() const;
   bool IsCaret() const;
@@ -115,8 +115,8 @@
   bool AssertValid() const;
   bool AssertValidFor(const Document&) const;
 
-  PositionTemplate<Strategy> ComputeEndPosition() const;
-  PositionTemplate<Strategy> ComputeStartPosition() const;
+  const PositionTemplate<Strategy>& ComputeEndPosition() const;
+  const PositionTemplate<Strategy>& ComputeStartPosition() const;
   EphemeralRangeTemplate<Strategy> ComputeRange() const;
 
   void Trace(Visitor*) const;
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index 6c890d8..167454c 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -483,6 +483,7 @@
 bool ContentSecurityPolicy::IsScriptInlineType(InlineType inline_type) {
   switch (inline_type) {
     case ContentSecurityPolicy::InlineType::kNavigation:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kScriptAttribute:
     case ContentSecurityPolicy::InlineType::kScript:
       return true;
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.h b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
index 0f66c3c..1b071de 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.h
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
@@ -140,6 +140,7 @@
     kNavigation,
     kScript,
     kScriptAttribute,
+    kScriptSpeculationRules,  // TODO(https://crbug.com/1382361): Standardize.
     kStyle,
     kStyleAttribute
   };
diff --git a/third_party/blink/renderer/core/frame/csp/conversion_util.cc b/third_party/blink/renderer/core/frame/csp/conversion_util.cc
index 1c1fc82a..5e28cf0 100644
--- a/third_party/blink/renderer/core/frame/csp/conversion_util.cc
+++ b/third_party/blink/renderer/core/frame/csp/conversion_util.cc
@@ -44,6 +44,7 @@
           source_list->allow_star,
           source_list->allow_response_redirects,
           source_list->allow_inline,
+          source_list->allow_inline_speculation_rules,
           source_list->allow_eval,
           source_list->allow_wasm_eval,
           source_list->allow_wasm_unsafe_eval,
@@ -107,9 +108,10 @@
       std::move(sources), ConvertToWTF(source_list.nonces), std::move(hashes),
       source_list.allow_self, source_list.allow_star,
       source_list.allow_response_redirects, source_list.allow_inline,
-      source_list.allow_eval, source_list.allow_wasm_eval,
-      source_list.allow_wasm_unsafe_eval, source_list.allow_dynamic,
-      source_list.allow_unsafe_hashes, source_list.report_sample);
+      source_list.allow_inline_speculation_rules, source_list.allow_eval,
+      source_list.allow_wasm_eval, source_list.allow_wasm_unsafe_eval,
+      source_list.allow_dynamic, source_list.allow_unsafe_hashes,
+      source_list.report_sample);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index ad4003d..c67ca98 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -102,6 +102,7 @@
     // "navigation":
     // 1. Return script-src-elem. [spec text]
     case ContentSecurityPolicy::InlineType::kScript:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kNavigation:
       return CSPDirectiveName::ScriptSrcElem;
 
@@ -294,6 +295,7 @@
       return CheckUnsafeHashesAllowed(directive);
 
     case ContentSecurityPolicy::InlineType::kScript:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kStyle:
       return true;
   }
@@ -429,8 +431,10 @@
     const String& hash_value,
     CSPDirectiveName effective_type) {
   if (!directive.source_list ||
-      CSPSourceListAllowAllInline(directive.type, *directive.source_list))
+      CSPSourceListAllowAllInline(directive.type, inline_type,
+                                  *directive.source_list)) {
     return true;
+  }
 
   bool is_script = ContentSecurityPolicy::IsScriptInlineType(inline_type);
 
@@ -670,6 +674,7 @@
         break;
 
       case ContentSecurityPolicy::InlineType::kScript:
+      case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
       case ContentSecurityPolicy::InlineType::kStyleAttribute:
       case ContentSecurityPolicy::InlineType::kStyle:
         hash_value = GetSha256String(content);
@@ -682,6 +687,10 @@
         message = "run the JavaScript URL";
         break;
 
+      case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
+        message = "assert inline speculation-rules";
+        break;
+
       case ContentSecurityPolicy::InlineType::kScriptAttribute:
         message = "execute inline event handler";
         break;
@@ -706,7 +715,8 @@
   }
 
   return !directive.source_list ||
-         CSPSourceListAllowAllInline(directive.type, *directive.source_list);
+         CSPSourceListAllowAllInline(directive.type, inline_type,
+                                     *directive.source_list);
 }
 
 bool CSPDirectiveListShouldCheckEval(
@@ -955,8 +965,11 @@
   // If no `script-src` enforcement occurs, or it allows any and all inline
   // script, the restriction is not reasonable.
   if (!script_src.source_list ||
-      CSPSourceListAllowAllInline(script_src.type, *script_src.source_list))
+      CSPSourceListAllowAllInline(script_src.type,
+                                  ContentSecurityPolicy::InlineType::kScript,
+                                  *script_src.source_list)) {
     return false;
+  }
 
   if (CSPSourceListIsNone(*script_src.source_list))
     return true;
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive.cc b/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
index 25d9bd56..3fc1971c 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
@@ -133,16 +133,21 @@
 
 bool CSPSourceListAllowAllInline(
     CSPDirectiveName directive_type,
+    ContentSecurityPolicy::InlineType inline_type,
     const network::mojom::blink::CSPSourceList& source_list) {
   if (!IsScriptDirective(directive_type) &&
       !IsStyleDirective(directive_type)) {
     return false;
   }
 
-  return source_list.allow_inline &&
-         !CSPSourceListIsHashOrNoncePresent(source_list) &&
-         (!IsScriptDirective(directive_type) ||
-          !source_list.allow_dynamic);
+  bool allow_inline = source_list.allow_inline;
+  if (inline_type ==
+      ContentSecurityPolicy::InlineType::kScriptSpeculationRules) {
+    allow_inline |= source_list.allow_inline_speculation_rules;
+  }
+
+  return allow_inline && !CSPSourceListIsHashOrNoncePresent(source_list) &&
+         (!IsScriptDirective(directive_type) || !source_list.allow_dynamic);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive.h b/third_party/blink/renderer/core/frame/csp/source_list_directive.h
index b39838e..20cad84 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive.h
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_CSP_SOURCE_LIST_DIRECTIVE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/frame/csp/csp_source.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -47,6 +48,7 @@
 CORE_EXPORT
 bool CSPSourceListAllowAllInline(
     network::mojom::blink::CSPDirectiveName directive_type,
+    ContentSecurityPolicy::InlineType inline_type,
     const network::mojom::blink::CSPSourceList& source_list);
 
 CORE_EXPORT
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
index e7f881d..dcfb2ff 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
@@ -312,30 +312,35 @@
   // Script-src and style-src differently handle presence of 'strict-dynamic'.
   network::mojom::blink::CSPSourceListPtr script_src =
       ParseSourceList("script-src", "'strict-dynamic' 'unsafe-inline'");
-  EXPECT_FALSE(
-      CSPSourceListAllowAllInline(CSPDirectiveName::ScriptSrc, *script_src));
+  EXPECT_FALSE(CSPSourceListAllowAllInline(
+      CSPDirectiveName::ScriptSrc, ContentSecurityPolicy::InlineType::kScript,
+      *script_src));
 
   network::mojom::blink::CSPSourceListPtr style_src =
       ParseSourceList("style-src", "'strict-dynamic' 'unsafe-inline'");
-  EXPECT_TRUE(
-      CSPSourceListAllowAllInline(CSPDirectiveName::StyleSrc, *style_src));
+  EXPECT_TRUE(CSPSourceListAllowAllInline(
+      CSPDirectiveName::StyleSrc, ContentSecurityPolicy::InlineType::kStyle,
+      *style_src));
 
   for (const auto& test : cases) {
     script_src = ParseSourceList("script-src", test.sources);
-    EXPECT_EQ(
-        CSPSourceListAllowAllInline(CSPDirectiveName::ScriptSrc, *script_src),
-        test.expected);
+    EXPECT_EQ(CSPSourceListAllowAllInline(
+                  CSPDirectiveName::ScriptSrc,
+                  ContentSecurityPolicy::InlineType::kScript, *script_src),
+              test.expected);
 
     style_src = ParseSourceList("style-src", test.sources);
-    EXPECT_EQ(
-        CSPSourceListAllowAllInline(CSPDirectiveName::StyleSrc, *style_src),
-        test.expected);
+    EXPECT_EQ(CSPSourceListAllowAllInline(
+                  CSPDirectiveName::StyleSrc,
+                  ContentSecurityPolicy::InlineType::kStyle, *style_src),
+              test.expected);
 
     // If source list doesn't have a valid type, it must not allow all inline.
     network::mojom::blink::CSPSourceListPtr img_src =
         ParseSourceList("img-src", test.sources);
-    EXPECT_FALSE(
-        CSPSourceListAllowAllInline(CSPDirectiveName::ImgSrc, *img_src));
+    EXPECT_FALSE(CSPSourceListAllowAllInline(
+        CSPDirectiveName::ImgSrc, ContentSecurityPolicy::InlineType::kScript,
+        *img_src));
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc
index aca9b01..01276e9 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -140,8 +140,10 @@
       // - The caret is on the beginning of a Text node, and its previous node
       //   is updated, or
       // - The caret is on the end of a text node, and its next node is updated.
-      CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
-                     ComputeSelectionDirection());
+      ComputedSelection computed_selection;
+      ComputeSelection(kStart | kEnd | kDirection, computed_selection);
+      CacheSelection(computed_selection.start, computed_selection.end,
+                     computed_selection.direction);
     }
 
     SubtreeHasChanged();
@@ -545,14 +547,25 @@
   if (ShouldApplySelectionCache())
     return cached_selection_start_;
 
-  return ComputeSelectionStart();
+  ComputedSelection computed_selection;
+  ComputeSelection(kStart, computed_selection);
+  return computed_selection.start;
 }
 
-unsigned TextControlElement::ComputeSelectionStart() const {
+void TextControlElement::ComputeSelection(
+    uint32_t flags,
+    ComputedSelection& computed_selection) const {
   DCHECK(IsTextControl());
+#if DCHECK_IS_ON()
+  // This code does not set all values of `computed_selection`. Ensure they
+  // are set to the default.
+  DCHECK_EQ(0u, computed_selection.start);
+  DCHECK_EQ(0u, computed_selection.end);
+  DCHECK_EQ(kSelectionHasNoDirection, computed_selection.direction);
+#endif
   LocalFrame* frame = GetDocument().GetFrame();
   if (!frame)
-    return 0;
+    return;
 
   // To avoid regression on speedometer benchmark[1] test, we should not
   // update layout tree in this code block.
@@ -561,8 +574,24 @@
       GetDocument().Lifecycle());
   const SelectionInDOMTree& selection =
       frame->Selection().GetSelectionInDOMTree();
-  return IndexForPosition(InnerEditorElement(),
-                          selection.ComputeStartPosition());
+  if (flags & kStart) {
+    computed_selection.start = IndexForPosition(
+        InnerEditorElement(), selection.ComputeStartPosition());
+  }
+  if (flags & kEnd) {
+    if (flags & kStart && (selection.Base() == selection.Extent())) {
+      computed_selection.end = computed_selection.start;
+    } else {
+      computed_selection.end = IndexForPosition(InnerEditorElement(),
+                                                selection.ComputeEndPosition());
+    }
+  }
+  if (flags & kDirection && frame->Selection().IsDirectional()) {
+    computed_selection.direction =
+        (selection.Base() == selection.ComputeStartPosition())
+            ? kSelectionHasForwardDirection
+            : kSelectionHasBackwardDirection;
+  }
 }
 
 unsigned TextControlElement::selectionEnd() const {
@@ -570,23 +599,9 @@
     return 0;
   if (ShouldApplySelectionCache())
     return cached_selection_end_;
-  return ComputeSelectionEnd();
-}
-
-unsigned TextControlElement::ComputeSelectionEnd() const {
-  DCHECK(IsTextControl());
-  LocalFrame* frame = GetDocument().GetFrame();
-  if (!frame)
-    return 0;
-
-  // To avoid regression on speedometer benchmark[1] test, we should not
-  // update layout tree in this code block.
-  // [1] http://browserbench.org/Speedometer/
-  DocumentLifecycle::DisallowTransitionScope disallow_transition(
-      GetDocument().Lifecycle());
-  const SelectionInDOMTree& selection =
-      frame->Selection().GetSelectionInDOMTree();
-  return IndexForPosition(InnerEditorElement(), selection.ComputeEndPosition());
+  ComputedSelection computed_selection;
+  ComputeSelection(kEnd, computed_selection);
+  return computed_selection.end;
 }
 
 static const AtomicString& DirectionString(
@@ -613,28 +628,9 @@
   DCHECK(IsTextControl());
   if (ShouldApplySelectionCache())
     return DirectionString(cached_selection_direction_);
-  return DirectionString(ComputeSelectionDirection());
-}
-
-TextFieldSelectionDirection TextControlElement::ComputeSelectionDirection()
-    const {
-  DCHECK(IsTextControl());
-  LocalFrame* frame = GetDocument().GetFrame();
-  if (!frame)
-    return kSelectionHasNoDirection;
-
-  // To avoid regression on speedometer benchmark[1] test, we should not
-  // update layout tree in this code block.
-  // [1] http://browserbench.org/Speedometer/
-  DocumentLifecycle::DisallowTransitionScope disallow_transition(
-      GetDocument().Lifecycle());
-  const SelectionInDOMTree& selection =
-      frame->Selection().GetSelectionInDOMTree();
-  const Position& start = selection.ComputeStartPosition();
-  return frame->Selection().IsDirectional()
-             ? (selection.Base() == start ? kSelectionHasForwardDirection
-                                          : kSelectionHasBackwardDirection)
-             : kSelectionHasNoDirection;
+  ComputedSelection computed_selection;
+  ComputeSelection(kDirection, computed_selection);
+  return DirectionString(computed_selection.direction);
 }
 
 static inline void SetContainerAndOffsetForRange(Node* node,
@@ -765,8 +761,10 @@
 
   // selectionStart() or selectionEnd() will return cached selection when this
   // node doesn't have focus.
-  CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
-                 ComputeSelectionDirection());
+  ComputedSelection computed_selection;
+  ComputeSelection(kStart | kEnd | kDirection, computed_selection);
+  CacheSelection(computed_selection.start, computed_selection.end,
+                 computed_selection.direction);
 
   LocalFrame* frame = GetDocument().GetFrame();
   if (!frame || !user_triggered)
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.h b/third_party/blink/renderer/core/html/forms/text_control_element.h
index 5990044..8ac4c3f 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.h
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.h
@@ -182,10 +182,24 @@
                                        CloneChildrenFlag) override;
 
  private:
+  // Used by ComputeSelection() to specify which values are needed.
+  enum ComputeSelectionFlags {
+    kStart = 1 << 0,
+    kEnd = 1 << 1,
+    kDirection = 1 << 2,
+  };
+
+  struct ComputedSelection {
+    unsigned start = 0;
+    unsigned end = 0;
+    TextFieldSelectionDirection direction = kSelectionHasNoDirection;
+  };
+
   bool ShouldApplySelectionCache() const;
-  unsigned ComputeSelectionStart() const;
-  unsigned ComputeSelectionEnd() const;
-  TextFieldSelectionDirection ComputeSelectionDirection() const;
+  // Computes the selection. `flags` is a bitmask of ComputeSelectionFlags that
+  // indicates what values should be computed.
+  void ComputeSelection(uint32_t flags,
+                        ComputedSelection& computed_selection) const;
   // Returns true if cached values and arguments are not same.
   bool CacheSelection(unsigned start,
                       unsigned end,
diff --git a/third_party/blink/renderer/core/html/html_script_element.cc b/third_party/blink/renderer/core/html/html_script_element.cc
index 487f29cd..8028e58 100644
--- a/third_party/blink/renderer/core/html/html_script_element.cc
+++ b/third_party/blink/renderer/core/html/html_script_element.cc
@@ -298,10 +298,19 @@
     const AtomicString& nonce,
     const WTF::OrdinalNumber& context_line,
     const String& script_content) {
+  // Support 'inline-speculation-rules' source.
+  // https://github.com/WICG/nav-speculation/blob/main/triggers.md#content-security-policy
+  // TODO(http://crbug.com/1382361): Standardize it officially.
+  DCHECK(loader_);
+  ContentSecurityPolicy::InlineType inline_type =
+      loader_->GetScriptType() ==
+              ScriptLoader::ScriptTypeAtPrepare::kSpeculationRules
+          ? ContentSecurityPolicy::InlineType::kScriptSpeculationRules
+          : ContentSecurityPolicy::InlineType::kScript;
   return GetExecutionContext()
       ->GetContentSecurityPolicyForCurrentWorld()
-      ->AllowInline(ContentSecurityPolicy::InlineType::kScript, this,
-                    script_content, nonce, GetDocument().Url(), context_line);
+      ->AllowInline(inline_type, this, script_content, nonce,
+                    GetDocument().Url(), context_line);
 }
 
 Document& HTMLScriptElement::GetDocument() const {
diff --git a/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc b/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
index 7e2f6cd..56b5f1a 100644
--- a/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
+++ b/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
@@ -54,9 +54,10 @@
   return network::mojom::blink::CSPSourceList::New(
       std::move(sources), CrossThreadCopier<Vector<String>>::Copy(in->nonces),
       std::move(hashes), in->allow_self, in->allow_star,
-      in->allow_response_redirects, in->allow_inline, in->allow_eval,
-      in->allow_wasm_eval, in->allow_wasm_unsafe_eval, in->allow_dynamic,
-      in->allow_unsafe_hashes, in->report_sample);
+      in->allow_response_redirects, in->allow_inline,
+      in->allow_inline_speculation_rules, in->allow_eval, in->allow_wasm_eval,
+      in->allow_wasm_unsafe_eval, in->allow_dynamic, in->allow_unsafe_hashes,
+      in->report_sample);
 }
 
 HashMap<network::mojom::blink::CSPDirectiveName,
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 59b71a3b..ac19abc 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2924,6 +2924,8 @@
 
 bool AXObject::ComputeIsInertViaStyle(const ComputedStyle* style,
                                       IgnoredReasons* ignored_reasons) const {
+  // TODO(szager): This method is n^2 -- it recurses into itself via
+  // ComputeIsInert(), and InertRoot() does as well.
   if (style) {
     if (style->IsInert()) {
       if (ignored_reasons) {
@@ -2959,6 +2961,8 @@
       }
       return true;
     } else if (IsBlockedByAriaModalDialog(ignored_reasons)) {
+      if (ignored_reasons)
+        ignored_reasons->push_back(IgnoredReason(kAXAriaModalDialog));
       return true;
     } else if (const LocalFrame* frame = GetNode()->GetDocument().GetFrame()) {
       // Inert frames don't expose the inertness to the style of their contents,
@@ -2969,16 +2973,44 @@
         return true;
       }
     }
-  } else {
-    // Either GetNode() is null, or it's locked by content-visibility, or we
-    // failed to obtain a ComputedStyle. Make a guess iterating the ancestors.
-    AXObject* parent = ParentObject();
-    if (parent && parent->IsInert()) {
-      if (ignored_reasons)
-        parent->ComputeIsInert(ignored_reasons);
-      return true;
+    return false;
+  }
+
+  // Either GetNode() is null, or it's locked by content-visibility, or we
+  // failed to obtain a ComputedStyle. Make a guess iterating the ancestors.
+  if (const AXObject* ax_inert_root = InertRoot()) {
+    if (ignored_reasons) {
+      if (ax_inert_root == this) {
+        ignored_reasons->push_back(IgnoredReason(kAXInertElement));
+      } else {
+        ignored_reasons->push_back(
+            IgnoredReason(kAXInertSubtree, ax_inert_root));
+      }
+    }
+    return true;
+  } else if (IsBlockedByAriaModalDialog(ignored_reasons)) {
+    if (ignored_reasons)
+      ignored_reasons->push_back(IgnoredReason(kAXAriaModalDialog));
+    return true;
+  } else if (GetNode()) {
+    if (const LocalFrame* frame = GetNode()->GetDocument().GetFrame()) {
+      // Inert frames don't expose the inertness to the style of their contents,
+      // but accessibility should consider them inert anyways.
+      if (frame->IsInert()) {
+        if (ignored_reasons)
+          ignored_reasons->push_back(IgnoredReason(kAXInertSubtree));
+        return true;
+      }
     }
   }
+
+  AXObject* parent = ParentObject();
+  if (parent && parent->IsInert()) {
+    if (ignored_reasons)
+      parent->ComputeIsInert(ignored_reasons);
+    return true;
+  }
+
   return false;
 }
 
@@ -3079,6 +3111,8 @@
   DCHECK(object);
 
   Node* node = object->GetNode();
+  if (!node)
+    return nullptr;
   auto* element = DynamicTo<Element>(node);
   if (!element)
     element = FlatTreeTraversal::ParentElement(*node);
@@ -3822,13 +3856,7 @@
   if (GetLayoutObject())
     return GetLayoutObject()->Style();
 
-  // No layout object: if possible, use EnsureComputedStyle().
-  // Cannot call EnsureComputedStyle() here because we may be in post lifecycle
-  // steps, and EnsureComputedStyle() can cause a style recalc which is not
-  // allowed at that time (enforced by DCHECK).
-  // TODO(szager) Figure out how to make this code cleaner.
-  return GetDocument()->InPostLifecycleSteps() ? node->GetComputedStyle()
-                                               : node->EnsureComputedStyle();
+  return node->GetComputedStyle();
 }
 
 // There are 4 ways to use CSS to hide something:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_test.cc b/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
index 3c691620..9090f8c2 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
@@ -1281,9 +1281,6 @@
   NonThrowableExceptionState exception_state;
   SetBodyInnerHTML(R"HTML(
     <div id="div1" inert>inert</div>
-    <div id="div2" hidden>
-      <span id="span" inert>non-rendered inert</span>
-    </div>
     <dialog id="dialog1">dialog</dialog>
     <dialog id="dialog2" inert>inert dialog</dialog>
     <p id="p1">fullscreen</p>
@@ -1294,9 +1291,6 @@
   Element* body = document.body();
   Element* div1 = GetElementById("div1");
   Node* div1_text = div1->firstChild();
-  Element* div2 = GetElementById("div2");
-  Element* span = GetElementById("span");
-  Node* span_text = span->firstChild();
   auto* dialog1 = To<HTMLDialogElement>(GetElementById("dialog1"));
   Node* dialog1_text = dialog1->firstChild();
   auto* dialog2 = To<HTMLDialogElement>(GetElementById("dialog2"));
@@ -1335,9 +1329,6 @@
   AssertNotInert(body);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertNotInert(div2);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertNotInert(dialog1);
   AssertNotInert(dialog1_text);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1352,9 +1343,6 @@
   AssertInertReasons(body, kAXActiveModalDialog);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertInertReasons(div2, kAXActiveModalDialog);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertNotInert(dialog1);
   AssertNotInert(dialog1_text);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1369,9 +1357,6 @@
   AssertInertReasons(body, kAXActiveModalDialog);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertInertReasons(div2, kAXActiveModalDialog);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertInertReasons(dialog1, kAXActiveModalDialog);
   AssertInertReasons(dialog1_text, kAXActiveModalDialog);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1386,9 +1371,6 @@
   AssertInertReasons(body, kAXActiveModalDialog);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertInertReasons(div2, kAXActiveModalDialog);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertInertReasons(dialog1, kAXActiveModalDialog);
   AssertInertReasons(dialog1_text, kAXActiveModalDialog);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1404,9 +1386,6 @@
   AssertInertReasons(body, kAXActiveFullscreenElement);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertInertReasons(div2, kAXActiveFullscreenElement);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertInertReasons(dialog1, kAXActiveFullscreenElement);
   AssertInertReasons(dialog1_text, kAXActiveFullscreenElement);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1422,9 +1401,6 @@
   AssertInertReasons(body, kAXActiveFullscreenElement);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertInertReasons(div2, kAXActiveFullscreenElement);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertInertReasons(dialog1, kAXActiveFullscreenElement);
   AssertInertReasons(dialog1_text, kAXActiveFullscreenElement);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1439,9 +1415,6 @@
   AssertNotInert(body);
   AssertInertReasons(div1, kAXInertElement);
   AssertInertReasons(div1_text, kAXInertSubtree);
-  AssertNotInert(div2);
-  AssertInertReasons(span, kAXInertElement);
-  AssertInertReasons(span_text, kAXInertSubtree);
   AssertNotInert(dialog1);
   AssertNotInert(dialog1_text);
   AssertInertReasons(dialog2, kAXInertElement);
@@ -1503,55 +1476,6 @@
   }
 }
 
-TEST_F(AccessibilityTest, IsInertInDisplayNone) {
-  const Document& document = GetDocument();
-  ScopedInertAttributeForTest enabled_scope(true);
-  SetBodyInnerHTML(R"HTML(
-    <div hidden>
-      foo
-      <p inert>
-        bar
-        <span>baz</span>
-      </p>
-    </div>
-  )HTML");
-
-  Element* body = document.body();
-  AXObject* ax_body = GetAXObjectCache().GetOrCreate(body);
-  ASSERT_NE(ax_body, nullptr);
-  ASSERT_FALSE(ax_body->IsInert());
-
-  Element* div = body->QuerySelector("div");
-  AXObject* ax_div = GetAXObjectCache().GetOrCreate(div);
-  ASSERT_NE(ax_div, nullptr);
-  ASSERT_FALSE(ax_div->IsInert());
-
-  Node* div_text = div->firstChild();
-  AXObject* ax_div_text = GetAXObjectCache().GetOrCreate(div_text);
-  ASSERT_NE(ax_div_text, nullptr);
-  ASSERT_FALSE(ax_div_text->IsInert());
-
-  Element* p = div->QuerySelector("p");
-  AXObject* ax_p = GetAXObjectCache().GetOrCreate(p);
-  ASSERT_NE(ax_p, nullptr);
-  ASSERT_TRUE(ax_p->IsInert());
-
-  Node* p_text = p->firstChild();
-  AXObject* ax_p_text = GetAXObjectCache().GetOrCreate(p_text);
-  ASSERT_NE(ax_p_text, nullptr);
-  ASSERT_TRUE(ax_p_text->IsInert());
-
-  Element* span = p->QuerySelector("span");
-  AXObject* ax_span = GetAXObjectCache().GetOrCreate(span);
-  ASSERT_NE(ax_span, nullptr);
-  ASSERT_TRUE(ax_span->IsInert());
-
-  Node* span_text = span->firstChild();
-  AXObject* ax_span_text = GetAXObjectCache().GetOrCreate(span_text);
-  ASSERT_NE(ax_span_text, nullptr);
-  ASSERT_TRUE(ax_span_text->IsInert());
-}
-
 TEST_F(AccessibilityTest, CanSetFocusInCanvasFallbackContent) {
   ScopedInertAttributeForTest enabled_scope(true);
   SetBodyInnerHTML(R"HTML(
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
index 0ea1da6c..a8fe56d 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
@@ -1114,6 +1114,38 @@
     EXPECT_FALSE(IsManifestEmpty(manifest));
     EXPECT_EQ(0u, GetErrorCount());
   }
+
+  // Smoke test.
+  {
+    auto& manifest = ParseManifest(R"(
+          {
+            "icons": [
+              {
+                "src": "foo.webp",
+                "type": "image/webp",
+                "sizes": "192x192"
+              },
+              {
+                "src": "foo.svg",
+                "type": "image/svg+xml",
+                "sizes": "144x144"
+              }
+            ]
+          }
+        )");
+    ASSERT_EQ(manifest->icons.size(), 2u);
+    EXPECT_EQ(manifest->icons[0]->src, KURL(DefaultDocumentUrl(), "foo.webp"));
+    EXPECT_EQ(manifest->icons[0]->type, "image/webp");
+    EXPECT_EQ(manifest->icons[0]->sizes.size(), 1u);
+    EXPECT_EQ(manifest->icons[0]->sizes[0].width(), 192);
+    EXPECT_EQ(manifest->icons[0]->sizes[0].height(), 192);
+    EXPECT_EQ(manifest->icons[1]->src, KURL(DefaultDocumentUrl(), "foo.svg"));
+    EXPECT_EQ(manifest->icons[1]->type, "image/svg+xml");
+    EXPECT_EQ(manifest->icons[1]->sizes.size(), 1u);
+    EXPECT_EQ(manifest->icons[1]->sizes[0].width(), 144);
+    EXPECT_EQ(manifest->icons[1]->sizes[0].height(), 144);
+    EXPECT_EQ(0u, GetErrorCount());
+  }
 }
 
 TEST_F(ManifestParserTest, ScreenshotsParseRules) {
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
index deeecd6..292b38e 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
@@ -1152,20 +1152,20 @@
     }
   }
 
-  auto* resample = MakeGarbageCollected<MLOperator>(
-      this, MLOperator::OperatorKind::kResample, options);
+  auto* resample2d = MakeGarbageCollected<MLOperator>(
+      this, MLOperator::OperatorKind::kResample2d, options);
   String error_message;
   // According to WebNN spec
   // https://www.w3.org/TR/webnn/#api-mlgraphbuilder-resample2d, the output
   // tensor of resample2d has the same type as its input.
   auto* output = MLOperand::ValidateAndCreateOutput(
-      this, input->Type(), std::move(output_shape), resample, error_message);
+      this, input->Type(), std::move(output_shape), resample2d, error_message);
   if (!output) {
     exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
                                       error_message);
     return nullptr;
   }
-  resample->Connect({input}, {output});
+  resample2d->Connect({input}, {output});
   return output;
 }
 
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
index 7b85713..6a82705 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
@@ -1942,11 +1942,11 @@
   EXPECT_NE(output, nullptr);
   EXPECT_EQ(output->Kind(), MLOperand::OperandKind::kOutput);
   EXPECT_EQ(output->Type(), input->Type());
-  auto* resample = output->Operator();
-  EXPECT_NE(resample, nullptr);
-  EXPECT_EQ(resample->Kind(), MLOperator::OperatorKind::kResample);
-  EXPECT_EQ(resample->IsConnected(), true);
-  EXPECT_NE(resample->Options(), nullptr);
+  auto* resample2d = output->Operator();
+  EXPECT_NE(resample2d, nullptr);
+  EXPECT_EQ(resample2d->Kind(), MLOperator::OperatorKind::kResample2d);
+  EXPECT_EQ(resample2d->IsConnected(), true);
+  EXPECT_NE(resample2d->Options(), nullptr);
   return output;
 }
 
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_operator.h b/third_party/blink/renderer/modules/ml/webnn/ml_operator.h
index 5241497d..35c29ff 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_operator.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operator.h
@@ -37,7 +37,7 @@
     kMaxPool2d,
     kRelu,
     kReshape,
-    kResample,
+    kResample2d,
     kSoftmax,
     kSigmoid
   };
diff --git a/third_party/blink/renderer/modules/webcodecs/background_readback.cc b/third_party/blink/renderer/modules/webcodecs/background_readback.cc
index 3f89f92..c34017d2 100644
--- a/third_party/blink/renderer/modules/webcodecs/background_readback.cc
+++ b/third_party/blink/renderer/modules/webcodecs/background_readback.cc
@@ -220,9 +220,11 @@
   gpu::MailboxHolder mailbox_holder = txt_frame->mailbox_holder(0);
   ri->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
 
+  gfx::Size texture_size = txt_frame->coded_size();
   ri->ReadbackARGBPixelsAsync(
-      mailbox_holder.mailbox, mailbox_holder.texture_target, origin, src_point,
-      info, base::saturated_cast<GLuint>(rgba_stide), dst_pixels,
+      mailbox_holder.mailbox, mailbox_holder.texture_target, origin,
+      texture_size, src_point, info, base::saturated_cast<GLuint>(rgba_stide),
+      dst_pixels,
       WTF::BindOnce(&BackgroundReadback::OnARGBPixelsFrameReadCompleted,
                     MakeUnwrappingCrossThreadHandle(this), std::move(result_cb),
                     std::move(txt_frame), std::move(result)));
@@ -296,9 +298,11 @@
   gpu::MailboxHolder mailbox_holder = txt_frame->mailbox_holder(0);
   ri->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
 
+  gfx::Size texture_size = txt_frame->coded_size();
   ri->ReadbackARGBPixelsAsync(
-      mailbox_holder.mailbox, mailbox_holder.texture_target, origin, src_point,
-      info, base::saturated_cast<GLuint>(stride), dst_pixels,
+      mailbox_holder.mailbox, mailbox_holder.texture_target, origin,
+      texture_size, src_point, info, base::saturated_cast<GLuint>(stride),
+      dst_pixels,
       WTF::BindOnce(&BackgroundReadback::OnARGBPixelsBufferReadCompleted,
                     MakeUnwrappingCrossThreadHandle(this), std::move(txt_frame),
                     src_rect, dest_layout, dest_buffer, std::move(done_cb)));
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
index 2bc1646..23cd9eb 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -603,11 +603,11 @@
 
   // Populate the output mailbox and callback.
   {
-    bool is_overlay_candidate = !!color_buffer_for_mailbox->gpu_memory_buffer;
     *out_resource = viz::TransferableResource::MakeGpu(
         color_buffer_for_mailbox->mailbox, GL_LINEAR, texture_target_,
         color_buffer_for_mailbox->produce_sync_token, size_,
-        color_buffer_for_mailbox->format, is_overlay_candidate);
+        color_buffer_for_mailbox->format,
+        color_buffer_for_mailbox->is_overlay_candidate);
     out_resource->color_space = color_buffer_for_mailbox->color_space;
     // This holds a ref on the DrawingBuffer that will keep it alive until the
     // mailbox is released (and while the release callback is running).
@@ -835,6 +835,7 @@
     const gfx::ColorSpace& color_space,
     viz::ResourceFormat format,
     GLuint texture_id,
+    bool is_overlay_candidate,
     std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer,
     gpu::Mailbox mailbox)
     : owning_thread_ref(base::PlatformThread::CurrentRef()),
@@ -843,6 +844,7 @@
       color_space(color_space),
       format(format),
       texture_id(texture_id),
+      is_overlay_candidate(is_overlay_candidate),
       gpu_memory_buffer(std::move(gpu_memory_buffer)),
       mailbox(mailbox) {}
 
@@ -1828,14 +1830,11 @@
   state_restorer_->SetTextureBindingDirty();
 
   gpu::SharedImageInterface* sii = ContextProvider()->SharedImageInterface();
-  gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager =
-      Platform::Current()->GetGpuMemoryBufferManager();
-
+  bool is_overlay_candidate = false;
   gpu::Mailbox back_buffer_mailbox;
   // Set only when using swap chains.
   gpu::Mailbox front_buffer_mailbox;
   GLuint texture_id = 0;
-  std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
   uint32_t usage = gpu::SHARED_IMAGE_USAGE_GLES2 |
                    gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
                    gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
@@ -1845,13 +1844,10 @@
                                ? kTopLeft_GrSurfaceOrigin
                                : kBottomLeft_GrSurfaceOrigin;
 
-  viz::ResourceFormat format;
-  if (have_alpha_channel_) {
-    format = use_half_float_storage_ ? viz::RGBA_F16 : viz::RGBA_8888;
-  } else {
-    DCHECK(!use_half_float_storage_);
-    format = viz::RGBX_8888;
-  }
+  viz::ResourceFormat format =
+      use_half_float_storage_
+          ? viz::RGBA_F16
+          : (have_alpha_channel_ ? viz::RGBA_8888 : viz::RGBX_8888);
   if (UsingSwapChain()) {
     gpu::SharedImageInterface::SwapChainMailboxes mailboxes =
         sii->CreateSwapChain(format, size, color_space_, origin,
@@ -1859,8 +1855,26 @@
                              usage | gpu::SHARED_IMAGE_USAGE_SCANOUT);
     back_buffer_mailbox = mailboxes.back_buffer;
     front_buffer_mailbox = mailboxes.front_buffer;
-  } else {
-    if (ShouldUseChromiumImage()) {
+  }
+
+  // Allocate a GpuMemoryBuffer-backed SharedImage.
+  std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
+  if (ShouldUseChromiumImage() && back_buffer_mailbox.IsZero()) {
+    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager =
+        Platform::Current()->GetGpuMemoryBufferManager();
+
+    // TODO(crbug.com/911176): We should not allocate a GpuMemoryBuffer here.
+    // Multiple attempts to remove this path have been reverted, so this path
+    // is to be removed by incrementally expanding the situations where
+    // `use_gpu_memory_buffers` is false until the whole block can be removed.
+    bool use_gpu_memory_buffers = true;
+#if !BUILDFLAG(IS_CHROMEOS)
+    use_gpu_memory_buffers = false;
+#endif
+    if (use_half_float_storage_)
+      use_gpu_memory_buffers = false;
+
+    if (use_gpu_memory_buffers) {
       gfx::BufferFormat buffer_format;
       if (have_alpha_channel_) {
         buffer_format = use_half_float_storage_ ? gfx::BufferFormat::RGBA_F16
@@ -1874,10 +1888,6 @@
           buffer_format = gfx::BufferFormat::BGRX_8888;
         }
       }
-      // TODO(crbug.com/911176): When RGB emulation is not needed, we should use
-      // the non-GMB CreateSharedImage with gpu::SHARED_IMAGE_USAGE_SCANOUT in
-      // order to allocate the GMB service-side and avoid a synchronous
-      // round-trip to the browser process here.
       gfx::BufferUsage buffer_usage = gfx::BufferUsage::SCANOUT;
       uint32_t additional_usage_flags = gpu::SHARED_IMAGE_USAGE_SCANOUT;
       if (low_latency_enabled()) {
@@ -1898,22 +1908,43 @@
         }
       }
     }
+  }
 
-    // Create a normal SharedImage if GpuMemoryBuffer is not needed or the
-    // allocation above failed.
-    if (!gpu_memory_buffer) {
-      // We want to set the correct SkAlphaType on the new shared image but in
-      // the case of ShouldUseChromiumImage() we instead keep this buffer
-      // premultiplied, draw to |premultiplied_alpha_false_mailbox_|, and
-      // convert during copy.
-      SkAlphaType alpha_type = kPremul_SkAlphaType;
-      if (!ShouldUseChromiumImage() && !premultiplied_alpha_)
-        alpha_type = kUnpremul_SkAlphaType;
-
-      back_buffer_mailbox =
-          sii->CreateSharedImage(format, size, color_space_, origin, alpha_type,
-                                 usage, gpu::kNullSurfaceHandle);
+  if (back_buffer_mailbox.IsZero()) {
+    if (ShouldUseChromiumImage()) {
+      // Only allocate using SCANOUT if GpuMemoryBuffers of the equivalent
+      // gfx::BufferFormat are supported.
+#if BUILDFLAG(IS_MAC)
+      const viz::ResourceFormat scanout_format =
+          use_half_float_storage_
+              ? viz::RGBA_F16
+              : (have_alpha_channel_ ? viz::BGRA_8888 : viz::BGRX_8888);
+#else
+      const viz::ResourceFormat scanout_format = format;
+#endif
+      if (gpu::IsImageFromGpuMemoryBufferFormatSupported(
+              viz::BufferFormat(scanout_format),
+              ContextProvider()->GetCapabilities())) {
+        format = scanout_format;
+        is_overlay_candidate = true;
+        usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
+        if (low_latency_enabled()) {
+          usage |= gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
+        }
+      }
     }
+
+    // We want to set the correct SkAlphaType on the new shared image but in
+    // the case of ShouldUseChromiumImage() we instead keep this buffer
+    // premultiplied, draw to |premultiplied_alpha_false_mailbox_|, and
+    // convert during copy.
+    SkAlphaType alpha_type = kPremul_SkAlphaType;
+    if (!ShouldUseChromiumImage() && !premultiplied_alpha_)
+      alpha_type = kUnpremul_SkAlphaType;
+
+    back_buffer_mailbox =
+        sii->CreateSharedImage(format, size, color_space_, origin, alpha_type,
+                               usage, gpu::kNullSurfaceHandle);
   }
 
   gpu::SyncToken sync_token = sii->GenUnverifiedSyncToken();
@@ -1925,7 +1956,7 @@
         front_buffer_mailbox.name);
     front_color_buffer_ = base::MakeRefCounted<ColorBuffer>(
         weak_factory_.GetWeakPtr(), size, color_space_, format, texture_id,
-        nullptr, front_buffer_mailbox);
+        /*is_overlay_candidate=*/false, nullptr, front_buffer_mailbox);
   }
   // Import the backbuffer of swap chain or allocated SharedImage into GL.
   texture_id =
@@ -1954,7 +1985,7 @@
 
   return base::MakeRefCounted<ColorBuffer>(
       weak_factory_.GetWeakPtr(), size, color_space_, format, texture_id,
-      std::move(gpu_memory_buffer), back_buffer_mailbox);
+      is_overlay_candidate, std::move(gpu_memory_buffer), back_buffer_mailbox);
 }
 
 void DrawingBuffer::AttachColorBufferToReadFramebuffer() {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
index 5dc76db..88c7d91 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
@@ -418,6 +418,7 @@
                 const gfx::ColorSpace& color_space,
                 viz::ResourceFormat,
                 GLuint texture_id,
+                bool is_overlay_candidate,
                 std::unique_ptr<gfx::GpuMemoryBuffer>,
                 gpu::Mailbox mailbox);
     ColorBuffer(const ColorBuffer&) = delete;
@@ -436,6 +437,7 @@
     const gfx::ColorSpace color_space;
     const viz::ResourceFormat format;
     const GLuint texture_id = 0;
+    const bool is_overlay_candidate;
     std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
 
     // If we're emulating an RGB back buffer using an RGBA Chromium
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc
index 040c8a5..385af30 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc
@@ -345,236 +345,6 @@
   testing::Mock::VerifyAndClearExpectations(gl_);
 }
 
-class DrawingBufferImageChromiumTest : public DrawingBufferTest,
-                                       private ScopedWebGLImageChromiumForTest {
- public:
-  DrawingBufferImageChromiumTest() : ScopedWebGLImageChromiumForTest(true) {}
-
- protected:
-  void SetUp() override {
-    platform_ = std::make_unique<
-        ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform>>();
-
-    gfx::Size initial_size(kInitialWidth, kInitialHeight);
-    auto gl = std::make_unique<GLES2InterfaceForTests>();
-    auto provider =
-        std::make_unique<WebGraphicsContext3DProviderForTests>(std::move(gl));
-    GLES2InterfaceForTests* gl_ =
-        static_cast<GLES2InterfaceForTests*>(provider->ContextGL());
-    EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-    Platform::GraphicsInfo graphics_info;
-    graphics_info.using_gpu_compositing = true;
-    drawing_buffer_ = DrawingBufferForTests::Create(
-        std::move(provider), graphics_info, gl_, initial_size,
-        DrawingBuffer::kPreserve, kDisableMultisampling);
-    CHECK(drawing_buffer_);
-    SetAndSaveRestoreState(true);
-    testing::Mock::VerifyAndClearExpectations(gl_);
-  }
-
-  void TearDown() override {
-    platform_.reset();
-  }
-
-  GLuint image_id0_;
-  std::unique_ptr<ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform>>
-      platform_;
-};
-
-TEST_F(DrawingBufferImageChromiumTest, VerifyResizingReallocatesImages) {
-  GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests();
-  viz::TestSharedImageInterface* sii =
-      drawing_buffer_->SharedImageInterfaceForTests();
-
-  viz::TransferableResource resource;
-  viz::ReleaseCallback release_callback;
-
-  gfx::Size initial_size(kInitialWidth, kInitialHeight);
-  gfx::Size alternate_size(kInitialWidth, kAlternateHeight);
-
-  // There should be currently one back buffer and therefore one SharedImage.
-  gpu::Mailbox mailbox1;
-  mailbox1.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(1u, sii->shared_image_count());
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox1));
-
-  // Produce one resource at size 100x100. This should create another buffer and
-  // therefore another SharedImage.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
-                                                           &release_callback));
-  EXPECT_EQ(initial_size, sii->MostRecentSize());
-  EXPECT_TRUE(resource.is_overlay_candidate);
-  EXPECT_EQ(initial_size, resource.size);
-  testing::Mock::VerifyAndClearExpectations(gl_);
-  VerifyStateWasRestored();
-  gpu::Mailbox mailbox2;
-  mailbox2.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox1));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox2));
-  EXPECT_EQ(mailbox2, resource.mailbox_holder.mailbox);
-
-  // Resize to 100x50. The current backbuffer must be destroyed. The exported
-  // resource should stay alive. A new backbuffer must be created.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  drawing_buffer_->Resize(alternate_size);
-  VerifyStateWasRestored();
-  gpu::Mailbox mailbox3;
-  mailbox3.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox1));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox2));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox3));
-  testing::Mock::VerifyAndClearExpectations(gl_);
-
-  // Return the exported resource. Now it should get destroyed too.
-  std::move(release_callback).Run(gpu::SyncToken(), false /* lostResource */);
-  VerifyStateWasRestored();
-  EXPECT_EQ(1u, sii->shared_image_count());
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox1));
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox2));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox3));
-
-  // Produce a resource at the new size.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
-                                                           &release_callback));
-  EXPECT_EQ(alternate_size, sii->MostRecentSize());
-  EXPECT_TRUE(resource.is_overlay_candidate);
-  EXPECT_EQ(alternate_size, resource.size);
-  gpu::Mailbox mailbox4;
-  mailbox4.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox3));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox4));
-  EXPECT_EQ(mailbox4, resource.mailbox_holder.mailbox);
-  testing::Mock::VerifyAndClearExpectations(gl_);
-
-  // Reset to initial size. The exported resource has to stay alive, but the
-  // current back buffer must be destroyed and a new one with the right size
-  // must be created.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  drawing_buffer_->Resize(initial_size);
-  VerifyStateWasRestored();
-  gpu::Mailbox mailbox5;
-  mailbox5.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox3));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox4));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox5));
-  testing::Mock::VerifyAndClearExpectations(gl_);
-
-  // Return the exported resource. Now it will be destroyed too.
-  std::move(release_callback).Run(gpu::SyncToken(), false /* lostResource */);
-  VerifyStateWasRestored();
-  EXPECT_EQ(1u, sii->shared_image_count());
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox3));
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox4));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox5));
-
-  // Prepare another resource and verify that it's the correct size.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
-                                                           &release_callback));
-  EXPECT_EQ(initial_size, sii->MostRecentSize());
-  EXPECT_TRUE(resource.is_overlay_candidate);
-  EXPECT_EQ(initial_size, resource.size);
-  testing::Mock::VerifyAndClearExpectations(gl_);
-  gpu::Mailbox mailbox6;
-  mailbox6.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox5));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox6));
-
-  // Prepare one final resource and verify that it's the correct size. We should
-  // recycle the previously exported resource and avoid allocating a new
-  // SharedImage.
-  std::move(release_callback).Run(gpu::SyncToken(), false /* lostResource */);
-  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
-                                                           &release_callback));
-  EXPECT_EQ(initial_size, sii->MostRecentSize());
-  EXPECT_TRUE(resource.is_overlay_candidate);
-  EXPECT_EQ(initial_size, resource.size);
-  std::move(release_callback).Run(gpu::SyncToken(), false /* lostResource */);
-  EXPECT_EQ(2u, sii->shared_image_count());
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox5));
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox6));
-
-  drawing_buffer_->BeginDestruction();
-  testing::Mock::VerifyAndClearExpectations(sii);
-  EXPECT_EQ(0u, sii->shared_image_count());
-}
-
-TEST_F(DrawingBufferImageChromiumTest, AllocationFailure) {
-  GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests();
-  viz::TestGpuMemoryBufferManager* gmb_manager =
-      static_cast<viz::TestGpuMemoryBufferManager*>(
-          Platform::Current()->GetGpuMemoryBufferManager());
-  viz::TestSharedImageInterface* sii =
-      drawing_buffer_->SharedImageInterfaceForTests();
-
-  viz::TransferableResource resource1;
-  viz::ReleaseCallback release_callback1;
-  viz::TransferableResource resource2;
-  viz::ReleaseCallback release_callback2;
-  viz::TransferableResource resource3;
-  viz::ReleaseCallback release_callback3;
-
-  // Request a resource. A SharedImage should already be created. Everything
-  // works as expected.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1,
-                                                           &release_callback1));
-  EXPECT_TRUE(resource1.is_overlay_candidate);
-  gpu::Mailbox mailbox1;
-  mailbox1.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox1));
-  testing::Mock::VerifyAndClearExpectations(gl_);
-  VerifyStateWasRestored();
-
-  // Force GpuMemoryBuffer creation failure. Request another resource. It should
-  // still be provided, but this time with allowOverlay = false.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  gmb_manager->SetFailOnCreate(true);
-  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2,
-                                                           &release_callback2));
-  EXPECT_FALSE(resource2.is_overlay_candidate);
-  gpu::Mailbox mailbox2;
-  mailbox2.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox2));
-  VerifyStateWasRestored();
-
-  // Check that if GpuMemoryBuffer allocation starts working again, resources
-  // are correctly created with allowOverlay = true.
-  EXPECT_CALL(*gl_, CreateAndTexStorage2DSharedImageCHROMIUMMock(_)).Times(1);
-  gmb_manager->SetFailOnCreate(false);
-  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3,
-                                                           &release_callback3));
-  EXPECT_TRUE(resource3.is_overlay_candidate);
-  gpu::Mailbox mailbox3;
-  mailbox3.SetName(gl_->last_imported_shared_image()->name);
-  EXPECT_TRUE(sii->CheckSharedImageExists(mailbox3));
-  testing::Mock::VerifyAndClearExpectations(gl_);
-  VerifyStateWasRestored();
-
-  std::move(release_callback1).Run(gpu::SyncToken(), false /* lostResource */);
-  std::move(release_callback2).Run(gpu::SyncToken(), false /* lostResource */);
-  std::move(release_callback3).Run(gpu::SyncToken(), false /* lostResource */);
-
-  drawing_buffer_->BeginDestruction();
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox1));
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox2));
-  EXPECT_FALSE(sii->CheckSharedImageExists(mailbox3));
-}
-
 class DepthStencilTrackingGLES2Interface
     : public gpu::gles2::GLES2InterfaceStub {
  public:
diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc b/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
index 6f4bc84..f20257e 100644
--- a/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
+++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image_to_video_frame_copier.cc
@@ -196,7 +196,8 @@
       mailbox_holder.sync_token.GetConstData());
   context_provider->RasterInterface()->ReadbackARGBPixelsAsync(
       mailbox_holder.mailbox, mailbox_holder.texture_target, image_origin,
-      src_point, info, temp_argb_frame->stride(media::VideoFrame::kARGBPlane),
+      image_size, src_point, info,
+      temp_argb_frame->stride(media::VideoFrame::kARGBPlane),
       temp_argb_frame->GetWritableVisibleData(media::VideoFrame::kARGBPlane),
       WTF::BindOnce(&StaticBitmapImageToVideoFrameCopier::OnARGBPixelsReadAsync,
                     weak_ptr_factory_.GetWeakPtr(), image, temp_argb_frame,
diff --git a/third_party/blink/renderer/platform/network/http_parsers.cc b/third_party/blink/renderer/platform/network/http_parsers.cc
index cf9f5c9..e220e26f 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers.cc
@@ -171,9 +171,10 @@
       std::move(sources), std::move(nonces), std::move(hashes),
       source_list->allow_self, source_list->allow_star,
       source_list->allow_response_redirects, source_list->allow_inline,
-      source_list->allow_eval, source_list->allow_wasm_eval,
-      source_list->allow_wasm_unsafe_eval, source_list->allow_dynamic,
-      source_list->allow_unsafe_hashes, source_list->report_sample);
+      source_list->allow_inline_speculation_rules, source_list->allow_eval,
+      source_list->allow_wasm_eval, source_list->allow_wasm_unsafe_eval,
+      source_list->allow_dynamic, source_list->allow_unsafe_hashes,
+      source_list->report_sample);
 }
 
 blink::ContentSecurityPolicyHeaderPtr ConvertToBlink(
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.h b/third_party/blink/renderer/platform/weborigin/kurl.h
index 7b9dd9d..d33bb3f 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.h
+++ b/third_party/blink/renderer/platform/weborigin/kurl.h
@@ -172,8 +172,9 @@
   bool ProtocolIsJavaScript() const;
   bool ProtocolIsInHTTPFamily() const;
   bool IsLocalFile() const;
-  bool IsAboutBlankURL() const;   // Is exactly about:blank.
-  bool IsAboutSrcdocURL() const;  // Is exactly about:srcdoc.
+  bool IsAboutBlankURL() const;   // Is about:blank, ignoring query/ref strings.
+  bool IsAboutSrcdocURL() const;  // Is about:srcdoc, ignoring query/ref
+                                  // strings..
 
   bool SetProtocol(const String&);
   void SetHost(const String&);
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7af45cd..264f217 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6142,10 +6142,6 @@
 crbug.com/1339293 [ Linux ] virtual/threaded/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html [ Failure Pass ]
 crbug.com/webrtc/14228 external/wpt/webrtc/protocol/h264-profile-levels.https.html [ Failure Pass ]
 
-# WebRTC: temporarily disabled to allow msid fixes to roll.
-crbug.com/webrtc/14729 external/wpt/webrtc/RTCRtpTransceiver.https.html [ Failure Pass ]
-crbug.com/webrtc/14729 external/wpt/webrtc/RTCTrackEvent-fire.html [ Failure Pass ]
-
 external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-networkState.html [ Crash Failure Pass Timeout ]
 
 # Sheriff 2022-06-29
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 4b882ba7..31fd468d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1064,13 +1064,18 @@
   {
     "prefix": "prerender",
     "platforms": ["Linux", "Mac", "Win"],
-    "bases": [ "external/wpt/speculation-rules/prerender",
-               "wpt_internal/prerender",
-               "http/tests/inspector-protocol/prerender"],
-    "exclusive_tests": ["external/wpt/speculation-rules/prerender/referrer-policy-from-rules.html"],
+    "bases": [
+      "external/wpt/speculation-rules/prerender",
+      "wpt_internal/prerender",
+      "http/tests/inspector-protocol/prerender"
+    ],
+    "exclusive_tests": [
+      "external/wpt/speculation-rules/prerender/referrer-policy-from-rules.html",
+      "external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html"
+    ],
     "args": [
       "--enable-blink-features=SpeculationRulesReferrerPolicyKey",
-      "--enable-features=SameSiteCrossOriginForSpeculationRulesPrerender"
+      "--enable-features=SameSiteCrossOriginForSpeculationRulesPrerender,Prerender2ContentSecurityPolicyExtensions"
     ],
     "expires": "Jul 1, 2023"
   },
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html
new file mode 100644
index 0000000..923598b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-inline-speculation-rules.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "true", "initial document.prerendering");
+    }, 'Test if CSP script-src inline-speculation-rules permits inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html.ini
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html.ini
rename to third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.tentative.html.ini
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html
new file mode 100644
index 0000000..f0f9784
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-self.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "blocked by script-src-elem", "csp block");
+    }, 'Test if CSP script-src self does not permit inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html
new file mode 100644
index 0000000..f6925f59
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-unsafe-inline.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "true", "initial document.prerendering");
+    }, 'Test if CSP script-src unsafe-inline permits inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html
new file mode 100644
index 0000000..febfbd01
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'inline-speculation-rules'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
+<script>
+  const params = new URLSearchParams(location.search);
+  writeValueToServer(params.get('key'), "csp is ignored unexpectedly");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html
new file mode 100644
index 0000000..8dc3820
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+
+<head>
+  <!-- disallow inline script -->
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
+<script>
+  const params = new URLSearchParams(location.search);
+  writeValueToServer(params.get('key'), "csp is ignored unexpectedly");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html
new file mode 100644
index 0000000..d2f010d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js
new file mode 100644
index 0000000..52419f9b4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js
@@ -0,0 +1,57 @@
+const params = new URLSearchParams(location.search);
+
+// Take a key used for storing a test result in the server.
+const key = params.get('key');
+
+// Speculation rules injection is not blocked in the csp-script-src 'self' test.
+const block = location.pathname.endsWith('csp-script-src-self.html');
+
+// The main test page (csp-script-src-*.html) in the parent directory) will load
+// this page only with the "key" parameter. This page will then try prerendering
+// itself with the "run-test" parameter. When "run-test" is in the URL we'll
+// actually start the test process and record the results to send back to the
+// main test page. We do this because the main test page cannot navigate itself
+// but it also cannot open a popup to a prerendered browsing context so the
+// prerender triggering and activation must both happen in this popup.
+const run_test = params.has('run-test');
+if (!run_test) {
+  // Generate a new stash key so we can communicate with the prerendered page
+  // about when to close the popup.
+  const done_key = token();
+  const url = new URL(document.URL);
+  url.searchParams.append('run-test', '');
+  url.searchParams.append('done-key', done_key);
+
+  if (block) {
+    // Observe `securitypolicyviolation` event that will be triggered by
+    // startPrerendering().
+    document.addEventListener('securitypolicyviolation', e => {
+      if (e.effectiveDirective != 'script-src' &&
+          e.effectiveDirective != 'script-src-elem') {
+        const message = 'unexpected effective directive: ' + e.effectiveDirective;
+        writeValueToServer(key, message).then(() => { window.close(); });
+      } else {
+        const message = 'blocked by ' + e.effectiveDirective;
+        writeValueToServer(key, message).then(() => { window.close(); });
+      }
+    });
+  }
+
+  startPrerendering(url.toString());
+
+  // Wait until the prerendered page signals us it's ready to close.
+  nextValueFromServer(done_key).then(() => {
+    window.close();
+  });
+} else {
+  if (block) {
+    writeValueToServer(key, 'unexpected prerendering');
+  } else {
+    // Tell the harness the initial document.prerendering value.
+    writeValueToServer(key, document.prerendering);
+
+    // Tell the prerendering initiating page test being finished.
+    const done_key = params.get('done-key');
+    writeValueToServer(done_key, "done");
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
index 574390a..0b57b55 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
@@ -3,7 +3,6 @@
 PASS checkAddTransceiverWithTrack
 PASS checkAddTransceiverWithAddTrack
 PASS checkAddTransceiverWithDirection
-FAIL checkMsidNoTrackId promise_test: Unhandled rejection with value: object "OperationError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. a=msid:fake-stream-id Expects 2 fields."
 PASS checkAddTransceiverWithSetRemoteOfferSending
 PASS checkAddTransceiverWithSetRemoteOfferNoSend
 PASS checkAddTransceiverBadKind
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html
index b02ce60..9a8fbed 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html
@@ -376,27 +376,6 @@
     hasProps(pc.getTransceivers(), []);
   };
 
-  const checkMsidNoTrackId = async t => {
-    const pc1 = new RTCPeerConnection();
-    const pc2 = new RTCPeerConnection();
-    t.add_cleanup(() => pc1.close());
-    t.add_cleanup(() => pc2.close());
-    const stream = await getNoiseStream({audio: true});
-    t.add_cleanup(() => stopTracks(stream));
-    const track = stream.getAudioTracks()[0];
-    pc1.addTrack(track, stream);
-    const offer = await pc1.createOffer();
-    await pc1.setLocalDescription(offer);
-    // Remove track-id from msid
-    // Fixate stream-id so that error message is consistent.
-    offer.sdp = offer.sdp.replace(/(a=msid:[^ \t]+).*\r\n/g,
-                                  "a=msid:fake-stream-id\r\n");
-    await pc2.setRemoteDescription(offer);
-    const answer = await pc2.createAnswer();
-    await pc1.setRemoteDescription(answer);
-    await pc2.setLocalDescription(answer);
-  };
-
   const checkNoMidOffer = async t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
@@ -2277,7 +2256,6 @@
   checkAddTransceiverWithTrack,
   checkAddTransceiverWithAddTrack,
   checkAddTransceiverWithDirection,
-  checkMsidNoTrackId,
   checkAddTransceiverWithSetRemoteOfferSending,
   checkAddTransceiverWithSetRemoteOfferNoSend,
   checkAddTransceiverBadKind,
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCTrackEvent-fire-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCTrackEvent-fire-expected.txt
deleted file mode 100644
index cb23952..0000000
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCTrackEvent-fire-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a testharness.js-based test.
-PASS When a=msid is absent, the track should still be associated with a stream
-PASS Source-level msid should be ignored if media-level msid is present
-PASS Source-level msid should be parsed if media-level msid is absent
-PASS Source-level msid should be ignored, or an error should be thrown, if a different media-level msid is present
-PASS stream ids should be found even if msid-semantic is absent
-FAIL a=msid:- should result in a track event with no streams promise_test: Unhandled rejection with value: object "OperationError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. a=msid:- Expects 2 fields."
-FAIL Duplicate a=msid should result in a track event with one stream assert_equals: track event has one stream expected 1 but got 2
-PASS Applying a remote description with removed msid should trigger firing a removetrack event on the corresponding stream
-PASS Applying a remote description with a new msid should trigger firing an event with populated streams
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/protocol/msid-parse.html b/third_party/blink/web_tests/external/wpt/webrtc/protocol/msid-parse.html
index 9630919..5596446 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/protocol/msid-parse.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/protocol/msid-parse.html
@@ -68,4 +68,16 @@
   assert_equals(trackevent.streams.length, 2);
 }, 'Description with two msid produces two streams');
 
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const ontrackPromise = addEventListenerPromise(t, pc, 'track');
+  await pc.setRemoteDescription({type: 'offer',
+                                 sdp: preamble + 'a=msid:foo\n'});
+  const trackevent = await ontrackPromise;
+  assert_equals(pc.getReceivers().length, 1);
+  assert_equals(trackevent.streams.length, 1);
+  assert_equals(trackevent.streams[0].id, 'foo');
+}, 'Description with msid foo but no track id is accepted');
+
 </script>
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index 98c2590e..055827a9 100644
--- a/third_party/libaom/README.chromium
+++ b/third_party/libaom/README.chromium
@@ -2,8 +2,8 @@
 Short Name: libaom
 URL: https://aomedia.googlesource.com/aom/
 Version: 3.5.0
-Date: Thursday December 01 2022
-Revision: c0239a23c24796ddc003f2a3199a9014a1930a80
+Date: Tuesday December 06 2022
+Revision: 55e7b1c59920923eb46060870096a1e411f3cb98
 CPEPrefix: cpe:/a:aomedia:aomedia:3.5.0
 License: BSD
 License File: source/libaom/LICENSE
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h
index 0ef82065..a8c7bc4 100644
--- a/third_party/libaom/source/config/config/aom_version.h
+++ b/third_party/libaom/source/config/config/aom_version.h
@@ -12,8 +12,8 @@
 #define VERSION_MAJOR 3
 #define VERSION_MINOR 5
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "493-gc0239a23c"
+#define VERSION_EXTRA "519-g55e7b1c59"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "3.5.0-493-gc0239a23c"
-#define VERSION_STRING " 3.5.0-493-gc0239a23c"
+#define VERSION_STRING_NOSP "3.5.0-519-g55e7b1c59"
+#define VERSION_STRING " 3.5.0-519-g55e7b1c59"
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/av1_rtcd.h b/third_party/libaom/source/config/ios/arm-neon/config/av1_rtcd.h
index c39e8796..c69112d4 100644
--- a/third_party/libaom/source/config/ios/arm-neon/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/ios/arm-neon/config/av1_rtcd.h
@@ -269,6 +269,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -276,6 +277,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/ios/arm64/config/av1_rtcd.h b/third_party/libaom/source/config/ios/arm64/config/av1_rtcd.h
index c39e8796..c69112d4 100644
--- a/third_party/libaom/source/config/ios/arm64/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/ios/arm64/config/av1_rtcd.h
@@ -269,6 +269,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -276,6 +277,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/av1_rtcd.h b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/av1_rtcd.h
index 2b739e31..a5801067 100644
--- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/av1_rtcd.h
@@ -307,6 +307,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -314,6 +315,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/av1_rtcd.h b/third_party/libaom/source/config/linux/arm-neon/config/av1_rtcd.h
index c39e8796..c69112d4 100644
--- a/third_party/libaom/source/config/linux/arm-neon/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm-neon/config/av1_rtcd.h
@@ -269,6 +269,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -276,6 +277,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/arm/config/av1_rtcd.h b/third_party/libaom/source/config/linux/arm/config/av1_rtcd.h
index 8091bdbf..41782b7 100644
--- a/third_party/libaom/source/config/linux/arm/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm/config/av1_rtcd.h
@@ -226,6 +226,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -233,6 +234,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/arm64/config/av1_rtcd.h b/third_party/libaom/source/config/linux/arm64/config/av1_rtcd.h
index c39e8796..c69112d4 100644
--- a/third_party/libaom/source/config/linux/arm64/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm64/config/av1_rtcd.h
@@ -269,6 +269,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -276,6 +277,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/generic/config/av1_rtcd.h b/third_party/libaom/source/config/linux/generic/config/av1_rtcd.h
index b9fd632..3e970bd 100644
--- a/third_party/libaom/source/config/linux/generic/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/generic/config/av1_rtcd.h
@@ -226,6 +226,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -233,6 +234,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/linux/ia32/config/av1_rtcd.h b/third_party/libaom/source/config/linux/ia32/config/av1_rtcd.h
index dfe5a88c..6bfaec0b 100644
--- a/third_party/libaom/source/config/linux/ia32/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/ia32/config/av1_rtcd.h
@@ -420,37 +420,50 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 void av1_calc_indices_dim1_sse2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 void av1_calc_indices_dim1_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim1)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
+void av1_calc_indices_dim2_sse2(const int* data,
+                                const int* centroids,
+                                uint8_t* indices,
+                                int64_t* total_dist,
+                                int n,
+                                int k);
 void av1_calc_indices_dim2_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim2)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
@@ -2900,7 +2913,7 @@
   av1_calc_indices_dim1 = av1_calc_indices_dim1_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim1 = av1_calc_indices_dim1_avx2;
-  av1_calc_indices_dim2 = av1_calc_indices_dim2_c;
+  av1_calc_indices_dim2 = av1_calc_indices_dim2_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim2 = av1_calc_indices_dim2_avx2;
   av1_compute_cross_correlation = av1_compute_cross_correlation_c;
diff --git a/third_party/libaom/source/config/linux/x64/config/av1_rtcd.h b/third_party/libaom/source/config/linux/x64/config/av1_rtcd.h
index dfe5a88c..6bfaec0b 100644
--- a/third_party/libaom/source/config/linux/x64/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/linux/x64/config/av1_rtcd.h
@@ -420,37 +420,50 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 void av1_calc_indices_dim1_sse2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 void av1_calc_indices_dim1_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim1)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
+void av1_calc_indices_dim2_sse2(const int* data,
+                                const int* centroids,
+                                uint8_t* indices,
+                                int64_t* total_dist,
+                                int n,
+                                int k);
 void av1_calc_indices_dim2_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim2)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
@@ -2900,7 +2913,7 @@
   av1_calc_indices_dim1 = av1_calc_indices_dim1_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim1 = av1_calc_indices_dim1_avx2;
-  av1_calc_indices_dim2 = av1_calc_indices_dim2_c;
+  av1_calc_indices_dim2 = av1_calc_indices_dim2_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim2 = av1_calc_indices_dim2_avx2;
   av1_compute_cross_correlation = av1_compute_cross_correlation_c;
diff --git a/third_party/libaom/source/config/win/arm64/config/av1_rtcd.h b/third_party/libaom/source/config/win/arm64/config/av1_rtcd.h
index c39e8796..c69112d4 100644
--- a/third_party/libaom/source/config/win/arm64/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/win/arm64/config/av1_rtcd.h
@@ -269,6 +269,7 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim1 av1_calc_indices_dim1_c
@@ -276,6 +277,7 @@
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 #define av1_calc_indices_dim2 av1_calc_indices_dim2_c
diff --git a/third_party/libaom/source/config/win/ia32/config/av1_rtcd.h b/third_party/libaom/source/config/win/ia32/config/av1_rtcd.h
index dfe5a88c..6bfaec0b 100644
--- a/third_party/libaom/source/config/win/ia32/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/win/ia32/config/av1_rtcd.h
@@ -420,37 +420,50 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 void av1_calc_indices_dim1_sse2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 void av1_calc_indices_dim1_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim1)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
+void av1_calc_indices_dim2_sse2(const int* data,
+                                const int* centroids,
+                                uint8_t* indices,
+                                int64_t* total_dist,
+                                int n,
+                                int k);
 void av1_calc_indices_dim2_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim2)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
@@ -2900,7 +2913,7 @@
   av1_calc_indices_dim1 = av1_calc_indices_dim1_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim1 = av1_calc_indices_dim1_avx2;
-  av1_calc_indices_dim2 = av1_calc_indices_dim2_c;
+  av1_calc_indices_dim2 = av1_calc_indices_dim2_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim2 = av1_calc_indices_dim2_avx2;
   av1_compute_cross_correlation = av1_compute_cross_correlation_c;
diff --git a/third_party/libaom/source/config/win/x64/config/av1_rtcd.h b/third_party/libaom/source/config/win/x64/config/av1_rtcd.h
index dfe5a88c..6bfaec0b 100644
--- a/third_party/libaom/source/config/win/x64/config/av1_rtcd.h
+++ b/third_party/libaom/source/config/win/x64/config/av1_rtcd.h
@@ -420,37 +420,50 @@
 void av1_calc_indices_dim1_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
 void av1_calc_indices_dim1_sse2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 void av1_calc_indices_dim1_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim1)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
 void av1_calc_indices_dim2_c(const int* data,
                              const int* centroids,
                              uint8_t* indices,
+                             int64_t* total_dist,
                              int n,
                              int k);
+void av1_calc_indices_dim2_sse2(const int* data,
+                                const int* centroids,
+                                uint8_t* indices,
+                                int64_t* total_dist,
+                                int n,
+                                int k);
 void av1_calc_indices_dim2_avx2(const int* data,
                                 const int* centroids,
                                 uint8_t* indices,
+                                int64_t* total_dist,
                                 int n,
                                 int k);
 RTCD_EXTERN void (*av1_calc_indices_dim2)(const int* data,
                                           const int* centroids,
                                           uint8_t* indices,
+                                          int64_t* total_dist,
                                           int n,
                                           int k);
 
@@ -2900,7 +2913,7 @@
   av1_calc_indices_dim1 = av1_calc_indices_dim1_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim1 = av1_calc_indices_dim1_avx2;
-  av1_calc_indices_dim2 = av1_calc_indices_dim2_c;
+  av1_calc_indices_dim2 = av1_calc_indices_dim2_sse2;
   if (flags & HAS_AVX2)
     av1_calc_indices_dim2 = av1_calc_indices_dim2_avx2;
   av1_compute_cross_correlation = av1_compute_cross_correlation_c;
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index f7054cf..e2e5610a 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: 32163382b9d9c02eaea9ab5cd576b3a91890f133
+Version: f73b23e720a9c587e1d4663b77775edb30aed4ae
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/tflite/README.chromium b/third_party/tflite/README.chromium
index f3140b3..338a638 100644
--- a/third_party/tflite/README.chromium
+++ b/third_party/tflite/README.chromium
@@ -1,8 +1,8 @@
 Name: TensorFlow Lite
 Short Name: tflite
 URL: https://github.com/tensorflow/tensorflow
-Version: 0ac64752b31fd6f9ccb0209a48453c6b110f75e1
-Date: 2022/12/05
+Version: be3be938f3c349daf0b0ef4b717fc2fc7010a974
+Date: 2022/12/12
 License: Apache 2.0
 License File: LICENSE
 Security Critical: Yes
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index d78aeaa1..72a05e2 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -19,13 +19,13 @@
       'chromeos-betty-arc-r-chrome': 'chromeos_betty-arc-r_include_unwind_tables_official_use_fake_dbus_clients_reclient',
       'chromeos-betty-pi-arc-cfi-thin-lto-chrome': 'chromeos_betty-pi-arc_cfi_thin_lto_official_reclient',
       'chromeos-betty-pi-arc-chrome': 'chromeos_betty-pi-arc_dchecks_reclient',
-      'chromeos-eve-arc-r-chrome': 'chromeos_eve-arc-r_include_unwind_tables_official',
+      'chromeos-eve-arc-r-chrome': 'chromeos_eve-arc-r_include_unwind_tables_official_reclient',
       'chromeos-eve-chrome': 'chromeos_eve_include_unwind_tables_official_dchecks_reclient',
       'chromeos-jacuzzi-chrome': 'chromeos_jacuzzi_include_unwind_tables_official_reclient',
       'chromeos-kevin-chrome': 'chromeos_kevin_include_unwind_tables_official_dchecks_reclient',
       'chromeos-octopus-chrome': 'chromeos_octopus_include_unwind_tables_official_dchecks_reclient',
       'chromeos-reven-chrome': 'chromeos_reven_include_unwind_tables_official_dchecks_reclient',
-      'chromeos-trogdor64-chrome-skylab': 'chromeos_trogdor64_include_unwind_tables_official_dchecks_skylab',
+      'chromeos-trogdor64-chrome-skylab': 'chromeos_trogdor64_include_unwind_tables_official_dchecks_skylab_reclient',
       'lacros-amd64-generic-chrome': 'chromeos_amd64-generic_lacros_official_no_symbols_reclient',
       'lacros-amd64-generic-chrome-skylab': 'chromeos_amd64-generic_lacros_official_skylab_reclient',
       'lacros-arm-generic-chrome': 'chromeos_arm-generic_lacros_official_reclient',
@@ -638,6 +638,10 @@
       'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release_reclient',
     },
 
+    'chromium.memory.fyi': {
+      'linux-ubsan-fyi-rel': 'ubsan_release_bot_reclient',
+    },
+
     'chromium.perf': {
       'Android Builder Perf': 'official_goma_minimal_symbols_android',
       'Android arm64 Builder Perf': 'official_goma_minimal_symbols_android_arm64',
@@ -1176,6 +1180,7 @@
       'linux-rel-inverse-fyi': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage_do_typecheck',
       'linux-rel-ml': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_do_typecheck_reclient',
       'linux-rel-warmed': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
+      'linux-ubsan-fyi-rel': 'ubsan_release_bot_reclient',
       'linux-viz-rel': 'release_trybot',
       'linux-wayland-rel': 'gpu_tests_wayland_release_trybot_no_symbols_use_dummy_lastchange_code_coverage_reclient',
       'linux-webkit-msan-rel': 'msan_release_bot_reclient',
@@ -2159,6 +2164,9 @@
     'chromeos_eve-arc-r_include_unwind_tables_official': [
       'chromeos_device', 'eve-arc-r', 'include_unwind_tables', 'official',
     ],
+    'chromeos_eve-arc-r_include_unwind_tables_official_reclient': [
+      'chromeos_device_reclient', 'eve-arc-r', 'include_unwind_tables', 'official',
+    ],
 
     'chromeos_eve_include_unwind_tables_official_dchecks': [
       'chromeos_device', 'eve', 'include_unwind_tables', 'official', 'dcheck_always_on',
@@ -2242,6 +2250,9 @@
     'chromeos_trogdor64_include_unwind_tables_official_dchecks_skylab': [
       'chromeos_device', 'trogdor64', 'include_unwind_tables', 'official', 'dcheck_always_on', 'is_skylab',
     ],
+    'chromeos_trogdor64_include_unwind_tables_official_dchecks_skylab_reclient': [
+      'chromeos_device_reclient', 'trogdor64', 'include_unwind_tables', 'official', 'dcheck_always_on', 'is_skylab',
+    ],
 
     'chromeos_with_codecs_debug_bot_reclient': [
       'chromeos_with_codecs', 'debug_bot_reclient',
diff --git a/tools/mb/mb_config_expectations/chrome.json b/tools/mb/mb_config_expectations/chrome.json
index 377c2b3..ef22f2cd 100644
--- a/tools/mb/mb_config_expectations/chrome.json
+++ b/tools/mb/mb_config_expectations/chrome.json
@@ -82,7 +82,7 @@
       "is_chromeos_device": true,
       "is_official_build": true,
       "ozone_platform_headless": true,
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "chromeos-eve-chrome": {
@@ -160,7 +160,7 @@
       "is_official_build": true,
       "is_skylab": true,
       "ozone_platform_headless": true,
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "lacros-amd64-generic-chrome": {
diff --git a/tools/mb/mb_config_expectations/chromium.memory.fyi.json b/tools/mb/mb_config_expectations/chromium.memory.fyi.json
new file mode 100644
index 0000000..72a154e06
--- /dev/null
+++ b/tools/mb/mb_config_expectations/chromium.memory.fyi.json
@@ -0,0 +1,11 @@
+{
+  "linux-ubsan-fyi-rel": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "is_component_build": false,
+      "is_debug": false,
+      "is_ubsan": true,
+      "use_remoteexec": true
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index 1bb587d..a0110db 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -458,6 +458,15 @@
       "use_goma": true
     }
   },
+  "linux-ubsan-fyi-rel": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "is_component_build": false,
+      "is_debug": false,
+      "is_ubsan": true,
+      "use_remoteexec": true
+    }
+  },
   "linux-viz-rel": {
     "gn_args": {
       "dcheck_always_on": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0e61c6c..db9c707 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -172,6 +172,11 @@
   <int value="2" label="ChromeOS quick settings cast menu"/>
 </enum>
 
+<enum name="AccessCodeCastUiTabSwitcherUsage">
+  <int value="0" label="Tab switcher UI shown and not used"/>
+  <int value="1" label="Tab switcher UI shown and used to switch tabs"/>
+</enum>
+
 <enum name="AccessibilityAndroidAnimationsEnabled">
   <obsolete>
     Removed from code 2020/10.
@@ -32296,6 +32301,7 @@
   <int value="1039" label="FloatingWorkspaceV2Enabled"/>
   <int value="1041" label="NewBaseUrlInheritanceBehaviorAllowed"/>
   <int value="1042" label="ShowCastSessionsStartedByOtherDevices"/>
+  <int value="1043" label="CloudAPAuthEnabled"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -50773,6 +50779,7 @@
   <int value="5" label="DEFAULT_TO_FORCE_ENABLED"/>
   <int value="6" label="FORCE_ENABLED_TO_DISABLED"/>
   <int value="7" label="FORCE_ENABLED_TO_DEFAULT"/>
+  <int value="8" label="FORCE_ENABLED_TO_ENABLED"/>
 </enum>
 
 <enum name="IMEAutocorrectQualityBreakdown">
@@ -55317,6 +55324,12 @@
   <int value="1003" label="LAUNCH_RESULT_FAILURE"/>
 </enum>
 
+<enum name="LauncherSearchSessionResult">
+  <int value="0" label="Quit"/>
+  <int value="1" label="Launch"/>
+  <int value="2" label="AnswerCardImpression"/>
+</enum>
+
 <enum name="LauncherUserAction">
   <summary>
     List of actions that represent a launcher workflow that the user can perform
@@ -59167,6 +59180,7 @@
   <int value="-860578793" label="TabGridLayoutAndroid:disabled"/>
   <int value="-860534647" label="SiteDetails:enabled"/>
   <int value="-859583725" label="WebRtcTimerUsesMetronome:enabled"/>
+  <int value="-859193162" label="KeepSecondaryZeroSuggest:disabled"/>
   <int value="-858280894" label="DelayCompetingLowPriorityRequests:disabled"/>
   <int value="-856915246" label="enable-new-audio-rendering-mixing-strategy"/>
   <int value="-856407187" label="OnDeviceGrammarCheck:disabled"/>
@@ -62244,6 +62258,7 @@
   <int value="975104092" label="show-taps"/>
   <int value="975249239" label="PasswordManagerRedesign:enabled"/>
   <int value="975463471" label="WebViewExtraHeadersSameOriginOnly:enabled"/>
+  <int value="975814920" label="KeepSecondaryZeroSuggest:enabled"/>
   <int value="976079108" label="TouchpadOverscrollHistoryNavigation:disabled"/>
   <int value="976767701" label="CategoricalSearch:disabled"/>
   <int value="976809924"
@@ -72506,6 +72521,10 @@
   <int value="158" label="ARCVM Data Migration"/>
   <int value="159" label="WebHID"/>
   <int value="160" label="Do Not Disturb"/>
+  <int value="161" label="Dictation all DLCs downloaded"/>
+  <int value="162" label="Dictation no DLCs downloaded"/>
+  <int value="163" label="Dictation only Pumpkin DLC downloaded"/>
+  <int value="164" label="Dictation only SODA DLC downloaded"/>
 </enum>
 
 <enum name="NotificationDatabaseStatus">
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index 08c5e92..9c7f5e0 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -115,6 +115,15 @@
   <variant name="User" summary="Installed by user"/>
 </variants>
 
+<variants name="LauncherSearchEntryPoint">
+  <variant name="HomeButton"
+      summary="Open launcher by activationg home button"/>
+  <variant name="Others" summary="Open launcher using a source we don't track"/>
+  <variant name="Scroll" summary="Open launcher by scrolling on the shelf"/>
+  <variant name="SearchKey" summary="Open launcher by pressing search key"/>
+  <variant name="Swipe" summary="Open launcher by swiping on the shelf"/>
+</variants>
+
 <variants name="LauncherUISurface">
   <variant name=".AppsSearch" summary="App tiles search"/>
   <variant name=".AppsZeroState" summary="App tiles zero-state"/>
@@ -851,6 +860,46 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.AppList.Search.Session.Error"
+    enum="AppListUserEventError" expires_after="2023-01-31">
+  <owner>yulunwu@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tbarzic@chromium.org</owner>
+  <summary>
+    Records errors in processing other Apps.AppList.Search.Session.* metrics.
+    These are expected to be rare and bucket proportion is not meaningful.
+  </summary>
+</histogram>
+
+<histogram name="Apps.AppList.Search.Session.{LauncherSearchEntryPoint}"
+    enum="LauncherSearchSessionResult" expires_after="2023-01-31">
+  <owner>yulunwu@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tbarzic@chromium.org</owner>
+  <summary>
+    Tracks search result launches, answer card impressions, and search session
+    abandons for launcher search sessions.
+
+    A search session is started when a query is entered into the bubble launcher
+    search box or when the tablet mode device enters the kFullscreenSearch
+    AppListViewState. Each search session must conclude one of following ways:
+
+    Quit: Recorded when ending a search session without launching a result and
+    no answer card was shown long enough to have make an impression.
+
+    Launch: Recorded when a search session is ended by launching a result. An
+    answer card may or may not have been shown long enough to make an
+    impression.
+
+    AnswerCardImpression: An answer card was shown long enough to the user to
+    have made an impression at some point during the search session. The answer
+    card may or may not be shown at the moment the search session ended.
+  </summary>
+  <token key="LauncherSearchEntryPoint" variants="LauncherSearchEntryPoint"/>
+</histogram>
+
 <histogram name="Apps.AppList.Search.{SearchView}" enum="AppListSearchAction"
     expires_after="2023-01-31">
   <owner>wrong@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/download/histograms.xml b/tools/metrics/histograms/metadata/download/histograms.xml
index 22b4d348..1f8c883 100644
--- a/tools/metrics/histograms/metadata/download/histograms.xml
+++ b/tools/metrics/histograms/metadata/download/histograms.xml
@@ -30,6 +30,7 @@
 <variants name="DownloadClient">
   <variant name="__Test__"/>
   <variant name="BackgroundFetch"/>
+  <variant name="Bruschetta"/>
   <variant name="Debugging"/>
   <variant name="MountainInternal"/>
   <variant name="OfflinePage"/>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index bb72331..c0162b3 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -67,8 +67,8 @@
 </histogram>
 
 <histogram name="BackForwardCache.AllSites.HistoryNavigationOutcome"
-    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2023-04-23">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, this records whether
@@ -81,8 +81,8 @@
 
 <histogram
     name="BackForwardCache.AllSites.HistoryNavigationOutcome.BlocklistedFeature"
-    enum="WebSchedulerTrackedFeature" expires_after="2023-04-30">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="WebSchedulerTrackedFeature" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, this records what
@@ -98,8 +98,8 @@
 <histogram
     name="BackForwardCache.AllSites.HistoryNavigationOutcome.BrowsingInstanceNotSwappedReason"
     enum="BackForwardCacheBrowsingInstanceNotSwappedReason"
-    expires_after="2023-05-07">
-  <owner>hajimehoshi@chromium.org</owner>
+    expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history and the
@@ -134,8 +134,8 @@
 
 <histogram
     name="BackForwardCache.AllSites.HistoryNavigationOutcome.NotRestoredReason"
-    enum="BackForwardCacheNotRestoredReason" expires_after="2023-04-30">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="BackForwardCacheNotRestoredReason" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, record why it wasn't
@@ -164,8 +164,8 @@
 
 <histogram name="BackForwardCache.EvictedAfterDocumentRestoredReason"
     enum="BackForwardCacheEvictedAfterDocumentRestoredReason"
-    expires_after="2023-04-23">
-  <owner>hajimehoshi@chromium.org</owner>
+    expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     Records the reason why the page is evicted from the back-forward cache but
@@ -190,8 +190,8 @@
 </histogram>
 
 <histogram name="BackForwardCache.Eviction.TimeUntilProcessKilled" units="ms"
-    expires_after="2022-10-01">
-  <owner>hajimehoshi@chromium.org</owner>
+    expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     Records the time duration between the last time when the entry goes to the
@@ -234,8 +234,8 @@
 </histogram>
 
 <histogram name="BackForwardCache.HistoryNavigationOutcome"
-    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2022-04-17">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, this records whether
@@ -248,8 +248,8 @@
 </histogram>
 
 <histogram name="BackForwardCache.HistoryNavigationOutcome.BlocklistedFeature"
-    enum="WebSchedulerTrackedFeature" expires_after="2023-04-16">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="WebSchedulerTrackedFeature" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, this records what
@@ -266,8 +266,8 @@
 <histogram
     name="BackForwardCache.HistoryNavigationOutcome.BrowsingInstanceNotSwappedReason"
     enum="BackForwardCacheBrowsingInstanceNotSwappedReason"
-    expires_after="2023-05-07">
-  <owner>hajimehoshi@chromium.org</owner>
+    expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history and the
@@ -283,7 +283,7 @@
 <histogram
     name="BackForwardCache.HistoryNavigationOutcome.DisabledForRenderFrameHostReason2"
     enum="BackForwardCacheDisabledForRenderFrameHostReason2"
-    expires_after="2023-04-16">
+    expires_after="2023-06-30">
   <owner>carlscab@chromium.org</owner>
   <owner>hajimehoshi@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
@@ -303,9 +303,9 @@
 
 <histogram
     name="BackForwardCache.HistoryNavigationOutcome.DisallowActivationReason"
-    enum="InactiveFrameDisallowActivationReason" expires_after="2021-11-30">
+    enum="InactiveFrameDisallowActivationReason" expires_after="2023-06-30">
   <owner>carlscab@chromium.org</owner>
-  <owner>hajimehoshi@chromium.org</owner>
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     For history navigations, records the reason passed to
@@ -342,8 +342,8 @@
 </histogram>
 
 <histogram name="BackForwardCache.HistoryNavigationOutcome.NotRestoredReason"
-    enum="BackForwardCacheNotRestoredReason" expires_after="2023-05-07">
-  <owner>hajimehoshi@chromium.org</owner>
+    enum="BackForwardCacheNotRestoredReason" expires_after="2023-06-30">
+  <owner>fergal@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     When navigating back to a page in the session history, record why it wasn't
@@ -455,7 +455,7 @@
 <histogram
     name="BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName"
     enum="MojoInterfaceName" expires_after="2023-05-07">
-  <owner>carlscab@google.com</owner>
+  <owner>fergal@google.com</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
     Hash (base::HashMetricName) of a mojo interface name.
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index a3075eb..356f499 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -390,7 +390,7 @@
 </histogram>
 
 <histogram name="Omnibox.CharTypedToRepaintLatency.PaintToPresent" units="ms"
-    expires_after="2021-06-30">
+    expires_after="2023-05-07">
   <owner>asvitkine@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -406,6 +406,9 @@
     fixed by using callbacks that are only called after the first following
     successful presentation, so a slight change might be noticed in the
     histogram values.
+
+    Warning: this histogram was expired from 2021-06-30 to 2022-12-12; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -2533,6 +2536,36 @@
   <token key="NavigationPrefetchOnly" variants="NavigationPrefetchOnly"/>
 </histogram>
 
+<histogram name="Realbox.CharTypedToRepaintLatency.ToPaint" units="ms"
+    expires_after="2023-05-07">
+  <owner>mahmadi@chromium.org</owner>
+  <owner>chrome-desktop-search@google.com</owner>
+  <summary>
+    Records the time taken between a keystroke being typed in the NTP realbox
+    and the time when we're ready to render the results in the DOM. If there are
+    multiple keystrokes before the results are rendered, logs the time since the
+    earliest. This is comparable to Omnibox.CharTypedToRepaintLatency.ToPaint as
+    it does not take into account the time the DOM change task spends in the
+    message loop nor its duration.
+  </summary>
+</histogram>
+
+<histogram name="Realbox.ResultChangedToRepaintLatency.ToPaint" units="ms"
+    expires_after="2023-05-07">
+  <owner>mahmadi@chromium.org</owner>
+  <owner>chrome-desktop-search@google.com</owner>
+  <summary>
+    Records the time taken between AutocompleteController::Observer's
+    OnResultChanged is called and the time when we're ready to render the
+    results in the NTP realbox. If there are multiple calls to OnResultChanged
+    before the results are rendered, logs the time since the earliest. This is
+    comparable to Realbox.CharTypedToRepaintLatency.ToPaint but excludes the
+    time taken to send the input to the browser and process it. Similarly this
+    does not take into account the time the DOM change task spends in the
+    message loop nor its duration.
+  </summary>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 07e95f38..27004060 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -321,6 +321,33 @@
   </summary>
 </histogram>
 
+<histogram name="AccessCodeCast.Ui.TabSwitcherUsageType"
+    enum="AccessCodeCastUiTabSwitcherUsage" expires_after="2023-12-12">
+  <owner>ahmedmoussa@google.com</owner>
+  <owner>takumif@chromium.org</owner>
+  <owner>openscreen-eng@google.com</owner>
+  <summary>
+    This metric is recorded once per AccessCodeCast tab mirroring session. It
+    records the number of times the tab switcher UI was shown and not used,
+    compared to the number of times it was shown and actually used to switch
+    tabs. This would make it possible to compute the % of users who cast a tab
+    who then switch which tab is being cast. Recorded once at the end of a tab
+    mirroring casting session.
+  </summary>
+</histogram>
+
+<histogram name="AccessCodeCast.Ui.TabSwitchingCount" units="count"
+    expires_after="2023-12-12">
+  <owner>ahmedmoussa@google.com</owner>
+  <owner>takumif@chromium.org</owner>
+  <owner>openscreen-eng@google.com</owner>
+  <summary>
+    This metric is recorded once per AccessCodeCast tab mirroring session. It
+    records the number of times the tab switcher UI was used to switch the
+    source tab.
+  </summary>
+</histogram>
+
 <histogram name="ActivityTracker.Collect.AnalyzerCreationError"
     enum="ActivityTrackerAnalyzerCreationError" expires_after="M90">
   <owner>siggi@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/plugin_vm/histograms.xml b/tools/metrics/histograms/metadata/plugin_vm/histograms.xml
index f9942de..6d152b12 100644
--- a/tools/metrics/histograms/metadata/plugin_vm/histograms.xml
+++ b/tools/metrics/histograms/metadata/plugin_vm/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="PluginVm.DlcUseResult" enum="PluginVmDlcUseResult"
-    expires_after="2023-06-04">
+    expires_after="2024-01-14">
   <owner>kimjae@google.com</owner>
   <owner>timloh@google.com</owner>
   <summary>Recorded at each time PluginVM DLC is installed.</summary>
@@ -75,19 +75,8 @@
   </summary>
 </histogram>
 
-<histogram name="PluginVm.Image.DownloadedSize" units="MB"
-    expires_after="2023-01-14">
-  <owner>joelhockey@chromium.org</owner>
-  <owner>okalitova@chromium.org</owner>
-  <owner>timloh@chromium.org</owner>
-  <summary>
-    The size of the PluginVm image downloaded in MB. Recorded each time PluginVm
-    image is sucessfully downloaded.
-  </summary>
-</histogram>
-
 <histogram name="PluginVm.LaunchResult" enum="PluginVmLaunchResult"
-    expires_after="2023-01-14">
+    expires_after="2024-01-14">
   <owner>joelhockey@chromium.org</owner>
   <owner>okalitova@chromium.org</owner>
   <owner>timloh@chromium.org</owner>
@@ -98,14 +87,14 @@
 </histogram>
 
 <histogram name="PluginVm.SetupFailureReason" enum="PluginVmSetupFailureReason"
-    expires_after="2023-01-14">
+    expires_after="2024-01-14">
   <owner>timloh@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>Recorded when the Plugin VM installer fails.</summary>
 </histogram>
 
 <histogram name="PluginVm.SetupResult" enum="PluginVmSetupResult"
-    expires_after="2023-06-04">
+    expires_after="2024-01-14">
   <owner>joelhockey@chromium.org</owner>
   <owner>okalitova@chromium.org</owner>
   <owner>timloh@chromium.org</owner>
@@ -116,18 +105,6 @@
   </summary>
 </histogram>
 
-<histogram name="PluginVm.SetupTime" units="ms" expires_after="2023-01-14">
-  <owner>joelhockey@chromium.org</owner>
-  <owner>okalitova@chromium.org</owner>
-  <owner>timloh@chromium.org</owner>
-  <summary>
-    Recorded at each successful attempt to set up PluginVm, recording the time
-    that user spent waiting for setup to be finished. When error occurs during
-    setup and user clicks retry button - time between pressing retry button and
-    setup being finished is recorded.
-  </summary>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index b49eff4..a2a75af 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "a2d3aae552e26ab21d5f57d730586deb2d151ad2",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/404b02b04437917628244a583a5e2d3f56a3debb/trace_processor_shell.exe"
+            "hash": "f6875c40f8daf10ab6c14f7934a075c90a9bfff3",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/2648c757a2555e5e1c5946e46202fc385d68625b/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
diff --git a/tools/traffic_annotation/auditor/chromeos/safe_list.txt b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
index c6f67eb..96fc26073 100644
--- a/tools/traffic_annotation/auditor/chromeos/safe_list.txt
+++ b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
@@ -17,7 +17,6 @@
 all,chrome/browser/search/background/ntp_background_service.cc
 all,chrome/browser/apps/app_service/webapk/webapk_install_task.cc
 all,chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
-all,chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc
 all,chrome/browser/ash/app_list/search/arc/recommend_apps_fetcher_impl.cc
 all,chrome/browser/ui/ash/projector/screencast_manager.cc
 all,chrome/services/cups_proxy/socket_manager.cc
\ No newline at end of file
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 9795468..78a5397 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -364,6 +364,7 @@
  <item id="pending_beacon_api" added_in_milestone="105" content_hash_code="02288864" os_list="linux,windows,android,chromeos" file_path="content/browser/renderer_host/pending_beacon_service.cc" />
  <item id="printing_server_printers_query" added_in_milestone="106" content_hash_code="06f4759c" os_list="chromeos" file_path="chrome/browser/ash/printing/server_printers_fetcher.cc" />
  <item id="download_bubble_retry_download" added_in_milestone="105" content_hash_code="000b1439" os_list="linux,windows,chromeos" file_path="chrome/browser/download/bubble/download_bubble_controller.cc" />
+ <item id="wilco_dtc_supportd" added_in_milestone="108" content_hash_code="02299cc6" os_list="chromeos" file_path="chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc" />
  <item id="quick_answers_loader" added_in_milestone="105" content_hash_code="070b3239" os_list="chromeos" file_path="chromeos/components/quick_answers/result_loader.cc" />
  <item id="chrome_search_suggest_service" added_in_milestone="105" content_hash_code="04d973c6" os_list="linux,windows,android,chromeos" file_path="components/search/start_suggest_service.cc" />
  <item id="coupon_persisted_tab_data" added_in_milestone="105" content_hash_code="006dcd97" os_list="android" file_path="chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabData.java" />
@@ -402,4 +403,5 @@
  <item id="wallpaper_online_downloader" added_in_milestone="110" content_hash_code="060d5790" os_list="chromeos" file_path="ash/wallpaper/wallpaper_controller_impl.cc" />
  <item id="password_sync_token_fetcher" added_in_milestone="110" content_hash_code="0451c1ff" os_list="chromeos" file_path="chrome/browser/ash/login/saml/password_sync_token_fetcher.cc" />
  <item id="projector_xhr_loader" added_in_milestone="110" content_hash_code="071c4ac5" os_list="chromeos" file_path="ash/webui/projector_app/projector_xhr_sender.cc" />
+ <item id="bruschetta_installer_download" added_in_milestone="110" content_hash_code="01b953f4" os_list="chromeos" file_path="chrome/browser/ash/bruschetta/bruschetta_installer.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index a1adebb..0b17640c 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -92,6 +92,7 @@
       <annotation id="wallpaper_google_photos_albums"/>
       <annotation id="wallpaper_google_photos_enabled"/>
       <annotation id="wallpaper_google_photos_photos"/>
+      <annotation id="wilco_dtc_supportd"/>
       <annotation id="search_and_assistant_enabled_checker"/>
       <annotation id="wallpaper_online_downloader"/>
     </sender>
@@ -269,6 +270,7 @@
       <annotation id="password_sync_token_fetcher"/>
       <annotation id="nearby_webrtc_connection"/>
       <annotation id="projector_xhr_loader"/>
+      <annotation id="bruschetta_installer_download"/>
     </sender>
   </group>
   <group name="Admin Features" hidden="true">
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index 33b680f..8ffae455 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -3361,7 +3361,7 @@
   TestPositionType test_position = tree_position->AsTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(root_.id, test_position->anchor_id());
   EXPECT_EQ(1, test_position->child_index());
   EXPECT_EQ(AXNodePosition::INVALID_OFFSET, test_position->text_offset());
@@ -3376,7 +3376,7 @@
   TestPositionType test_position = text_position->AsTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(text_field_.id, test_position->anchor_id());
   // The created tree position should point to the second static text node
   // inside the text field.
@@ -3392,7 +3392,7 @@
   test_position = text_position->AsTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
   EXPECT_EQ(0, test_position->text_offset());
@@ -3405,7 +3405,7 @@
   test_position = text_position->AsTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
   EXPECT_EQ(6, test_position->text_offset());
@@ -3428,7 +3428,7 @@
   TestPositionType test_position = tree_position->AsTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(text_field_.id, test_position->anchor_id());
   // The created text position should point to the 6th character inside the text
   // field, i.e. the line break.
@@ -3446,7 +3446,7 @@
   test_position = tree_position->AsTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
@@ -3458,7 +3458,7 @@
   test_position = tree_position->AsTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(6, test_position->text_offset());
   EXPECT_EQ(0, test_position->child_index());
@@ -3473,7 +3473,7 @@
   TestPositionType test_position = text_position->AsTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(text_field_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3497,7 +3497,7 @@
   TestPositionType test_position = tree_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3508,7 +3508,7 @@
   test_position = tree_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3519,7 +3519,7 @@
   test_position = tree_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 }
@@ -3534,7 +3534,7 @@
   TestPositionType test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -3555,7 +3555,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3566,7 +3566,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3577,7 +3577,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3592,7 +3592,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -3605,7 +3605,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -3617,7 +3617,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3629,7 +3629,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -3641,7 +3641,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -3653,7 +3653,7 @@
   test_position = text_position->AsLeafTreePosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTreePosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 }
@@ -3675,7 +3675,7 @@
   TestPositionType test_position = tree_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3687,7 +3687,7 @@
   test_position = tree_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3699,7 +3699,7 @@
   test_position = tree_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3734,7 +3734,7 @@
   TestPositionType test_position = tree_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(empty_div_data2.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3745,7 +3745,7 @@
   test_position = tree_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(empty_div_data2.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3762,7 +3762,7 @@
   TestPositionType test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(6, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3773,7 +3773,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3784,7 +3784,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3795,7 +3795,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3810,7 +3810,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(6, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3824,7 +3824,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(6, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3837,7 +3837,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3850,7 +3850,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3863,7 +3863,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(6, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3876,7 +3876,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(3, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3889,7 +3889,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(3, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3937,7 +3937,7 @@
   TestPositionType test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_data.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -3948,7 +3948,7 @@
   test_position = text_position->AsLeafTextPosition();
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsLeafTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(text_data.id, test_position->anchor_id());
   EXPECT_EQ(9, test_position->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
@@ -5996,7 +5996,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6006,7 +6006,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6017,7 +6017,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6028,7 +6028,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6038,7 +6038,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6048,7 +6048,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6059,7 +6059,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6070,7 +6070,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6081,7 +6081,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6092,7 +6092,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6103,7 +6103,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 
@@ -6114,7 +6114,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
 }
@@ -6157,7 +6157,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6168,7 +6168,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6180,7 +6180,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6192,7 +6192,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6203,7 +6203,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6214,7 +6214,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6226,7 +6226,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6238,7 +6238,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6250,7 +6250,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6262,7 +6262,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6274,7 +6274,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6286,7 +6286,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 }
@@ -6356,7 +6356,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6366,7 +6366,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6377,7 +6377,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6388,7 +6388,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6398,7 +6398,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6408,7 +6408,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6419,7 +6419,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6430,7 +6430,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6441,7 +6441,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6452,7 +6452,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6463,7 +6463,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6474,7 +6474,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 }
@@ -6515,7 +6515,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6526,7 +6526,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6538,7 +6538,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6550,7 +6550,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6561,7 +6561,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6572,7 +6572,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -6584,7 +6584,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -6596,7 +6596,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -6608,7 +6608,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -6620,7 +6620,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -6632,7 +6632,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(13, test_position->text_offset());
 
@@ -6644,7 +6644,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfAXTree());
-  EXPECT_EQ(iframe_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(iframe_tree_id, test_position->tree_id());
   EXPECT_EQ(iframe_root.id, test_position->anchor_id());
   EXPECT_EQ(13, test_position->text_offset());
 }
@@ -6714,7 +6714,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6724,7 +6724,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6735,7 +6735,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6746,7 +6746,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6756,7 +6756,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6766,7 +6766,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6777,7 +6777,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6788,7 +6788,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6799,7 +6799,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6810,7 +6810,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6821,7 +6821,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -6832,7 +6832,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 }
@@ -6874,7 +6874,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6885,7 +6885,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6897,7 +6897,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6909,7 +6909,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6920,7 +6920,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6931,7 +6931,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(window.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6943,7 +6943,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6955,7 +6955,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6967,7 +6967,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6979,7 +6979,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -6991,7 +6991,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7003,7 +7003,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtStartOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(root_web_area.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 }
@@ -7073,7 +7073,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7083,7 +7083,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7094,7 +7094,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7105,7 +7105,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7115,7 +7115,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7125,7 +7125,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7136,7 +7136,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7147,7 +7147,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7158,7 +7158,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7169,7 +7169,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7180,7 +7180,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 
@@ -7191,7 +7191,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTreePosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->child_index());
 }
@@ -7233,7 +7233,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7244,7 +7244,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7256,7 +7256,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7268,7 +7268,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7279,7 +7279,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7290,7 +7290,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(views_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(views_tree_id, test_position->tree_id());
   EXPECT_EQ(address_bar.id, test_position->anchor_id());
   EXPECT_EQ(8, test_position->text_offset());
 
@@ -7302,7 +7302,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -7314,7 +7314,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -7326,7 +7326,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -7338,7 +7338,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -7350,7 +7350,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 
@@ -7362,7 +7362,7 @@
   ASSERT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
   EXPECT_TRUE(test_position->AtEndOfContent());
-  EXPECT_EQ(webpage_tree_id, test_position->GetTreeID());
+  EXPECT_EQ(webpage_tree_id, test_position->tree_id());
   EXPECT_EQ(paragraph.id, test_position->anchor_id());
   EXPECT_EQ(12, test_position->text_offset());
 }
@@ -8614,7 +8614,7 @@
       check_box_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(check_box_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8627,7 +8627,7 @@
   test_position = root_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8638,28 +8638,28 @@
   test_position = button_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(check_box_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8673,7 +8673,7 @@
   test_position = text_field_position->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8686,7 +8686,7 @@
   test_position = root_position2->CreateNextLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(3, test_position->text_offset());
 }
@@ -8700,7 +8700,7 @@
       text_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8711,28 +8711,28 @@
   test_position = before_text_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(line_break_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(check_box_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
   test_position = test_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8746,7 +8746,7 @@
   test_position = text_field_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(check_box_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8759,7 +8759,7 @@
   test_position = check_box_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(button_.id, test_position->anchor_id());
   EXPECT_EQ(0, test_position->text_offset());
 
@@ -8772,7 +8772,7 @@
   test_position = root_position2->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, test_position);
   EXPECT_TRUE(test_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), test_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), test_position->tree_id());
   EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
   EXPECT_EQ(3, test_position->text_offset());
 }
@@ -11260,18 +11260,18 @@
 
   swap(*tree_position1, *tree_position2);
   EXPECT_TRUE(tree_position1->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), tree_position1->GetTreeID());
+  EXPECT_EQ(GetTreeID(), tree_position1->tree_id());
   EXPECT_EQ(text_field_.id, tree_position1->anchor_id());
   EXPECT_EQ(3, tree_position1->child_index());
   EXPECT_TRUE(tree_position1->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), tree_position2->GetTreeID());
+  EXPECT_EQ(GetTreeID(), tree_position2->tree_id());
   EXPECT_EQ(root_.id, tree_position2->anchor_id());
   EXPECT_EQ(2, tree_position2->child_index());
 
   swap(*tree_position1, *null_position1);
   EXPECT_TRUE(tree_position1->IsNullPosition());
   EXPECT_TRUE(null_position1->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), null_position1->GetTreeID());
+  EXPECT_EQ(GetTreeID(), null_position1->tree_id());
   EXPECT_EQ(text_field_.id, null_position1->anchor_id());
   EXPECT_EQ(3, null_position1->child_index());
 
@@ -11281,12 +11281,12 @@
 
   swap(*text_position, *null_position1);
   EXPECT_TRUE(null_position1->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), text_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), text_position->tree_id());
   EXPECT_EQ(line_break_.id, null_position1->anchor_id());
   EXPECT_EQ(1, null_position1->text_offset());
   EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, null_position1->affinity());
   EXPECT_TRUE(text_position->IsTreePosition());
-  EXPECT_EQ(GetTreeID(), text_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), text_position->tree_id());
   EXPECT_EQ(text_field_.id, text_position->anchor_id());
   EXPECT_EQ(3, text_position->child_index());
 }
@@ -12123,7 +12123,7 @@
   text_position = generic_container_position->CreatePreviousLeafTextPosition();
   EXPECT_NE(nullptr, text_position);
   EXPECT_TRUE(text_position->IsTextPosition());
-  EXPECT_EQ(GetTreeID(), text_position->GetTreeID());
+  EXPECT_EQ(GetTreeID(), text_position->tree_id());
   EXPECT_EQ(button_14.id, text_position->anchor_id());
 }
 
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index b6439e7..4693ef1 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -447,7 +447,8 @@
   AXPositionKind kind() const { return kind_; }
 
   // Deprecated.
-  // TODO(crbug.com/1362839): replace on GetAnchorID().
+  // TODO(crbug.com/1362839): replace on GetTreeID()/GetAnchorID().
+  AXTreeID tree_id() const { return GetTreeID(); }
   AXNodeID anchor_id() const { return GetAnchorID(); }
 
   // Returns true if this position is within an "empty object", i.e. within a
@@ -1284,7 +1285,7 @@
     if (previous_anchor->IsNullPosition())
       return true;
 
-    return previous_anchor->GetTree() != GetTree();
+    return previous_anchor->tree_id() != tree_id();
   }
 
   // Returns true if this position is at the end of the current accessibility
diff --git a/ui/accessibility/ax_range.h b/ui/accessibility/ax_range.h
index fd626e7..0562fe0 100644
--- a/ui/accessibility/ax_range.h
+++ b/ui/accessibility/ax_range.h
@@ -435,7 +435,7 @@
     if (IsCollapsed() && range_start->IsInTextObject()) {
       AXOffscreenResult offscreen_result;
       gfx::Rect degenerate_range_rect = delegate->GetInnerTextRangeBoundsRect(
-          range_start->GetTreeID(), range_start->anchor_id(),
+          range_start->tree_id(), range_start->anchor_id(),
           range_start->text_offset(), range_end->text_offset(),
           ui::AXClippingBehavior::kUnclipped, &offscreen_result);
       if (offscreen_result == AXOffscreenResult::kOnscreen) {
@@ -464,12 +464,12 @@
           (current_line_start->GetAnchor()->IsLineBreak() ||
            current_line_start->IsInTextObject())
               ? delegate->GetInnerTextRangeBoundsRect(
-                    current_line_start->GetTreeID(),
+                    current_line_start->tree_id(),
                     current_line_start->anchor_id(),
                     current_line_start->text_offset(),
                     current_line_end->text_offset(),
                     ui::AXClippingBehavior::kClipped, &offscreen_result)
-              : delegate->GetBoundsRect(current_line_start->GetTreeID(),
+              : delegate->GetBoundsRect(current_line_start->tree_id(),
                                         current_line_start->anchor_id(),
                                         &offscreen_result);
 
diff --git a/ui/accessibility/ax_tree_manager.cc b/ui/accessibility/ax_tree_manager.cc
index a8cc65ad..a5856290 100644
--- a/ui/accessibility/ax_tree_manager.cc
+++ b/ui/accessibility/ax_tree_manager.cc
@@ -71,9 +71,9 @@
       ax_tree_id_(tree ? tree->data().tree_id : AXTreeIDUnknown()),
       ax_tree_(std::move(tree)),
       event_generator_(ax_tree()) {
+  DCHECK(ax_tree_);
   GetMap().AddTreeManager(ax_tree_id_, this);
-  if (ax_tree())
-    tree_observation_.Observe(ax_tree());
+  tree_observation_.Observe(ax_tree());
 }
 
 AXTreeManager::AXTreeManager(const AXTreeID& tree_id,
@@ -82,9 +82,9 @@
       ax_tree_id_(tree_id),
       ax_tree_(std::move(tree)),
       event_generator_(ax_tree()) {
+  DCHECK(ax_tree_);
   GetMap().AddTreeManager(ax_tree_id_, this);
-  if (ax_tree())
-    tree_observation_.Observe(ax_tree());
+  tree_observation_.Observe(ax_tree());
 }
 
 void AXTreeManager::FireFocusEvent(AXNode* node) {
diff --git a/ui/accessibility/platform/automation/automation_position.cc b/ui/accessibility/platform/automation/automation_position.cc
index 6d8010f0..6bc2353 100644
--- a/ui/accessibility/platform/automation/automation_position.cc
+++ b/ui/accessibility/platform/automation/automation_position.cc
@@ -125,7 +125,7 @@
 }
 
 std::string AutomationPosition::GetTreeID(gin::Arguments* arguments) {
-  return position_->GetTreeID().ToString();
+  return position_->tree_id().ToString();
 }
 
 int AutomationPosition::GetAnchorID(gin::Arguments* arguments) {
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index 98f3d71..1e87e1c 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -595,7 +595,7 @@
 
   // Iterate over anchor positions
   for (auto it = normalized_start->AsLeafTextPosition();
-       it->anchor_id() != end->anchor_id() || it->GetTree() != end->GetTree();
+       it->anchor_id() != end->anchor_id() || it->tree_id() != end->tree_id();
        it = it->CreateNextAnchorPosition()) {
     // If the iterator creates a null position, then it has likely overrun the
     // range, return failure. This is unexpected but may happen if the range
@@ -621,7 +621,7 @@
     base::win::VariantVector current_value;
     const bool at_end_leaf_text_anchor =
         it->anchor_id() == end_leaf_text_position->anchor_id() &&
-        it->GetTree() == end_leaf_text_position->GetTree();
+        it->tree_id() == end_leaf_text_position->tree_id();
     const absl::optional<int> start_offset =
         it->IsTextPosition() ? absl::make_optional(it->text_offset())
                              : absl::nullopt;
@@ -923,7 +923,7 @@
   // Blink only supports selections within a single tree. So if start_ and  end_
   // are in different trees, we can't directly pass them to the render process
   // for selection.
-  if (selection_start->GetTree() != selection_end->GetTree()) {
+  if (selection_start->tree_id() != selection_end->tree_id()) {
     // Prioritize the end position's tree, as a selection's focus object is the
     // end of a selection.
     selection_start = selection_end->CreatePositionAtStartOfAXTree();
@@ -931,7 +931,7 @@
 
   DCHECK(!selection_start->IsNullPosition());
   DCHECK(!selection_end->IsNullPosition());
-  DCHECK_EQ(selection_start->GetTree(), selection_end->GetTree());
+  DCHECK_EQ(selection_start->tree_id(), selection_end->tree_id());
 
   // TODO(crbug.com/1124051): Blink does not support selection on the list
   // markers. So if |selection_start| or |selection_end| are in list markers, we
@@ -943,7 +943,7 @@
   }
 
   AXPlatformNodeDelegate* delegate =
-      GetDelegate(selection_start->GetTreeID(), selection_start->anchor_id());
+      GetDelegate(selection_start->tree_id(), selection_start->anchor_id());
   DCHECK(delegate);
 
   AXNodeRange new_selection_range(std::move(selection_start),
@@ -992,7 +992,7 @@
   const AXNode* common_ancestor_anchor = start_common_ancestor->GetAnchor();
   DCHECK(common_ancestor_anchor == end_common_ancestor->GetAnchor());
 
-  const AXTreeID common_ancestor_tree_id = start_common_ancestor->GetTreeID();
+  const AXTreeID common_ancestor_tree_id = start_common_ancestor->tree_id();
   const AXPlatformNodeDelegate* root_delegate =
       GetRootDelegate(common_ancestor_tree_id);
   DCHECK(root_delegate);
@@ -1178,7 +1178,7 @@
 
 AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
     const AXPositionInstanceType* position) const {
-  return GetDelegate(position->GetTreeID(), position->anchor_id());
+  return GetDelegate(position->tree_id(), position->anchor_id());
 }
 
 AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
@@ -1631,52 +1631,54 @@
 
 void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::SetStart(
     AXPositionInstance new_start) {
-  bool did_tree_change = start_->GetTree() != new_start->GetTree();
+  bool did_tree_change = start_->tree_id() != new_start->tree_id();
   // TODO(bebeaudr): We can't use IsNullPosition() here because of
   // https://crbug.com/1152939. Once this is fixed, we can go back to
   // IsNullPosition().
   if (did_tree_change && start_->kind() != AXPositionKind::NULL_POSITION &&
-      start_->GetTree() != end_->GetTree()) {
-    RemoveObserver(start_);
+      start_->tree_id() != end_->tree_id()) {
+    RemoveObserver(start_->tree_id());
   }
 
   start_ = std::move(new_start);
 
   if (did_tree_change && !start_->IsNullPosition() &&
-      start_->GetTree() != end_->GetTree()) {
-    AddObserver(start_);
+      start_->tree_id() != end_->tree_id()) {
+    AddObserver(start_->tree_id());
   }
 }
 
 void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::SetEnd(
     AXPositionInstance new_end) {
-  bool did_tree_change = end_->GetTree() != new_end->GetTree();
+  bool did_tree_change = end_->tree_id() != new_end->tree_id();
   // TODO(bebeaudr): We can't use IsNullPosition() here because of
   // https://crbug.com/1152939. Once this is fixed, we can go back to
   // IsNullPosition().
   if (did_tree_change && end_->kind() != AXPositionKind::NULL_POSITION &&
-      end_->GetTree() != start_->GetTree()) {
-    RemoveObserver(end_);
+      end_->tree_id() != start_->tree_id()) {
+    RemoveObserver(end_->tree_id());
   }
 
   end_ = std::move(new_end);
 
   if (did_tree_change && !end_->IsNullPosition() &&
-      start_->GetTree() != end_->GetTree()) {
-    AddObserver(end_);
+      start_->tree_id() != end_->tree_id()) {
+    AddObserver(end_->tree_id());
   }
 }
 
 void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::AddObserver(
-    const AXPositionInstance& position) {
-  if (position->GetTree())
-    position->GetTree()->AddObserver(this);
+    const AXTreeID tree_id) {
+  AXTreeManager* ax_tree_manager = AXTreeManager::FromID(tree_id);
+  DCHECK(ax_tree_manager);
+  ax_tree_manager->ax_tree()->AddObserver(this);
 }
 
 void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::RemoveObserver(
-    const AXPositionInstance& position) {
-  if (position->GetTree())
-    position->GetTree()->RemoveObserver(this);
+    const AXTreeID tree_id) {
+  AXTreeManager* ax_tree_manager = AXTreeManager::FromID(tree_id);
+  if (ax_tree_manager)
+    ax_tree_manager->ax_tree()->RemoveObserver(this);
 }
 
 // Ensures that our endpoints are located on non-deleted nodes (step 1, case A
@@ -1702,7 +1704,7 @@
                                      bool is_start_endpoint) {
   AXPositionInstance endpoint =
       is_start_endpoint ? start_->Clone() : end_->Clone();
-  if (tree != endpoint->GetTree())
+  if (tree->GetAXTreeID() != endpoint->tree_id())
     return;
 
   // When the subtree of the root node will be deleted, we can be certain that
@@ -1814,11 +1816,9 @@
 
 void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::
     OnTreeManagerWillBeRemoved(AXTreeID previous_tree_id) {
-  if (start_->GetTreeID() == previous_tree_id ||
-      end_->GetTreeID() == previous_tree_id) {
-    AXTreeManager* ax_tree_manager = AXTreeManager::FromID(previous_tree_id);
-    if (ax_tree_manager)
-      ax_tree_manager->ax_tree()->RemoveObserver(this);
+  if (start_->tree_id() == previous_tree_id ||
+      end_->tree_id() == previous_tree_id) {
+    RemoveObserver(previous_tree_id);
   }
 }
 
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
index b160cf4..2b39df7 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -262,8 +262,8 @@
     void SetStart(AXPositionInstance new_start);
     void SetEnd(AXPositionInstance new_end);
 
-    void AddObserver(const AXPositionInstance& position);
-    void RemoveObserver(const AXPositionInstance& position);
+    void AddObserver(const AXTreeID tree_id);
+    void RemoveObserver(const AXTreeID tree_id);
     void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override;
     void OnNodeDeleted(AXTree* tree, AXNodeID node_id) override;
     void OnTreeManagerWillBeRemoved(AXTreeID previous_tree_id) override;
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 0d53d84..75e727d 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -4375,7 +4375,8 @@
     // We don't support when the IA2 coordinate type is parent relative, but
     // we have to return something rather than E_NOTIMPL or screen readers
     // will complain.
-    NOTIMPLEMENTED_LOG_ONCE() << "See http://crbug.com/1010726";
+    // See http://crbug.com/1010726
+    NOTIMPLEMENTED_LOG_ONCE();
     return S_FALSE;
   }
 
@@ -4582,7 +4583,7 @@
 
   AXActionData action_data;
   action_data.action = ax::mojom::Action::kSetSelection;
-  action_data.target_tree_id = start_position->GetTreeID();
+  action_data.target_tree_id = start_position->tree_id();
   int start_offset = start_position->IsTextPosition()
                          ? start_position->text_offset()
                          : start_position->child_index();
diff --git a/ui/file_manager/file_manager/foreground/js/ui/context_menu_handler.js b/ui/file_manager/file_manager/foreground/js/ui/context_menu_handler.js
index c1bd5234..58104f5 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/context_menu_handler.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/context_menu_handler.js
@@ -189,8 +189,9 @@
             e.stopPropagation();
             e.preventDefault();
 
-            // If the menu is visible we let it handle all the keyboard events.
-          } else if (this.menu) {
+            // If the menu is visible we let it handle all the keyboard events
+            // unless Ctrl is held down.
+          } else if (this.menu && !e.ctrlKey) {
             this.menu.handleKeyDown(e);
             e.preventDefault();
             e.stopPropagation();
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index f1749aa..ccf1ba8 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -530,12 +530,12 @@
     if (connection->wayland_output_manager_)
       connection->wayland_output_manager_->InitializeAllXdgOutputs();
   } else if (strcmp(interface, "org_kde_plasma_shell") == 0) {
-    NOTIMPLEMENTED_LOG_ONCE()
-        << interface << " is recognized but not yet supported";
+    // Recognized but not yet supported.
+    NOTIMPLEMENTED_LOG_ONCE();
     ReportShellUMA(UMALinuxWaylandShell::kOrgKdePlasmaShell);
   } else if (strcmp(interface, "zwlr_layer_shell_v1") == 0) {
-    NOTIMPLEMENTED_LOG_ONCE()
-        << interface << " is recognized but not yet supported";
+    // Recognized but not yet supported.
+    NOTIMPLEMENTED_LOG_ONCE();
     ReportShellUMA(UMALinuxWaylandShell::kZwlrLayerShellV1);
   } else if (!connection->zcr_stylus_v2_ &&
              strcmp(interface, "zcr_stylus_v2") == 0) {
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index c104525..de9fd1e 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -729,8 +729,7 @@
   } else {
     // TODO(https://crbug.com/1113900): Implement AuraShell support for
     // non-browser windows and replace this if-else clause by a DCHECK.
-    NOTIMPLEMENTED_LOG_ONCE()
-        << "Implement AuraShell support for non-browser windows.";
+    NOTIMPLEMENTED_LOG_ONCE();
   }
 }
 #endif
@@ -754,8 +753,8 @@
     return;
   }
 
-  NOTIMPLEMENTED_LOG_ONCE()
-      << "Window snapping isn't available for non-lacros builds.";
+  // Window snapping isn't available for non-lacros builds.
+  NOTIMPLEMENTED_LOG_ONCE();
 }
 
 void WaylandToplevelWindow::CommitSnap(
@@ -773,9 +772,8 @@
         return;
     }
   }
-
-  NOTIMPLEMENTED_LOG_ONCE()
-      << "Window snapping isn't available for non-lacros builds.";
+  // Window snapping isn't available for non-lacros builds.
+  NOTIMPLEMENTED_LOG_ONCE();
 }
 
 void WaylandToplevelWindow::SetCanGoBack(bool value) {
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index e43ffbe..b445ab11 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -16,6 +16,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/theme_provider.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
 #include "ui/compositor/layer.h"
@@ -58,7 +59,10 @@
 
 double GetCornerRadius(float halo_thickness) {
   const double thickness = halo_thickness / 2.f;
-  return FocusableBorder::kCornerRadiusDp + thickness;
+  return (features::IsChromeRefresh2023()
+              ? FocusableBorder::kChromeRefresh2023CornerRadiusDp
+              : FocusableBorder::kCornerRadiusDp) +
+         thickness;
 }
 
 SkPath GetHighlightPathInternal(const View* view, float halo_thickness) {
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index ce0347a0..0f6358f 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -129,10 +129,10 @@
 }
 
 group("closure_compile") {
-  deps = [
-    "js:closure_compile",
-    "js:closure_compile_modules",
-  ]
+  deps = [ "js:closure_compile" ]
+  if (is_chromeos_ash) {
+    deps += [ "js:closure_compile_modules" ]
+  }
 }
 
 # TypeScript targets
@@ -154,10 +154,14 @@
 }
 
 # Files for which .d.ts files will be auto-generated with ts_definitions().
-generate_definitions_js_files = [ "js/assert.js" ]
-
-if (is_ios) {
-  generate_definitions_js_files += [ "js/ios/web_ui.js" ]
+if (is_chromeos_ash || is_ios) {
+  generate_definitions_js_files = []
+  if (is_chromeos_ash) {
+    generate_definitions_js_files += [ "js/assert.js" ]
+  }
+  if (is_ios) {
+    generate_definitions_js_files += [ "js/ios/web_ui.js" ]
+  }
 }
 
 ts_library("library") {
@@ -224,16 +228,21 @@
   }
 
   # Auto-generated .d.ts files.
-  foreach(_file, generate_definitions_js_files) {
-    definitions += [ "$root_dir/" + string_replace(_file, ".js", ".d.ts") ]
+  if (is_chromeos_ash || is_ios) {
+    foreach(_file, generate_definitions_js_files) {
+      definitions += [ "$root_dir/" + string_replace(_file, ".js", ".d.ts") ]
+    }
   }
 
   deps = [ "//third_party/polymer/v3_0:library" ]
   extra_deps += [
-    ":generate_definitions",
     ":preprocess",
+    "mojo:library",
   ]
 
+  if (is_chromeos_ash || is_ios) {
+    extra_deps += [ ":generate_definitions" ]
+  }
   if (is_chromeos_ash) {
     extra_deps += [ ":copy_checked_in_dts_files" ]
   }
@@ -247,17 +256,19 @@
       filter_include(get_target_outputs(":library"), [ "*.manifest" ])
 }
 
-ts_definitions("generate_definitions") {
-  root_dir = preprocessed_folder
-  out_dir = preprocessed_folder
-  js_files = generate_definitions_js_files
-  extra_deps = [
-    ":preprocess",
-    "mojo:library",
-  ]
-  if (is_chromeos_ash) {
-    # Copy checked-in d.ts files first, so that |generate_definitions| leverages
-    # these files, instead of accidentally auto-generating them.
-    extra_deps += [ ":copy_checked_in_dts_files" ]
+if (is_chromeos_ash || is_ios) {
+  ts_definitions("generate_definitions") {
+    root_dir = preprocessed_folder
+    out_dir = preprocessed_folder
+    js_files = generate_definitions_js_files
+    extra_deps = [
+      ":preprocess",
+      "mojo:library",
+    ]
+    if (is_chromeos_ash) {
+      # Copy checked-in d.ts files first, so that |generate_definitions|
+      # leverages these files, instead of accidentally auto-generating them.
+      extra_deps += [ ":copy_checked_in_dts_files" ]
+    }
   }
 }
diff --git a/ui/webui/resources/cr_elements/cr_menu_selector/cr_menu_selector.ts b/ui/webui/resources/cr_elements/cr_menu_selector/cr_menu_selector.ts
index 98a7638..a695daa 100644
--- a/ui/webui/resources/cr_elements/cr_menu_selector/cr_menu_selector.ts
+++ b/ui/webui/resources/cr_elements/cr_menu_selector/cr_menu_selector.ts
@@ -29,6 +29,12 @@
     this.setAttribute('role', 'menu');
     this.addEventListener('focusin', this.onFocusin_.bind(this));
     this.addEventListener('keydown', this.onKeydown_.bind(this));
+    this.addEventListener(
+        'iron-deselect',
+        e => this.onIronDeselected_(e as CustomEvent<{item: HTMLElement}>));
+    this.addEventListener(
+        'iron-select',
+        e => this.onIronSelected_(e as CustomEvent<{item: HTMLElement}>));
   }
 
   private getAllFocusableItems_(): HTMLElement[] {
@@ -53,6 +59,14 @@
     }
   }
 
+  private onIronDeselected_(e: CustomEvent<{item: HTMLElement}>) {
+    e.detail.item.removeAttribute('aria-current');
+  }
+
+  private onIronSelected_(e: CustomEvent<{item: HTMLElement}>) {
+    e.detail.item.setAttribute('aria-current', 'page');
+  }
+
   private onKeydown_(event: KeyboardEvent) {
     const items = this.getAllFocusableItems_();
     assert(items.length >= 1);
diff --git a/ui/webui/resources/cr_elements/find_shortcut_mixin.ts b/ui/webui/resources/cr_elements/find_shortcut_mixin.ts
index e01bd4c..7b407d43 100644
--- a/ui/webui/resources/cr_elements/find_shortcut_mixin.ts
+++ b/ui/webui/resources/cr_elements/find_shortcut_mixin.ts
@@ -4,7 +4,7 @@
 
 import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {assert, assertNotReached} from '../js/assert.js';
+import {assert, assertNotReached} from '../js/assert_ts.js';
 import {KeyboardShortcutList} from '../js/keyboard_shortcut_list.js';
 import {isMac} from '../js/platform.js';
 
@@ -108,8 +108,12 @@
           listeners.push(this);
         }
 
+        private handleFindShortcutInternal_() {
+          assertNotReached('Must override handleFindShortcut()');
+        }
+
         handleFindShortcut(_modalContextOpen: boolean) {
-          assertNotReached();
+          this.handleFindShortcutInternal_();
           return false;
         }
 
@@ -120,8 +124,12 @@
           listeners.splice(index, 1);
         }
 
+        private searchInputHasFocusInternal_() {
+          assertNotReached('Must override searchInputHasFocus()');
+        }
+
         searchInputHasFocus() {
-          assertNotReached();
+          this.searchInputHasFocusInternal_();
           return false;
         }
       }
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index 097fce6..6cdcb7f 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -80,14 +80,12 @@
   in_folder = "./"
   out_folder = "$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_src_manifest"
-  in_files = [
-    "assert.js",
-    "load_time_data_deprecated.js",
-  ]
+  in_files = [ "load_time_data_deprecated.js" ]
 
   if (is_chromeos_ash) {
     # Used by ChromeOS UIs.
     in_files += [
+      "assert.js",
       "cr.m.js",
       "load_time_data.m.js",
     ]
@@ -130,24 +128,20 @@
   }
 }
 
-# Targets for type-checking JS Modules
-
-js_type_check("closure_compile_modules") {
-  is_polymer3 = true
-  deps = [ ":assert" ]
-
-  if (is_chromeos_ash) {
-    deps += [
+# Targets for type-checking JS Modules on ChromeOS Ash only.
+if (is_chromeos_ash) {
+  js_type_check("closure_compile_modules") {
+    is_polymer3 = true
+    deps = [
+      ":assert",
       ":cr.m",
       ":load_time_data.m",
     ]
   }
-}
 
-js_library("assert") {
-}
+  js_library("assert") {
+  }
 
-if (is_chromeos_ash) {
   js_library("cr.m") {
   }
 
diff --git a/ui/webui/resources/js/focus_grid.ts b/ui/webui/resources/js/focus_grid.ts
index 062e7885..0cccd16 100644
--- a/ui/webui/resources/js/focus_grid.ts
+++ b/ui/webui/resources/js/focus_grid.ts
@@ -3,8 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {assert} from './assert.js';
-
+import {assert} from './assert_ts.js';
 import {FocusRow, FocusRowDelegate} from './focus_row.js';
 // clang-format on
 
diff --git a/ui/webui/resources/js/metrics_reporter/BUILD.gn b/ui/webui/resources/js/metrics_reporter/BUILD.gn
index 6fcb00b..487a50b 100644
--- a/ui/webui/resources/js/metrics_reporter/BUILD.gn
+++ b/ui/webui/resources/js/metrics_reporter/BUILD.gn
@@ -10,6 +10,7 @@
   sources = [ "metrics_reporter.mojom" ]
   webui_module_path = "chrome://resources/js/metrics_reporter"
   public_deps = [ "//mojo/public/mojom/base" ]
+  use_typescript_sources = true
 }
 
 # Output folder used to hold ts_library() output.
@@ -20,24 +21,24 @@
   root_dir = target_gen_dir
   out_dir = preprocess_folder
   composite = true
-  tsconfig_base = "tsconfig_base.json"
   in_files = [
     "metrics_reporter.ts",
     "browser_proxy.ts",
-    "metrics_reporter.mojom-webui.js",
+    "metrics_reporter.mojom-webui.ts",
   ]
   definitions = [ "//tools/typescript/definitions/chrome_timeticks.d.ts" ]
   deps = [
     "//ui/webui/resources:library",
     "//ui/webui/resources/mojo:library",
   ]
-  extra_deps = [ ":copy_src_and_mojom" ]
+  extra_deps = [
+    ":copy_src",
+    ":mojo_bindings_ts__generator",
+  ]
 }
 
-copy("copy_src_and_mojom") {
-  deps = [ ":mojo_bindings_js__generator" ]
+copy("copy_src") {
   sources = [
-    "$root_gen_dir/mojom-webui/ui/webui/resources/js/metrics_reporter/metrics_reporter.mojom-webui.js",
     "browser_proxy.ts",
     "metrics_reporter.ts",
   ]
diff --git a/ui/webui/resources/js/metrics_reporter/tsconfig_base.json b/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
deleted file mode 100644
index 5502828..0000000
--- a/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "extends": "../../../../../tools/typescript/tsconfig_base.json",
-  "compilerOptions": {
-    "allowJs": true
-  }
-}
diff --git a/ui/webui/resources/js/util_deprecated.js b/ui/webui/resources/js/util_deprecated.js
index 9cd900c9..0faf976 100644
--- a/ui/webui/resources/js/util_deprecated.js
+++ b/ui/webui/resources/js/util_deprecated.js
@@ -3,9 +3,8 @@
 // found in the LICENSE file.
 
 /* @filedescription Minimal utils and assertion support for places in the code
- * that are still not updated to JS modules. Do not use in new code; use the JS
- * modules (and more extensive) util.js, assert.js (for JS code), and
- * assert_ts.ts (for TS code) instead. */
+ * that are still not updated to JS modules. Do not use in new code. Use
+ * assert_ts and util_ts instead. */
 
 /**
  * Note: This method is deprecated. Use the equivalent method in assert_ts.ts