diff --git a/AUTHORS b/AUTHORS
index 232cf01..0e64013 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -174,6 +174,7 @@
 Bhanukrushana Rout <b.rout@samsung.com>
 Biljith Jayan <billy.jayan@samsung.com>
 Bin Liao <bin.liao@intel.com>
+Bin Miao <bin.miao@intel.com>
 Boaz Sender <boaz@bocoup.com>
 Bobby Powers <bobbypowers@gmail.com>
 Branden Archer <bma4@zips.uakron.edu>
diff --git a/DEPS b/DEPS
index c8542be8..c41b436 100644
--- a/DEPS
+++ b/DEPS
@@ -303,15 +303,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '702018d267c0559a9c2be6854dd64a74ad44c79a',
+  'angle_revision': '684ff60bd7c482b8b64eb254aa5877e7b873f5ca',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '36e043fa6d468fff9865a00200c0face80dd7727',
+  'swiftshader_revision': '570d5b7d832752b54930182f22068bcc0bbc6ee9',
   # 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': '9a02c9040e16d95c44e7dfec10d55c3ad0b39380',
+  'pdfium_revision': 'd0f42d7bfb7dc2bd44b5a988647a5fa056b5376d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -414,7 +414,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '99ce762dba455520c792f0deb3b42c395da95daf',
+  'quiche_revision': 'bb8ac619a1e6563a94cf2abd0e59df9d795fa8a3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # 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': 'ba1578777ddaaf8bcc93f19500a23dcdd6bbbcbe',
+  'nearby_revision': 'c50cdc5671f17a2180c39d30414d3a1397592568',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -573,7 +573,7 @@
     'packages': [
       {
         'package': 'chromium/third_party/rust_src',
-        'version': 'version:2@2022-12-07',
+        'version': 'version:2@2022-12-09',
       },
     ],
     'dep_type': 'cipd',
@@ -764,7 +764,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '989cedae35874062996390368caf6e6f68eb5e16',
+    'c431c03effdf8eb3722766b58ef60ef31f42568e',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -863,7 +863,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'Qt-Sy9T_6Ap6__B41BbhAlsttzZHh093ANhugOq8C7EC',
+          'version': '3Dr0CT16nfqgz5Zw_lLuQLTBd-L4xDSFe89ZUT4fpOsC',
         },
       ],
       'dep_type': 'cipd',
@@ -1188,7 +1188,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '3844a96e1a51d094df0a66b515c4c0f12018b52f',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'fc4f25a24905b745450e97f10c061b9e6eb9d1a5',
       'condition': 'checkout_linux',
   },
 
@@ -1198,7 +1198,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'a964ca1296b9238d0797aa5f25597efa7b897515',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd291058e4780c0f330dcc906cff3c9472aa7c1cf',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1673,7 +1673,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'MsU-cuTJ-2qmDXwu2uAte38j5EdjkfodQz76DZubrZQC',
+              'version': '3Vuxtp3m63h2bcmamz2iBC04rNQOUmQ3O6eDyLoVY3EC',
           },
       ],
       'condition': 'checkout_android',
@@ -1818,7 +1818,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '1ff6ae7870a674b2e2ee7b003004cca4d004d8b1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '7ff599b75384f093d8cc8e821a61249e4ae7088d',
+    Var('webrtc_git') + '/src.git' + '@' + 'a106095333cc0ecb0405dbc2199445878c197c16',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 93dd790..0308f591 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1056,7 +1056,9 @@
           # Required for interop with the third-party webrtc library.
           'third_party/blink/renderer/modules/peerconnection/mock_peer_connection_impl\.cc',
           'third_party/blink/renderer/modules/peerconnection/mock_peer_connection_impl\.h',
-
+          # This code is in the process of being extracted into a third-party library.
+          # See https://crbug.com/1322914
+          '^net/cert/pki/path_builder_unittest\.cc',
           # TODO(https://crbug.com/1364577): Various uses that should be
           # migrated to something else.
           # Should use base::OnceCallback or base::RepeatingCallback.
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 6bccdd4..ee18c8a 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -288,8 +288,6 @@
                             + " rather than token count."),
             Flag.baseFeature(AwFeatures.WEBVIEW_HIT_TEST_IN_BLINK_ON_TOUCH_START,
                     "Hit test on touch start in blink"),
-            Flag.baseFeature(BlinkFeatures.SCROLL_UPDATE_OPTIMIZATIONS,
-                    "Enable scroll update optimizations. See https://crbug.com/1346789."),
             Flag.baseFeature(BaseFeatures.ALIGN_WAKE_UPS, "Align delayed wake ups at 125 Hz"),
             Flag.baseFeature(BlinkSchedulerFeatures.THREADED_SCROLL_PREVENT_RENDERING_STARVATION,
                     "Enable rendering starvation-prevention during threaded scrolling."
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index b0c0c56..6ca0fd7a8 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1204,6 +1204,8 @@
     "system/cast/cast_feature_pod_controller.h",
     "system/cast/cast_notification_controller.cc",
     "system/cast/cast_notification_controller.h",
+    "system/cast/cast_zero_state_view.cc",
+    "system/cast/cast_zero_state_view.h",
     "system/cast/tray_cast.cc",
     "system/cast/tray_cast.h",
     "system/cast/unified_cast_detailed_view_controller.cc",
@@ -1230,6 +1232,8 @@
     "system/diagnostics/routine_log.h",
     "system/diagnostics/telemetry_log.cc",
     "system/diagnostics/telemetry_log.h",
+    "system/do_not_disturb_notification_controller.cc",
+    "system/do_not_disturb_notification_controller.h",
     "system/eche/eche_icon_loading_indicator_view.cc",
     "system/eche/eche_icon_loading_indicator_view.h",
     "system/eche/eche_tray.cc",
@@ -1926,23 +1930,23 @@
     "system/usb_peripheral/usb_peripheral_notification_controller.h",
     "system/user/login_status.cc",
     "system/user/login_status.h",
-    "system/video_conference/fake_video_conference_effects.cc",
-    "system/video_conference/fake_video_conference_effects.h",
+    "system/video_conference/bubble/video_conference_bubble.cc",
+    "system/video_conference/bubble/video_conference_bubble.h",
+    "system/video_conference/effects/fake_video_conference_effects.cc",
+    "system/video_conference/effects/fake_video_conference_effects.h",
+    "system/video_conference/effects/video_conference_tray_effects_delegate.cc",
+    "system/video_conference/effects/video_conference_tray_effects_delegate.h",
+    "system/video_conference/effects/video_conference_tray_effects_manager.cc",
+    "system/video_conference/effects/video_conference_tray_effects_manager.h",
+    "system/video_conference/effects/video_conference_tray_effects_manager_types.cc",
+    "system/video_conference/effects/video_conference_tray_effects_manager_types.h",
     "system/video_conference/fake_video_conference_tray_controller.cc",
     "system/video_conference/fake_video_conference_tray_controller.h",
-    "system/video_conference/video_conference_bubble.cc",
-    "system/video_conference/video_conference_bubble.h",
     "system/video_conference/video_conference_media_state.h",
     "system/video_conference/video_conference_tray.cc",
     "system/video_conference/video_conference_tray.h",
     "system/video_conference/video_conference_tray_controller.cc",
     "system/video_conference/video_conference_tray_controller.h",
-    "system/video_conference/video_conference_tray_effects_delegate.cc",
-    "system/video_conference/video_conference_tray_effects_delegate.h",
-    "system/video_conference/video_conference_tray_effects_manager.cc",
-    "system/video_conference/video_conference_tray_effects_manager.h",
-    "system/video_conference/video_conference_tray_effects_manager_types.cc",
-    "system/video_conference/video_conference_tray_effects_manager_types.h",
     "system/virtual_keyboard/virtual_keyboard_observer.h",
     "system/virtual_keyboard/virtual_keyboard_tray.cc",
     "system/virtual_keyboard/virtual_keyboard_tray.h",
@@ -2995,6 +2999,7 @@
     "system/diagnostics/networking_log_unittest.cc",
     "system/diagnostics/routine_log_unittest.cc",
     "system/diagnostics/telemetry_log_unittest.cc",
+    "system/do_not_disturb_notification_controller_unittest.cc",
     "system/eche/eche_icon_loading_indicator_view_unittest.cc",
     "system/eche/eche_tray_unittest.cc",
     "system/federated/federated_service_controller_unittest.cc",
@@ -3172,7 +3177,7 @@
     "system/unified/user_chooser_detailed_view_controller_unittest.cc",
     "system/update/update_notification_controller_unittest.cc",
     "system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc",
-    "system/video_conference/video_conference_tray_effects_manager_unittest.cc",
+    "system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc",
     "system/video_conference/video_conference_tray_unittest.cc",
     "system/virtual_keyboard/virtual_keyboard_tray_unittest.cc",
     "test/ash_test_helper_unittest.cc",
diff --git a/ash/accelerators/accelerator_layout_table.h b/ash/accelerators/accelerator_layout_table.h
index 1ba10e7..6482999 100644
--- a/ash/accelerators/accelerator_layout_table.h
+++ b/ash/accelerators/accelerator_layout_table.h
@@ -11,10 +11,62 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/accelerators.h"
 #include "ash/public/mojom/accelerator_info.mojom.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "base/containers/fixed_flat_map.h"
 
 namespace ash {
 
+namespace {
+
+// non-ash accelerator action Id. Contains browser action ids and ambient action
+// ids.
+enum class NonConfigurableActions {
+  // Browser action ids:
+  kBrowserCloseTab,
+  kBrowserCloseWindow,
+  kBrowserSelectLastTab,
+  kBrowserOpenFile,
+  kBrowserNewIncognitoWindow,
+  kBrowserNewTab,
+  kBrowserNewWindow,
+  kBrowserRestoreTab,
+  kBrowserTabSearch,
+  kBrowserClearBrowsingData,
+  kBrowserCloseFindOrStop,
+  kBrowserFocusBookmarks,
+  kBrowserBack,
+  kBrowserForward,
+  kBrowserFind,
+  kBrowserFindNext,
+  kBrowserFindPrevious,
+  kBrowserHome,
+  kBrowserShowDownloads,
+  kBrowserShowHistory,
+  kBrowserFocusSearch,
+  kBrowserFocusMenuBar,
+  kBrowserPrint,
+  kBrowserReload,
+  kBrowserReloadBypassingCache,
+  kBrowserZoomNormal,
+  kBrowserBookmarkAllTabs,
+  kBrowserSavePage,
+  kBrowserBookmarkThisTab,
+  kBrowserShowAppMenu,
+  kBrowserShowBookmarkManager,
+  kBrowserDevToolsConsole,
+  kBrowserDebToolsInspect,
+  kBrowserDevTools,
+  kBrowserShowBookmarkBar,
+  kBrowserViewSource,
+  kBrowserZoomPlus,
+  kBrowserZoomMinus,
+  kBrowserFocusLocation,
+  kBrowserFocusToolbar,
+  kBrowserFocusInactivePopupForAccessibility,
+};
+
+}  // namespace
+
 // Contains details for UI styling of an accelerator.
 struct ASH_EXPORT AcceleratorLayoutDetails {
   // The accelerator action id associated for a source. Concat `source` and
@@ -39,6 +91,94 @@
   mojom::AcceleratorSource source;
 };
 
+// A map between browser action id and accelerator description ID.
+ASH_EXPORT constexpr auto kBrowserActionToStringIdMap = base::MakeFixedFlatMap<
+    NonConfigurableActions,
+    int>({
+    {NonConfigurableActions::kBrowserCloseTab,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_TAB},
+    {NonConfigurableActions::kBrowserCloseWindow,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_WINDOW},
+    {NonConfigurableActions::kBrowserSelectLastTab,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SELECT_LAST_TAB},
+    {NonConfigurableActions::kBrowserOpenFile,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_OPEN_FILE},
+    {NonConfigurableActions::kBrowserNewIncognitoWindow,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_INCOGNITO_WINDOW},
+    {NonConfigurableActions::kBrowserNewTab,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_TAB},
+    {NonConfigurableActions::kBrowserNewWindow,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_WINDOW},
+    {NonConfigurableActions::kBrowserRestoreTab,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_RESTORE_TAB},
+    {NonConfigurableActions::kBrowserTabSearch,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_TAB_SEARCH},
+    {NonConfigurableActions::kBrowserClearBrowsingData,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLEAR_BROWSING_DATA},
+    {NonConfigurableActions::kBrowserCloseFindOrStop,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_FIND_OR_STOP},
+    {NonConfigurableActions::kBrowserFocusBookmarks,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_BOOKMARKS},
+    {NonConfigurableActions::kBrowserBack,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_BACK},
+    {NonConfigurableActions::kBrowserForward,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FORWARD},
+    {NonConfigurableActions::kBrowserFind,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND},
+    {NonConfigurableActions::kBrowserFindNext,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_NEXT},
+    {NonConfigurableActions::kBrowserFindPrevious,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_PREVIOUS},
+    {NonConfigurableActions::kBrowserHome,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_HOME},
+    {NonConfigurableActions::kBrowserShowDownloads,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_DOWNLOADS},
+    {NonConfigurableActions::kBrowserShowHistory,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_HISTORY},
+    {NonConfigurableActions::kBrowserFocusSearch,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_SEARCH},
+    {NonConfigurableActions::kBrowserFocusMenuBar,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_MENU_BAR},
+    {NonConfigurableActions::kBrowserPrint,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_PRINT},
+    {NonConfigurableActions::kBrowserReload,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD},
+    {NonConfigurableActions::kBrowserReloadBypassingCache,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD_BYPASSING_CACHE},
+    {NonConfigurableActions::kBrowserZoomNormal,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_NORMAL},
+    {NonConfigurableActions::kBrowserBookmarkAllTabs,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_BOOKMARK_ALL_TABS},
+    {NonConfigurableActions::kBrowserSavePage,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SAVE_PAGE},
+    {NonConfigurableActions::kBrowserBookmarkThisTab,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_BOOKMARK_THIS_TAB},
+    {NonConfigurableActions::kBrowserShowAppMenu,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_APP_MENU},
+    {NonConfigurableActions::kBrowserShowBookmarkManager,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_BOOKMARK_MANAGER},
+    {NonConfigurableActions::kBrowserDevToolsConsole,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS_CONSOLE},
+    {NonConfigurableActions::kBrowserDebToolsInspect,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS_INSPECT},
+    {NonConfigurableActions::kBrowserDevTools,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS},
+    {NonConfigurableActions::kBrowserShowBookmarkBar,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_BOOKMARK_BAR},
+    {NonConfigurableActions::kBrowserViewSource,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_VIEW_SOURCE},
+    {NonConfigurableActions::kBrowserZoomPlus,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_PLUS},
+    {NonConfigurableActions::kBrowserZoomMinus,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_MINUS},
+    {NonConfigurableActions::kBrowserFocusLocation,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_LOCATION},
+    {NonConfigurableActions::kBrowserFocusToolbar,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_TOOLBAR},
+    {NonConfigurableActions::kBrowserFocusInactivePopupForAccessibility,
+     IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY},
+});
+
 // A fixed array of accelerator layouts used for categorization and styling of
 // accelerator actions. The ordering of the array is important and is used
 // 1:1 for displaying shortcuts in the shortcut customization app.
diff --git a/ash/accessibility/magnifier/fullscreen_magnifier_controller.h b/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
index c00e460..001026f 100644
--- a/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
+++ b/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
@@ -10,7 +10,6 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
-#include "base/timer/timer.h"
 #include "ui/aura/window_observer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/events/event_handler.h"
@@ -246,10 +245,6 @@
   // to center the |rect| in that dimension.
   void MoveMagnifierWindowFollowRect(const gfx::Rect& rect);
 
-  // Invoked when |move_magnifier_timer_| fires to move the magnifier window to
-  // follow the caret.
-  void OnMoveMagnifierTimer();
-
   // Target root window. This must not be NULL.
   aura::Window* root_window_;
 
@@ -306,9 +301,6 @@
   // reacts to gestures.
   std::unique_ptr<ui::GestureProviderAura> gesture_provider_;
 
-  // Timer for moving magnifier window when it fires.
-  base::OneShotTimer move_magnifier_timer_;
-
   // Most recent caret position in |root_window_| coordinates.
   gfx::Point caret_point_;
 
diff --git a/ash/app_list/app_list_color_provider_impl.cc b/ash/app_list/app_list_color_provider_impl.cc
index 5e4cefb..49c81f3 100644
--- a/ash/app_list/app_list_color_provider_impl.cc
+++ b/ash/app_list/app_list_color_provider_impl.cc
@@ -9,7 +9,6 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/color_util.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/color_palette.h"
 
@@ -20,12 +19,6 @@
 constexpr float kLightInkDropOpacity = 0.08f;
 constexpr float kDarkInkDropOpacity = 0.12f;
 
-// Helper to check if tablet mode is enabled.
-bool IsTabletModeEnabled() {
-  return Shell::Get()->tablet_mode_controller() &&
-         Shell::Get()->tablet_mode_controller()->InTabletMode();
-}
-
 bool IsDarkModeEnabled() {
   // May be null in unit tests.
   if (!Shell::HasInstance())
@@ -35,57 +28,10 @@
 
 }  // namespace
 
-AppListColorProviderImpl::AppListColorProviderImpl()
-    : is_background_blur_enabled_(features::IsBackgroundBlurEnabled()) {}
+AppListColorProviderImpl::AppListColorProviderImpl() = default;
 
 AppListColorProviderImpl::~AppListColorProviderImpl() = default;
 
-SkColor AppListColorProviderImpl::GetSearchBoxBackgroundColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  const ui::ColorProvider* color_provider = app_list_widget->GetColorProvider();
-  if (IsTabletModeEnabled()) {
-    return color_provider->GetColor(is_background_blur_enabled_
-                                        ? kColorAshShieldAndBase80
-                                        : kColorAshShieldAndBase95);
-  }
-  return color_provider->GetColor(kColorAshControlBackgroundColorInactive);
-}
-
-SkColor AppListColorProviderImpl::GetSearchBoxCardBackgroundColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      is_background_blur_enabled_ ? kColorAshShieldAndBase80
-                                  : kColorAshShieldAndBase95);
-}
-
-SkColor AppListColorProviderImpl::GetSearchBoxTextColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      cros_tokens::kTextColorPrimary);
-}
-
-SkColor AppListColorProviderImpl::GetSearchBoxSecondaryTextColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      cros_tokens::kTextColorSecondary);
-}
-
-SkColor AppListColorProviderImpl::GetSearchBoxSuggestionTextColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      kColorAshTextColorSuggestion);
-}
-
 SkColor AppListColorProviderImpl::GetAppListItemTextColor(
     const views::Widget* app_list_widget) const {
   DCHECK(app_list_widget);
@@ -102,14 +48,6 @@
       kColorAshButtonIconColor);
 }
 
-SkColor AppListColorProviderImpl::GetSearchBoxIconColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      kColorAshButtonIconColor);
-}
-
 SkColor AppListColorProviderImpl::GetFolderBackgroundColor(
     const views::Widget* app_list_widget) const {
   DCHECK(app_list_widget);
diff --git a/ash/app_list/app_list_color_provider_impl.h b/ash/app_list/app_list_color_provider_impl.h
index 9b45b44..212809e 100644
--- a/ash/app_list/app_list_color_provider_impl.h
+++ b/ash/app_list/app_list_color_provider_impl.h
@@ -14,22 +14,10 @@
   AppListColorProviderImpl();
   ~AppListColorProviderImpl() override;
   // AppListColorProvider:
-  SkColor GetSearchBoxBackgroundColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxSecondaryTextColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxSuggestionTextColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxTextColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetAppListItemTextColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxIconColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxCardBackgroundColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetFolderBackgroundColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetFolderTitleTextColor(
@@ -60,10 +48,6 @@
   SkColor GetSearchResultViewHighlightColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetTextColorURL(const views::Widget* app_list_widget) const override;
-
- private:
-  // Whether feature BackgroundBlur is enabled. Cached for efficiency.
-  const bool is_background_blur_enabled_;
 };
 
 }  // namespace ash
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index 7681e38..4a0e4ed 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -388,10 +388,9 @@
           : gfx::Canvas::TEXT_ALIGN_CENTER);
   // Fullscreen launcher uses custom colors (dark-on-light by default).
   search_box()->set_placeholder_text_color(
-      is_search_box_active()
-          ? AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
-                GetWidget())
-          : AppListColorProvider::Get()->GetSearchBoxTextColor(GetWidget()));
+      GetWidget()->GetColorProvider()->GetColor(
+          is_search_box_active() ? kColorAshTextColorSecondary
+                                 : kColorAshTextColorPrimary));
 }
 
 void SearchBoxView::UpdateSearchBoxBorder() {
@@ -437,17 +436,16 @@
 
 void SearchBoxView::OnThemeChanged() {
   SearchBoxViewBase::OnThemeChanged();
-  const auto* app_list_widget = GetWidget();
+  const SkColor button_icon_color =
+      GetWidget()->GetColorProvider()->GetColor(kColorAshButtonIconColor);
   close_button()->SetImage(
       views::ImageButton::STATE_NORMAL,
-      gfx::CreateVectorIcon(
-          views::kIcCloseIcon, GetSearchBoxIconSize(),
-          AppListColorProvider::Get()->GetSearchBoxIconColor(app_list_widget)));
+      gfx::CreateVectorIcon(views::kIcCloseIcon, GetSearchBoxIconSize(),
+                            button_icon_color));
   assistant_button()->SetImage(
       views::ImageButton::STATE_NORMAL,
-      gfx::CreateVectorIcon(
-          chromeos::kAssistantIcon, GetSearchBoxIconSize(),
-          AppListColorProvider::Get()->GetSearchBoxIconColor(app_list_widget)));
+      gfx::CreateVectorIcon(chromeos::kAssistantIcon, GetSearchBoxIconSize(),
+                            button_icon_color));
   OnWallpaperColorsChanged();
 }
 
@@ -814,7 +812,7 @@
       search_engine_is_google ? google_icon : kSearchEngineNotGoogleIcon;
   SetSearchIconImage(gfx::CreateVectorIcon(
       icon, GetSearchBoxIconSize(),
-      AppListColorProvider::Get()->GetSearchBoxIconColor(GetWidget())));
+      GetWidget()->GetColorProvider()->GetColor(kColorAshButtonIconColor)));
 }
 
 bool SearchBoxView::IsValidAutocompleteText(
@@ -835,7 +833,7 @@
 
 void SearchBoxView::UpdateTextColor() {
   search_box()->SetTextColor(
-      GetColorProvider()->GetColor(cros_tokens::kTextColorPrimary));
+      GetColorProvider()->GetColor(kColorAshTextColorPrimary));
 }
 
 void SearchBoxView::UpdatePlaceholderTextAndAccessibleName() {
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index 06470df..2e7c363 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -293,7 +293,7 @@
   SetSearchEngineIsGoogle(true);
   SetSearchBoxActive(false, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
-      kGoogleBlackIcon, view()->GetSearchBoxIconSize(), kDefaultSearchboxColor);
+      kGoogleBlackIcon, view()->GetSearchBoxIconSize(), gfx::kGoogleGrey900);
 
   const gfx::ImageSkia actual_icon = view()->search_icon()->GetImage();
 
@@ -307,7 +307,7 @@
   SetSearchBoxActive(true, ui::ET_MOUSE_PRESSED);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
       vector_icons::kGoogleColorIcon, view()->GetSearchBoxIconSize(),
-      kDefaultSearchboxColor);
+      gfx::kGoogleGrey900);
 
   const gfx::ImageSkia actual_icon = view()->search_icon()->GetImage();
 
@@ -321,7 +321,7 @@
   SetSearchBoxActive(false, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
       kSearchEngineNotGoogleIcon, view()->GetSearchBoxIconSize(),
-      kDefaultSearchboxColor);
+      gfx::kGoogleGrey900);
 
   const gfx::ImageSkia actual_icon = view()->search_icon()->GetImage();
 
@@ -335,7 +335,7 @@
   SetSearchBoxActive(true, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
       kSearchEngineNotGoogleIcon, view()->GetSearchBoxIconSize(),
-      kDefaultSearchboxColor);
+      gfx::kGoogleGrey900);
 
   const gfx::ImageSkia actual_icon = view()->search_icon()->GetImage();
 
diff --git a/ash/app_list/views/search_result_image_list_view.cc b/ash/app_list/views/search_result_image_list_view.cc
index 48620c8..c583d97f 100644
--- a/ash/app_list/views/search_result_image_list_view.cc
+++ b/ash/app_list/views/search_result_image_list_view.cc
@@ -11,6 +11,7 @@
 #include "ash/app_list/views/search_result_image_view.h"
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -44,6 +45,8 @@
   title_label_ = AddChildView(std::make_unique<views::Label>(
       l10n_util::GetStringUTF16(IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_IMAGES)));
   title_label_->SetBackgroundColor(SK_ColorTRANSPARENT);
+  title_label_->SetAutoColorReadabilityEnabled(false);
+  title_label_->SetEnabledColorId(kColorAshTextColorSecondary);
   title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   title_label_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
       kPreferredTitleTopMargins, kPreferredTitleHorizontalMargins,
@@ -104,12 +107,6 @@
   node_data->role = ax::mojom::Role::kListBox;
 }
 
-void SearchResultImageListView::OnThemeChanged() {
-  SearchResultContainerView::OnThemeChanged();
-  title_label_->SetEnabledColor(
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(GetWidget()));
-}
-
 void SearchResultImageListView::OnSelectedResultChanged() {
   // TODO(crbug.com/1352636) once result selection spec is available.
   return;
diff --git a/ash/app_list/views/search_result_image_list_view.h b/ash/app_list/views/search_result_image_list_view.h
index 737231e9..4c136d9e 100644
--- a/ash/app_list/views/search_result_image_list_view.h
+++ b/ash/app_list/views/search_result_image_list_view.h
@@ -45,7 +45,6 @@
  private:
   // Overridden from views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  void OnThemeChanged() override;
 
   // Overridden from SearchResultContainerView:
   void OnSelectedResultChanged() override;
diff --git a/ash/app_list/views/search_result_image_view.cc b/ash/app_list/views/search_result_image_view.cc
index bd6a266..63a457e 100644
--- a/ash/app_list/views/search_result_image_view.cc
+++ b/ash/app_list/views/search_result_image_view.cc
@@ -7,6 +7,7 @@
 #include "ash/app_list/model/search/search_result.h"
 #include "ash/app_list/views/search_result_image_view_delegate.h"
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -55,7 +56,7 @@
   // TODO(crbug.com/1352636) remove placeholder image.
   result_image_->SetImage(gfx::CreateVectorIcon(
       vector_icons::kGoogleColorIcon, kIconSize,
-      AppListColorProvider::Get()->GetSearchBoxIconColor(GetWidget())));
+      GetWidget()->GetColorProvider()->GetColor(kColorAshButtonIconColor)));
 }
 
 void SearchResultImageView::OnGestureEvent(ui::GestureEvent* event) {
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index a0f7e1c9..e6259a0 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -25,6 +25,7 @@
 #include "ash/public/cpp/ash_typography.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "base/bind.h"
 #include "base/dcheck_is_on.h"
 #include "base/time/time.h"
@@ -119,6 +120,8 @@
   title_label_ = AddChildView(std::make_unique<views::Label>(
       u"", CONTEXT_SEARCH_RESULT_CATEGORY_LABEL, STYLE_PRODUCTIVITY_LAUNCHER));
   title_label_->SetBackgroundColor(SK_ColorTRANSPARENT);
+  title_label_->SetAutoColorReadabilityEnabled(false);
+  title_label_->SetEnabledColorId(kColorAshTextColorSecondary);
   title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   title_label_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
       kPreferredTitleTopMargins, kPreferredTitleHorizontalMargins,
@@ -471,12 +474,6 @@
   return results_container_->GetHeightForWidth(w);
 }
 
-void SearchResultListView::OnThemeChanged() {
-  SearchResultContainerView::OnThemeChanged();
-  title_label_->SetEnabledColor(
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(GetWidget()));
-}
-
 void SearchResultListView::SearchResultActivated(SearchResultView* view,
                                                  int event_flags,
                                                  bool by_button_press) {
diff --git a/ash/app_list/views/search_result_list_view.h b/ash/app_list/views/search_result_list_view.h
index 398c02d..ec71458a 100644
--- a/ash/app_list/views/search_result_list_view.h
+++ b/ash/app_list/views/search_result_list_view.h
@@ -132,7 +132,6 @@
   // Overridden from views::View:
   void Layout() override;
   int GetHeightForWidth(int w) const override;
-  void OnThemeChanged() override;
 
   // Fetches the category of results this view should show.
   SearchResult::Category GetSearchCategory();
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 85c25746..64d7da81 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -17,6 +17,7 @@
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/style/color_provider.h"
 #include "ash/search_box/search_box_constants.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/system_shadow.h"
 #include "base/bind.h"
 #include "base/time/time.h"
@@ -164,6 +165,7 @@
   GetBackground()->SetNativeControlColor(
       ColorProvider::Get()->GetBaseLayerColor(
           ColorProvider::BaseLayerType::kTransparent80));
+
   // SchedulePaint() marks the entire SearchResultPageView's bounds as dirty.
   SchedulePaint();
   AppListPage::OnThemeChanged();
@@ -366,17 +368,6 @@
   return search_view_->CanSelectSearchResults();
 }
 
-SkColor SearchResultPageView::GetBackgroundColorForState(
-    AppListState state) const {
-  const auto* app_list_widget = GetWidget();
-  if (state == AppListState::kStateSearchResults) {
-    return AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor(
-        app_list_widget);
-  }
-  return AppListColorProvider::Get()->GetSearchBoxBackgroundColor(
-      app_list_widget);
-}
-
 bool SearchResultPageView::ShouldShowSearchResultView() const {
   return contents_view()->GetSearchBoxView()->HasValidQuery();
 }
@@ -472,23 +463,6 @@
   AnimateToSearchResultsState(to_result_state);
 }
 
-void SearchResultPageView::OnAnimationUpdated(double progress,
-                                              AppListState from_state,
-                                              AppListState to_state) {
-  if (from_state != AppListState::kStateSearchResults &&
-      to_state != AppListState::kStateSearchResults) {
-    return;
-  }
-  const SkColor color = gfx::Tween::ColorValueBetween(
-      progress, GetBackgroundColorForState(from_state),
-      GetBackgroundColorForState(to_state));
-
-  if (color != background()->get_color()) {
-    background()->SetNativeControlColor(color);
-    SchedulePaint();
-  }
-}
-
 gfx::Size SearchResultPageView::GetPreferredSearchBoxSize() const {
   static gfx::Size size = gfx::Size(kWidth, kActiveSearchBoxHeight);
   return size;
diff --git a/ash/app_list/views/search_result_page_view.h b/ash/app_list/views/search_result_page_view.h
index 13e52e98..9558ded 100644
--- a/ash/app_list/views/search_result_page_view.h
+++ b/ash/app_list/views/search_result_page_view.h
@@ -55,9 +55,6 @@
       const gfx::Rect& search_box_bounds) const override;
   void OnAnimationStarted(AppListState from_state,
                           AppListState to_state) override;
-  void OnAnimationUpdated(double progress,
-                          AppListState from_state,
-                          AppListState to_state) override;
   gfx::Size GetPreferredSearchBoxSize() const override;
 
   // Whether any results are available for selection within the search result
@@ -70,9 +67,6 @@
     return dialog_controller_->dialog();
   }
 
-  // Returns background color for the given state.
-  SkColor GetBackgroundColorForState(AppListState state) const;
-
   // Hide zero state search result view when ProductivityLauncher is enabled.
   bool ShouldShowSearchResultView() const;
 
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 48d2db5..56258dfd 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -103,6 +103,56 @@
 // title and the details label need to be elided.
 constexpr float kDetailsElideRatio = 0.25f;
 
+bool IsTitleLabel(SearchResultView::LabelType label_type) {
+  switch (label_type) {
+    case SearchResultView::LabelType::kDetails:
+    case SearchResultView::LabelType::kKeyboardShortcut:
+      return false;
+    case SearchResultView::LabelType::kTitle:
+    case SearchResultView::LabelType::kBigTitle:
+    case SearchResultView::LabelType::kBigTitleSuperscript:
+      return true;
+  }
+}
+
+ui::ColorId GetLabelColorId(bool is_title, const SearchResult::Tags& tags) {
+  auto color_tag = SearchResult::Tag::NONE;
+  for (const auto& tag : tags) {
+    // Each label only supports one type of color tag. `color_tag` should only
+    // be set once.
+    if (tag.styles & SearchResult::Tag::URL) {
+      DCHECK(color_tag == SearchResult::Tag::NONE ||
+             color_tag == SearchResult::Tag::URL);
+      color_tag = SearchResult::Tag::URL;
+    }
+    if (tag.styles & SearchResult::Tag::GREEN) {
+      DCHECK(color_tag == SearchResult::Tag::NONE ||
+             color_tag == SearchResult::Tag::GREEN);
+      color_tag = SearchResult::Tag::GREEN;
+    }
+    if (tag.styles & SearchResult::Tag::RED) {
+      DCHECK(color_tag == SearchResult::Tag::NONE ||
+             color_tag == SearchResult::Tag::RED);
+      color_tag = SearchResult::Tag::RED;
+    }
+  }
+
+  switch (color_tag) {
+    case SearchResult::Tag::NONE:
+      ABSL_FALLTHROUGH_INTENDED;
+    case SearchResult::Tag::DIM:
+      ABSL_FALLTHROUGH_INTENDED;
+    case SearchResult::Tag::MATCH:
+      return is_title ? kColorAshTextColorPrimary : kColorAshTextColorSecondary;
+    case SearchResult::Tag::URL:
+      return kColorAshTextColorURL;
+    case SearchResult::Tag::GREEN:
+      return kColorAshTextColorPositive;
+    case SearchResult::Tag::RED:
+      return kColorAshTextColorAlert;
+  }
+}
+
 views::ImageView* SetupChildImageView(views::FlexLayoutView* parent) {
   views::ImageView* image_view =
       parent->AddChildView(std::make_unique<views::ImageView>());
@@ -117,6 +167,7 @@
     views::FlexLayoutView* parent,
     SearchResultView::SearchResultViewType view_type,
     SearchResultView::LabelType label_type,
+    ui::ColorId color_id,
     int flex_order,
     bool has_keyboard_shortcut_contents,
     bool is_multi_line,
@@ -128,6 +179,8 @@
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->GetViewAccessibility().OverrideIsIgnored(true);
   label->SetBackgroundColor(SK_ColorTRANSPARENT);
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetEnabledColorId(color_id);
   label->SetVisible(false);
   label->SetElideBehavior(overflow_behavior ==
                                   SearchResultTextItem::OverflowBehavior::kElide
@@ -351,11 +404,12 @@
   title_container_->SetFlexAllocationOrder(
       views::FlexAllocationOrder::kReverse);
 
-  result_text_separator_label_ = SetupChildLabelView(
-      title_and_details_container_, view_type_, LabelType::kDetails,
-      kSeparatorOrder, has_keyboard_shortcut_contents_,
-      /*is_multi_line=*/false,
-      SearchResultTextItem::OverflowBehavior::kNoElide);
+  result_text_separator_label_ =
+      SetupChildLabelView(title_and_details_container_, view_type_,
+                          LabelType::kDetails, kColorAshTextColorSecondary,
+                          kSeparatorOrder, has_keyboard_shortcut_contents_,
+                          /*is_multi_line=*/false,
+                          SearchResultTextItem::OverflowBehavior::kNoElide);
   result_text_separator_label_->SetText(
       l10n_util::GetStringUTF16(IDS_ASH_SEARCH_RESULT_SEPARATOR));
   result_text_separator_label_->GetViewAccessibility().OverrideIsIgnored(true);
@@ -372,20 +426,22 @@
           .WithOrder(TitleDetailContainerOrder)
           .WithWeight(1));
 
-  rating_separator_label_ = SetupChildLabelView(
-      title_and_details_container_, view_type_, LabelType::kDetails,
-      kSeparatorOrder, has_keyboard_shortcut_contents_,
-      /*is_multi_line=*/false,
-      SearchResultTextItem::OverflowBehavior::kNoElide);
+  rating_separator_label_ =
+      SetupChildLabelView(title_and_details_container_, view_type_,
+                          LabelType::kDetails, kColorAshTextColorSecondary,
+                          kSeparatorOrder, has_keyboard_shortcut_contents_,
+                          /*is_multi_line=*/false,
+                          SearchResultTextItem::OverflowBehavior::kNoElide);
   rating_separator_label_->SetText(
       l10n_util::GetStringUTF16(IDS_ASH_SEARCH_RESULT_SEPARATOR));
   rating_separator_label_->GetViewAccessibility().OverrideIsIgnored(true);
 
-  rating_ = SetupChildLabelView(
-      title_and_details_container_, view_type_, LabelType::kDetails,
-      kRatingOrder, has_keyboard_shortcut_contents_,
-      /*is_multi_line=*/false,
-      SearchResultTextItem::OverflowBehavior::kNoElide);
+  rating_ =
+      SetupChildLabelView(title_and_details_container_, view_type_,
+                          LabelType::kDetails, kColorAshTextColorSecondary,
+                          kRatingOrder, has_keyboard_shortcut_contents_,
+                          /*is_multi_line=*/false,
+                          SearchResultTextItem::OverflowBehavior::kNoElide);
 
   rating_star_ = SetupChildImageView(title_and_details_container_);
   rating_star_->SetBorder(views::CreateEmptyBorder(
@@ -611,6 +667,7 @@
                         SearchResultTextItem::OverflowBehavior::kNoElide;
         views::Label* label = SetupChildLabelView(
             parent, view_type_, label_type,
+            GetLabelColorId(IsTitleLabel(label_type), span.GetTextTags()),
             !elidable ? kNonElideLabelOrder
                       : kElidableLabelOrderStart + label_count,
             has_keyboard_shortcut_contents,
@@ -653,7 +710,7 @@
           }
         }
 
-        label_tags.push_back(LabelAndTag(label, span.GetTextTags()));
+        label_tags.emplace_back(label, span.GetTextTags());
       } break;
       case SearchResultTextItemType::kIconifiedText: {
         SearchResultInlineIconView* iconified_text_view =
@@ -860,86 +917,44 @@
 }
 
 void SearchResultView::StyleLabel(views::Label* label,
-                                  bool is_title_label,
                                   const SearchResult::Tags& tags) {
   // Reset font weight styling for label.
   label->ApplyBaselineTextStyle();
-  auto color_tag = SearchResult::Tag::NONE;
-  for (const auto& tag : tags) {
-    // Each label only supports one type of color tag. `color_tag` should only
-    // be set once.
-    if (tag.styles & SearchResult::Tag::URL) {
-      DCHECK(color_tag == SearchResult::Tag::NONE ||
-             color_tag == SearchResult::Tag::URL);
-      color_tag = SearchResult::Tag::URL;
-    }
-    if (tag.styles & SearchResult::Tag::GREEN) {
-      DCHECK(color_tag == SearchResult::Tag::NONE ||
-             color_tag == SearchResult::Tag::GREEN);
-      color_tag = SearchResult::Tag::GREEN;
-    }
-    if (tag.styles & SearchResult::Tag::RED) {
-      DCHECK(color_tag == SearchResult::Tag::NONE ||
-             color_tag == SearchResult::Tag::RED);
-      color_tag = SearchResult::Tag::RED;
-    }
 
+  for (const auto& tag : tags) {
     bool has_match_tag = (tag.styles & SearchResult::Tag::MATCH);
     if (has_match_tag)
       label->SetTextStyleRange(AshTextStyle::STYLE_HIGHLIGHT, tag.range);
   }
-
-  switch (color_tag) {
-    case SearchResult::Tag::NONE:
-      ABSL_FALLTHROUGH_INTENDED;
-    case SearchResult::Tag::DIM:
-      ABSL_FALLTHROUGH_INTENDED;
-    case SearchResult::Tag::MATCH:
-      label->SetEnabledColorId(is_title_label
-                                   ? cros_tokens::kTextColorPrimary
-                                   : cros_tokens::kTextColorSecondary);
-      break;
-    case SearchResult::Tag::URL:
-      label->SetEnabledColorId(kColorAshTextColorURL);
-      break;
-    case SearchResult::Tag::GREEN:
-      label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorPositive));
-      break;
-    case SearchResult::Tag::RED:
-      label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorAlert));
-      break;
-  }
 }
 
 void SearchResultView::StyleBigTitleContainer() {
   for (auto& span : big_title_label_tags_) {
-    StyleLabel(span.GetLabel(), true /*is_title_label*/, span.GetTags());
+    StyleLabel(span.GetLabel(), span.GetTags());
   }
 }
 
 void SearchResultView::StyleBigTitleSuperscriptContainer() {
   for (auto& span : big_title_superscript_label_tags_) {
-    StyleLabel(span.GetLabel(), true /*is_title_label*/, span.GetTags());
+    StyleLabel(span.GetLabel(), span.GetTags());
   }
 }
 
 void SearchResultView::StyleTitleContainer() {
   for (auto& span : title_label_tags_) {
-    StyleLabel(span.GetLabel(), true /*is_title_label*/, span.GetTags());
+    StyleLabel(span.GetLabel(), span.GetTags());
   }
 }
 
 void SearchResultView::StyleDetailsContainer() {
   for (auto& span : details_label_tags_) {
-    StyleLabel(span.GetLabel(), false /*is_title_label*/, span.GetTags());
+    StyleLabel(span.GetLabel(), span.GetTags());
   }
 }
 
 void SearchResultView::StyleKeyboardShortcutContainer() {
   for (auto& span : keyboard_shortcut_container_tags_) {
-    StyleLabel(span.GetLabel(), false /*is_title_label*/, span.GetTags());
+    StyleLabel(span.GetLabel(), span.GetTags());
   }
 }
 
@@ -1180,31 +1195,11 @@
 }
 
 void SearchResultView::OnThemeChanged() {
-  if (!big_title_label_tags_.empty())
-    StyleBigTitleContainer();
-  if (!title_label_tags_.empty())
-    StyleTitleContainer();
-  if (!details_label_tags_.empty())
-    StyleDetailsContainer();
-  if (!keyboard_shortcut_container_tags_.empty())
-    StyleKeyboardShortcutContainer();
-
-  const auto* app_list_widget = GetWidget();
-  result_text_separator_label_->SetEnabledColor(
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
-          app_list_widget));
-
-  rating_separator_label_->SetEnabledColor(
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
-          app_list_widget));
-  rating_->SetEnabledColor(
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
-          app_list_widget));
+  views::View::OnThemeChanged();
   rating_star_->SetImage(gfx::CreateVectorIcon(
       kBadgeRatingIcon, kSearchRatingStarSize,
-      AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
-          app_list_widget)));
-  views::View::OnThemeChanged();
+      GetWidget()->GetColorProvider()->GetColor(kColorAshTextColorSecondary)));
+  SchedulePaint();
 }
 
 void SearchResultView::OnGestureEvent(ui::GestureEvent* event) {
diff --git a/ash/app_list/views/search_result_view.h b/ash/app_list/views/search_result_view.h
index 81dcd34..31b3f28 100644
--- a/ash/app_list/views/search_result_view.h
+++ b/ash/app_list/views/search_result_view.h
@@ -205,9 +205,7 @@
   void UpdateKeyboardShortcutContainer();
   void UpdateRating();
 
-  void StyleLabel(views::Label* label,
-                  bool is_title_label,
-                  const SearchResult::Tags& tags);
+  void StyleLabel(views::Label* label, const SearchResult::Tags& tags);
   void StyleBigTitleContainer();
   void StyleBigTitleSuperscriptContainer();
   void StyleTitleContainer();
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index c5781b2d..8d0783a 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -492,6 +492,12 @@
       <message name="IDS_ASH_STATUS_TRAY_CAST_ACCESS_CODE_CAST_CONNECT" desc="Menu item label that, when clicked on, opens the access code casting dialog.">
         Connect with a code
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE" desc="The main label for the system tray Cast view when no Chromecast devices are available.">
+        Cast your screen from your Chromebook
+      </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE" desc="The secondary label for the system tray Cast view when no Chromecast devices are available.">
+        Make sure your Chromebook is on the same Wi-Fi network as your Chromecast device.
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_DARK_THEME" desc="The label used as the header in the dark theme popup. [CHAR_LIMIT=19">
         Dark theme
       </message>
@@ -1627,6 +1633,12 @@
       <message name="IDS_ASH_PHONE_HUB_APP_STREAM_LAUNCHER_TITLE" desc="Title for the launcher that displays apps from your phone.">
         Apps from your phone
       </message>
+      <message name="IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED" desc="Tooltip text for the apps that cannot be streamed.">
+        Not supported
+      </message>
+      <message name="IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED_BY_APP" desc="Tooltip text for the apps that cannot be streamed due to exclusion by the developer.">
+        Not supported by app
+      </message>
       <message name="IDS_ASH_PHONE_HUB_APPS_OPT_IN_DESCRIPTION" desc="Description for the apps opt in view.">
         View your phone's apps on your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>
       </message>
@@ -3477,6 +3489,129 @@
       <message name="IDS_ASH_ACCELERATOR_ACTION_DEV_TOGGLE_UNIFIED_DESKTOP" translateable="false" desc="Label for accelerator action - Toggle unified desktop.">
         Toggle unified desktop
       </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_TAB" translateable="false" desc="Label for accelerator action - Close the current tab.">
+        Close the current tab
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_WINDOW" translateable="false" desc="Label for accelerator action - Close the current window.">
+        Close the current window
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SELECT_LAST_TAB" translateable="false" desc="Label for accelerator action - Go to the last tab in the window.">
+        Go to the last tab in the window
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_OPEN_FILE" translateable="false" desc="Label for accelerator action - Open a file in the browser.">
+        Open a file in the browser
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_INCOGNITO_WINDOW" translateable="false" desc="Label for accelerator action - Open a new window in Incognito mode.">
+        Open a new window in Incognito mode
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_TAB" translateable="false" desc="Label for accelerator action - Open new tab.">
+        Open new tab
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_NEW_WINDOW" translateable="false" desc="Label for accelerator action - Open new window.">
+        Open new window
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_RESTORE_TAB" translateable="false" desc="Label for accelerator action - Reopen the last tab or window you closed.">
+        Reopen the last tab or window you closed
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_TAB_SEARCH" translateable="false" desc="Label for accelerator action - Search tabs.">
+        Search tabs
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLEAR_BROWSING_DATA" translateable="false" desc="Label for accelerator action - Clear browsing data.">
+        Clear browsing data
+      </message>
+       <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_CLOSE_FIND_OR_STOP" translateable="false" desc="Label for accelerator action - Close any open find window or stop the loading of your current page.">
+        Close any open find window or stop the loading of your current page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_BOOKMARKS" translateable="false" desc="Label for accelerator action - Focus on or Highlight the bookmarks bar (if shown).">
+        Focus on or Highlight the bookmarks bar (if shown)
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_BACK" translateable="false" desc="Label for accelerator action - Go back to previous page.">
+        Go back to previous page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FORWARD" translateable="false" desc="Label for accelerator action - Go forward to next page.">
+        Go forward to next page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND" translateable="false" desc="Label for accelerator action - Search the current page.">
+        Search the current page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_NEXT" translateable="false" desc="Label for accelerator action - Go to the next match for your search.">
+        Go to the next match for your search
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_PREVIOUS" translateable="false" desc="Label for accelerator action - Go to the previous match for your search.">
+        Go to the previous match for your search
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_HOME" translateable="false" desc="Label for accelerator action - Open home page.">
+        Open home page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_DOWNLOADS" translateable="false" desc="Label for accelerator action - Open the Downloads page.">
+        Open the Downloads page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_HISTORY" translateable="false" desc="Label for accelerator action - Open the History page.">
+        Open the History page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_SEARCH" translateable="false" desc="Label for accelerator action - Place focus in search address bar.">
+        Place focus in search address bar
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_MENU_BAR" translateable="false" desc="Label for accelerator action - Place focus on menu bar.">
+        Place focus on menu bar
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_PRINT" translateable="false" desc="Label for accelerator action - Print your current page.">
+        Print your current page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD" translateable="false" desc="Label for accelerator action - Reload your current page.">
+        Reload your current page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD_BYPASSING_CACHE" translateable="false" desc="Label for accelerator action - Reload your current page without using cached content.">
+        Reload your current page without using cached content
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_NORMAL" translateable="false" desc="Label for accelerator action - Reset zoom level on the page.">
+        Reset zoom level on the page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_BOOKMARK_ALL_TABS" translateable="false" desc="Label for accelerator action - Save all open pages in your current window as bookmarks in a new folder.">
+        Save all open pages in your current window as bookmarks in a new folder
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SAVE_PAGE" translateable="false" desc="Label for accelerator action - Save your current page.">
+        Save your current page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_BOOKMARK_THIS_TAB" translateable="false" desc="Label for accelerator action - Save your current tab as a bookmark.">
+        Save your current tab as a bookmark
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_APP_MENU" translateable="false" desc="Label for accelerator action - Show Chrome menu.">
+        Show Chrome menu
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_BOOKMARK_MANAGER" translateable="false" desc="Label for accelerator action - Show bookmark manager.">
+        Show bookmark manager
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS_CONSOLE" translateable="false" desc="Label for accelerator action - Show or hide the Developer Tools console.">
+        Show or hide the Developer Tools console
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS_INSPECT" translateable="false" desc="Label for accelerator action - Show or hide the Developer Tools inspector.">
+        Show or hide the Developer Tools inspector
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_DEV_TOOLS" translateable="false" desc="Label for accelerator action - Show or hide the Developer Tools panel.">
+        Show or hide the Developer Tools panel
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_BOOKMARK_BAR" translateable="false" desc="Label for accelerator action - Show or hide the bookmarks bar.">
+        Show or hide the bookmarks bar
+      </message>
+       <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_VIEW_SOURCE" translateable="false" desc="Label for accelerator action - View page source.">
+        View page source
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_PLUS" translateable="false" desc="Label for accelerator action - Zoom in on the page.">
+        Zoom in on the page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_ZOOM_MINUS" translateable="false" desc="Label for accelerator action - Zoom out on the page.">
+        Zoom out on the page
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_LOCATION" translateable="false" desc="Label for accelerator action - Focus address bar.">
+        Focus address bar
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_TOOLBAR" translateable="false" desc="Label for accelerator action - Highlight the row with the address bar.">
+        Highlight the row with the address bar
+      </message>
+      <message name="IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY" translateable="false" desc="Label for accelerator action - Move focus to popups and dialogs.">
+        Move focus to popups and dialogs
+      </message>
 
       <!-- Tray scale strings -->
       <message name="IDS_ASH_STATUS_TRAY_SCALE" desc="The label used in scale setting detailed page of ash tray popup.">
@@ -4511,10 +4646,10 @@
 
       <!-- Strings for camera privacy hub switch notifications -->
       <message name="IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE" desc="Title for a notification shown to the users when they disable camera via the hardware switch.">
-        Do you want to turn off the camera access on this device?
+        Turn off camera access?
       </message>
       <message name="IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE" desc="Message for a notification shown to the users when they disable camera via the hardware switch.">
-        Turn off the camera access on this device to block all cameras all the time.
+        You used the physical switch to turn off 1 camera. You can also turn off access for all cameras.
       </message>
       <message name="IDS_PRIVACY_HUB_CAMERA_OFF_NOTIFICATION_TITLE" desc="Title for a notification shown to the users when an app tries to use the camera while the camera is disabled.">
         Turn on camera access?
@@ -5772,6 +5907,17 @@
       <message name="IDS_GLANCEABLES_UP_NEXT_EVENT_EMPTY_TITLE" desc="Placeholder text for calendar events without title.">
         (No title)
       </message>
+
+      <!-- Do not disturb notification -->
+      <message name="IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE" desc="Label used for the notification that shows up when the 'Do not disturb' feature is enabled.">
+        Do not disturb is on
+      </message>
+      <message name="IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_DESCRIPTION" desc="Text description used for the notification that shows up when the 'Do not disturb' feature is enabled.">
+        Notifications are muted when Do not disturb is on
+      </message>
+      <message name="IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TURN_OFF" desc="Label for a button on the 'Do not disturb' notification that turns off 'Do not disturb' mode.">
+        Turn off
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_DESCRIPTION.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..9d32d3e
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+870c2310637561084a103d5e4643c3e7037a0fe9
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE.png.sha1
new file mode 100644
index 0000000..573c90b7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@
+54804d65018679364feefca132a93cdfef1408d1
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TURN_OFF.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TURN_OFF.png.sha1
new file mode 100644
index 0000000..eb1e2489
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TURN_OFF.png.sha1
@@ -0,0 +1 @@
+9917953c4a5c4f954bcf2482fc4bfffc24e76134
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED.png.sha1 b/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED.png.sha1
new file mode 100644
index 0000000..04aba7b1
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED.png.sha1
@@ -0,0 +1 @@
+df6ed8e7916a851e2595509e8a428088646a04e8
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED_BY_APP.png.sha1 b/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED_BY_APP.png.sha1
new file mode 100644
index 0000000..e848fc0
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED_BY_APP.png.sha1
@@ -0,0 +1 @@
+e84377266b2457c3d8d9312407feb1d879accbb8
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE.png.sha1
new file mode 100644
index 0000000..99752ab
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+ba9e1fd2ba582ae131af0268b7e356fb5aadd0f6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE.png.sha1
new file mode 100644
index 0000000..99752ab
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE.png.sha1
@@ -0,0 +1 @@
+ba9e1fd2ba582ae131af0268b7e356fb5aadd0f6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE.png.sha1 b/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE.png.sha1
index b2f55d7..121e949 100644
--- a/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE.png.sha1
+++ b/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_MESSAGE.png.sha1
@@ -1 +1 @@
-6cee0333e8cfbb50d113cac4fe6624d51eeaa17f
\ No newline at end of file
+86a191ccdd86cd358d20e92cec1bf4e2409dae12
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE.png.sha1 b/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE.png.sha1
index b2f55d7..121e949 100644
--- a/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE.png.sha1
+++ b/ash/ash_strings_grd/IDS_PRIVACY_HUB_WANT_TO_TURN_OFF_CAMERA_NOTIFICATION_TITLE.png.sha1
@@ -1 +1 @@
-6cee0333e8cfbb50d113cac4fe6624d51eeaa17f
\ No newline at end of file
+86a191ccdd86cd358d20e92cec1bf4e2409dae12
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_test_util.cc b/ash/capture_mode/capture_mode_test_util.cc
index 3d47cc75..9ca8b64 100644
--- a/ash/capture_mode/capture_mode_test_util.cc
+++ b/ash/capture_mode/capture_mode_test_util.cc
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/projector/projector_controller.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
 #include "ash/public/cpp/projector/projector_session.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "ash/shell.h"
 #include "ash/style/icon_button.h"
 #include "ash/wm/cursor_manager_chromeos.h"
@@ -193,8 +194,9 @@
           []() { ProjectorController::Get()->OnSpeechRecognitionStopped(); }));
 
   // Simulate the availability of speech recognition.
-  projector_controller->OnSpeechRecognitionAvailabilityChanged(
-      SpeechRecognitionAvailability::kAvailable);
+  ON_CALL(projector_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(
+          testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
   EXPECT_CALL(projector_client_, IsDriveFsMounted())
       .WillRepeatedly(testing::Return(true));
 }
diff --git a/ash/constants/notifier_catalogs.h b/ash/constants/notifier_catalogs.h
index 231b0f4..fed308a1 100644
--- a/ash/constants/notifier_catalogs.h
+++ b/ash/constants/notifier_catalogs.h
@@ -172,7 +172,8 @@
   kPrivacyHubMicAndCamera = 157,
   kArcVmDataMigration = 158,
   kWebHid = 159,
-  kMaxValue = kWebHid
+  kDoNotDisturb = 160,
+  kMaxValue = kDoNotDisturb,
 };
 
 // A living catalog that registers system nudges.
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index cad97a7..a46cd55 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -16,12 +16,12 @@
 #include "ash/public/cpp/projector/projector_client.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
 #include "ash/public/cpp/projector/projector_session.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
 #include "base/bind.h"
 #include "base/check.h"
-#include "base/containers/fixed_flat_set.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted_memory.h"
@@ -32,7 +32,6 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "media/mojo/mojom/speech_recognition_service.mojom.h"
-#include "third_party/icu/source/common/unicode/locid.h"
 #include "ui/gfx/image/image.h"
 
 namespace ash {
@@ -84,30 +83,6 @@
   return gfx::Image(image_skia).As1xPNGBytes();
 }
 
-// TODO(b/245613717): Move this to ServerBasedRecognitionRecognizerClient.
-bool IsServerBasedRecognizerAvailable(const std::string& language_or_locale) {
-  if (!ash::features::IsInternalServerSideSpeechRecognitionEnabled()) {
-    return false;
-  }
-
-  static constexpr auto kSupportedLocales =
-      base::MakeFixedFlatSet<base::StringPiece>(
-          {"ar-x-maghrebi", "cmn-hant-tw", "cs-cz", "da-dk", "de-de", "en-au",
-           "en-gb",         "en-in",       "en-us", "es-es", "es-us", "fi-fi",
-           "fr-fr",         "hi-in",       "id-id", "it-it", "ja-jp", "ko-kr",
-           "nl-nl",         "pt-br",       "ru-ru", "sv-se", "tr-tr", "vi-vn"});
-
-  // The locals that we get come from ui/base/l10n/l10n_util.cc and can
-  // therefore just be language names.
-  static constexpr auto kDefaultLanguages =
-      base::MakeFixedFlatSet<base::StringPiece>(
-          {"cs", "da", "de", "en", "es", "fi", "fr", "hi", "id", "it", "ja",
-           "ko", "nl", "pt", "ru", "sv", "tr", "vi"});
-
-  return kSupportedLocales.contains(base::ToLowerASCII(language_or_locale)) ||
-         kDefaultLanguages.contains(base::ToLowerASCII(language_or_locale));
-}
-
 }  // namespace
 
 ProjectorControllerImpl::ProjectorControllerImpl()
@@ -181,16 +156,10 @@
   client_ = client;
 }
 
-void ProjectorControllerImpl::OnSpeechRecognitionAvailabilityChanged(
-    SpeechRecognitionAvailability availability) {
+void ProjectorControllerImpl::OnSpeechRecognitionAvailabilityChanged() {
   if (ProjectorController::AreExtendedProjectorFeaturesDisabled())
     return;
 
-  if (availability == on_device_speech_recognition_availability_)
-    return;
-
-  on_device_speech_recognition_availability_ = availability;
-
   OnNewScreencastPreconditionChanged();
 }
 
@@ -233,54 +202,45 @@
   // For development purposes on the x11 simulator, on-device speech recognition
   // and DriveFS are not supported.
   if (!ProjectorController::AreExtendedProjectorFeaturesDisabled()) {
-    // TODO(b/245613717): Use the appropriate fallback mechanism for using
-    // server based recognizer vs on-device based recognizer.
-    if (ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev()) {
-      auto locale = icu::Locale::getDefault();
-      if (IsServerBasedRecognizerAvailable(locale.getLanguage())) {
+    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};
-      } else {
+        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;
-      }
-    } else {
-      switch (on_device_speech_recognition_availability_) {
-        case SpeechRecognitionAvailability::
-            kOnDeviceSpeechRecognitionNotSupported:
-          result.state = NewScreencastPreconditionState::kDisabled;
-          result.reasons = {NewScreencastPreconditionReason::
-                                kOnDeviceSpeechRecognitionNotSupported};
-          return result;
-        case SpeechRecognitionAvailability::kUserLanguageNotSupported:
-          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::kSodaInstallationErrorUnspecified:
-          result.state = NewScreencastPreconditionState::kDisabled;
-          result.reasons = {NewScreencastPreconditionReason::
-                                kSodaInstallationErrorUnspecified};
-          return result;
-        case SpeechRecognitionAvailability::kSodaInstallationErrorNeedsReboot:
-          result.state = NewScreencastPreconditionState::kDisabled;
-          result.reasons = {NewScreencastPreconditionReason::
-                                kSodaInstallationErrorNeedsReboot};
-          return result;
-        case SpeechRecognitionAvailability::kAvailable:
-          break;
-      }
+      // 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;
     }
 
     if (!client_->IsDriveFsMounted()) {
@@ -496,9 +456,8 @@
   if (ProjectorController::AreExtendedProjectorFeaturesDisabled() || !client_)
     return;
 
-  DCHECK(on_device_speech_recognition_availability_ ==
-             SpeechRecognitionAvailability::kAvailable ||
-         ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev());
+  DCHECK(ProjectorController::IsRecognitionAvailable(
+      client_->GetSpeechRecognitionAvailability()));
 
   DCHECK(!is_speech_recognition_on_);
   client_->StartSpeechRecognition();
@@ -511,9 +470,10 @@
     OnSpeechRecognitionStopped();
     return;
   }
-  DCHECK(on_device_speech_recognition_availability_ ==
-             SpeechRecognitionAvailability::kAvailable ||
-         ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev());
+
+  DCHECK(ProjectorController::IsRecognitionAvailable(
+      client_->GetSpeechRecognitionAvailability()));
+
   client_->StopSpeechRecognition();
   is_speech_recognition_on_ = false;
 }
diff --git a/ash/projector/projector_controller_impl.h b/ash/projector/projector_controller_impl.h
index 24acb00f..3990484e 100644
--- a/ash/projector/projector_controller_impl.h
+++ b/ash/projector/projector_controller_impl.h
@@ -68,8 +68,7 @@
   // ProjectorController:
   void StartProjectorSession(const std::string& storage_dir) override;
   void SetClient(ProjectorClient* client) override;
-  void OnSpeechRecognitionAvailabilityChanged(
-      SpeechRecognitionAvailability availability) override;
+  void OnSpeechRecognitionAvailabilityChanged() override;
   void OnTranscription(const media::SpeechRecognitionResult& result) override;
   void OnTranscriptionError() override;
   void OnSpeechRecognitionStopped() override;
@@ -196,10 +195,6 @@
   std::unique_ptr<ProjectorUiController> ui_controller_;
   std::unique_ptr<ProjectorMetadataController> metadata_controller_;
 
-  // Whether SODA is available on the device.
-  SpeechRecognitionAvailability on_device_speech_recognition_availability_ =
-      SpeechRecognitionAvailability::kOnDeviceSpeechRecognitionNotSupported;
-
   // Whether speech recognition is taking place or not.
   bool is_speech_recognition_on_ = false;
 
diff --git a/ash/projector/projector_controller_unittest.cc b/ash/projector/projector_controller_unittest.cc
index 4fe0d0c..bc68c11 100644
--- a/ash/projector/projector_controller_unittest.cc
+++ b/ash/projector/projector_controller_unittest.cc
@@ -17,6 +17,7 @@
 #include "ash/projector/test/mock_projector_ui_controller.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
 #include "ash/public/cpp/projector/projector_session.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "ash/public/cpp/test/mock_projector_client.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
@@ -150,10 +151,10 @@
     mock_metadata_controller_ = mock_metadata_controller.get();
     controller_->SetProjectorMetadataControllerForTest(
         std::move(mock_metadata_controller));
-
+    ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+        .WillByDefault(
+            testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
     controller_->SetClient(&mock_client_);
-    controller_->OnSpeechRecognitionAvailabilityChanged(
-        SpeechRecognitionAvailability::kAvailable);
   }
 
   void InitializeRealMetadataController() {
@@ -242,8 +243,11 @@
                   NewScreencastPreconditionState::kDisabled,
                   {NewScreencastPreconditionReason::
                        kOnDeviceSpeechRecognitionNotSupported})));
-  controller_->OnSpeechRecognitionAvailabilityChanged(
-      SpeechRecognitionAvailability::kOnDeviceSpeechRecognitionNotSupported);
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(
+          testing::Return(SpeechRecognitionAvailability::kSodaNotAvailable));
+
+  controller_->OnSpeechRecognitionAvailabilityChanged();
 
   ON_CALL(mock_client_, IsDriveFsMounted())
       .WillByDefault(testing::Return(true));
@@ -252,8 +256,22 @@
               OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                   NewScreencastPreconditionState::kEnabled,
                   {NewScreencastPreconditionReason::kEnabledBySoda})));
-  controller_->OnSpeechRecognitionAvailabilityChanged(
-      SpeechRecognitionAvailability::kAvailable);
+
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(
+          testing::Return(SpeechRecognitionAvailability::kSodaAvailable));
+
+  controller_->OnSpeechRecognitionAvailabilityChanged();
+
+  EXPECT_CALL(mock_client_,
+              OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
+                  NewScreencastPreconditionState::kEnabled,
+                  {NewScreencastPreconditionReason::
+                       kEnabledByServerSideSpeechRecognition})));
+  ON_CALL(mock_client_, GetSpeechRecognitionAvailability)
+      .WillByDefault(testing::Return(
+          SpeechRecognitionAvailability::kServerBasedRecognitionAvailable));
+  controller_->OnSpeechRecognitionAvailabilityChanged();
 }
 
 TEST_F(ProjectorControllerTest, EnableAnnotatorTool) {
@@ -574,48 +592,4 @@
   run_loop.Run();
 }
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
-class ProjectorControllerTestServerBased : public ProjectorControllerTest {
- public:
-  ProjectorControllerTestServerBased() = default;
-  ProjectorControllerTestServerBased(
-      const ProjectorControllerTestServerBased&) = delete;
-  ProjectorControllerTestServerBased& operator=(
-      const ProjectorControllerTestServerBased&) = delete;
-  ~ProjectorControllerTestServerBased() override = default;
-
-  void InitFeatureFlags() override {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kProjector, features::kProjectorAnnotator,
-         features::kForceEnableServerSideSpeechRecognitionForDev},
-        {});
-  }
-};
-
-TEST_F(ProjectorControllerTestServerBased, GetNewScreencastPrecondition) {
-  InitFakeMic(/*mic_present=*/true);
-  ON_CALL(mock_client_, IsDriveFsMountFailed())
-      .WillByDefault(testing::Return(false));
-  ON_CALL(mock_client_, IsDriveFsMounted())
-      .WillByDefault(testing::Return(true));
-  UErrorCode error_code = U_ZERO_ERROR;
-
-  icu::Locale::setDefault(icu::Locale::getUS(), error_code);
-  auto precondition = controller_->GetNewScreencastPrecondition();
-  EXPECT_EQ(precondition, NewScreencastPrecondition(
-                              NewScreencastPreconditionState::kEnabled,
-                              {NewScreencastPreconditionReason::
-                                   kEnabledByServerSideSpeechRecognition}));
-
-  icu::Locale::setDefault(icu::Locale::getRoot(), error_code);
-  precondition = controller_->GetNewScreencastPrecondition();
-  EXPECT_EQ(precondition,
-            NewScreencastPrecondition(
-                NewScreencastPreconditionState::kDisabled,
-                {NewScreencastPreconditionReason::kUserLocaleNotSupported}));
-}
-
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
 }  // namespace ash
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 8418325..e9aa92d 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.h",
     "reauth_reason.h",
     "resize_shadow_type.h",
     "rounded_corner_utils.cc",
diff --git a/ash/public/cpp/app_list/app_list_color_provider.h b/ash/public/cpp/app_list/app_list_color_provider.h
index 818c6e4..093267e 100644
--- a/ash/public/cpp/app_list/app_list_color_provider.h
+++ b/ash/public/cpp/app_list/app_list_color_provider.h
@@ -18,22 +18,10 @@
   // Returns the singleton instance.
   static AppListColorProvider* Get();
 
-  virtual SkColor GetSearchBoxBackgroundColor(
-      const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetSearchBoxTextColor(
-      const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetSearchBoxSecondaryTextColor(
-      const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetSearchBoxSuggestionTextColor(
-      const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetAppListItemTextColor(
       const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetSearchBoxIconColor(
-      const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetSearchBoxCardBackgroundColor(
-      const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetFolderBackgroundColor(
       const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetFolderTitleTextColor(
diff --git a/ash/public/cpp/projector/projector_client.h b/ash/public/cpp/projector/projector_client.h
index b49d39c..94e1973 100644
--- a/ash/public/cpp/projector/projector_client.h
+++ b/ash/public/cpp/projector/projector_client.h
@@ -16,6 +16,7 @@
 namespace ash {
 
 struct NewScreencastPrecondition;
+enum class SpeechRecognitionAvailability;
 
 // Creates interface to access Browser side functionalities for the
 // ProjectorControllerImpl.
@@ -28,6 +29,8 @@
   ProjectorClient& operator=(const ProjectorClient&) = delete;
   virtual ~ProjectorClient();
 
+  virtual SpeechRecognitionAvailability GetSpeechRecognitionAvailability()
+      const = 0;
   virtual void StartSpeechRecognition() = 0;
   virtual void StopSpeechRecognition() = 0;
   // Returns false if base storage path is not available. Normally the base path
diff --git a/ash/public/cpp/projector/projector_controller.cc b/ash/public/cpp/projector/projector_controller.cc
index 3f431339..a8a4357 100644
--- a/ash/public/cpp/projector/projector_controller.cc
+++ b/ash/public/cpp/projector/projector_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/public/cpp/projector/projector_controller.h"
 
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
 
@@ -30,6 +31,14 @@
 }
 
 // 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 9ffb16c..7023618 100644
--- a/ash/public/cpp/projector/projector_controller.h
+++ b/ash/public/cpp/projector/projector_controller.h
@@ -15,24 +15,7 @@
 
 class ProjectorClient;
 
-// Enum class used to notify the ProjectorController on the availability of
-// speech recognition.
-enum class ASH_PUBLIC_EXPORT SpeechRecognitionAvailability {
-  // Device does not support SODA (Speech on Device API)
-  kOnDeviceSpeechRecognitionNotSupported,
-  // User's language is not supported by SODA.
-  kUserLanguageNotSupported,
-  // SODA binary is not yet installed.
-  kSodaNotInstalled,
-  // SODA binary and language packs are downloading.
-  kSodaInstalling,
-  // SODA installation failed.
-  kSodaInstallationErrorUnspecified,
-  // SODA installation error needs reboot
-  kSodaInstallationErrorNeedsReboot,
-  // SODA is available to be used.
-  kAvailable
-};
+enum class SpeechRecognitionAvailability;
 
 // Interface to control projector in ash.
 class ASH_PUBLIC_EXPORT ProjectorController {
@@ -42,6 +25,9 @@
   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
@@ -57,9 +43,8 @@
   // ProjectorController.
   virtual void SetClient(ProjectorClient* client) = 0;
 
-  // Called when speech recognition using SODA is available.
-  virtual void OnSpeechRecognitionAvailabilityChanged(
-      SpeechRecognitionAvailability availability) = 0;
+  // Called when speech recognition availability changes.
+  virtual void OnSpeechRecognitionAvailabilityChanged() = 0;
 
   // Called when transcription result from mic input is ready.
   virtual void OnTranscription(
diff --git a/ash/public/cpp/projector/speech_recognition_availability.h b/ash/public/cpp/projector/speech_recognition_availability.h
new file mode 100644
index 0000000..1813530
--- /dev/null
+++ b/ash/public/cpp/projector/speech_recognition_availability.h
@@ -0,0 +1,36 @@
+// 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_PUBLIC_CPP_PROJECTOR_SPEECH_RECOGNITION_AVAILABILITY_H_
+#define ASH_PUBLIC_CPP_PROJECTOR_SPEECH_RECOGNITION_AVAILABILITY_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+
+namespace ash {
+
+// Enum class used to represent the availability of speech recognition.
+enum class ASH_PUBLIC_EXPORT SpeechRecognitionAvailability {
+  // Device does not support SODA (Speech on Device API)
+  kSodaNotAvailable,
+  // Server based feature is not supported.
+  kServerBasedRecognitionNotAvailable,
+  // User's language is not supported.
+  kUserLanguageNotAvailable,
+  // SODA binary is not yet installed.
+  kSodaNotInstalled,
+  // SODA binary and language packs are downloading.
+  kSodaInstalling,
+  // SODA installation failed.
+  kSodaInstallationErrorUnspecified,
+  // SODA installation error needs reboot
+  kSodaInstallationErrorNeedsReboot,
+  // SODA is available to be used.
+  kSodaAvailable,
+  // Server based recognition is available.
+  kServerBasedRecognitionAvailable
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_PROJECTOR_SPEECH_RECOGNITION_AVAILABILITY_H_
diff --git a/ash/public/cpp/resources/ash_public_unscaled_resources.grd b/ash/public/cpp/resources/ash_public_unscaled_resources.grd
index 2bfed50d..f700c636 100644
--- a/ash/public/cpp/resources/ash_public_unscaled_resources.grd
+++ b/ash/public/cpp/resources/ash_public_unscaled_resources.grd
@@ -29,6 +29,9 @@
       <include name="IDR_SETTINGS_RGB_KEYBOARD_RAINBOW_COLOR_48_PNG" file="unscaled_resources/rgb_keyboard_rainbow_color_48.png" type="BINDATA" />
       <!-- Accessibility animations -->
       <include name="IDR_DICTATION_BUBBLE_ANIMATION" file="unscaled_resources/dictation_bubble_animation.json" type="BINDATA"/>
+      <!-- System tray Cast images -->
+      <include name="IDR_TRAY_CAST_ZERO_STATE_DARK" file="unscaled_resources/tray_cast_zero_state_dark.png" type="BINDATA" />
+      <include name="IDR_TRAY_CAST_ZERO_STATE_LIGHT" file="unscaled_resources/tray_cast_zero_state_light.png" type="BINDATA" />
     </includes>
     <structures>
       <structure type="lottie" name="IDR_PHONE_HUB_ONBOARDING_IMAGE" file="unscaled_resources/phone_hub_onboarding_image.json" compress="gzip" />
diff --git a/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_dark.png b/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_dark.png
new file mode 100644
index 0000000..f08de3a
--- /dev/null
+++ b/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_dark.png
Binary files differ
diff --git a/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_light.png b/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_light.png
new file mode 100644
index 0000000..95a9881c
--- /dev/null
+++ b/ash/public/cpp/resources/unscaled_resources/tray_cast_zero_state_light.png
Binary files differ
diff --git a/ash/public/cpp/test/mock_projector_client.h b/ash/public/cpp/test/mock_projector_client.h
index 3a5fc50..be470de4 100644
--- a/ash/public/cpp/test/mock_projector_client.h
+++ b/ash/public/cpp/test/mock_projector_client.h
@@ -28,6 +28,8 @@
   ~MockProjectorClient() override;
 
   // ProjectorClient:
+  MOCK_CONST_METHOD0(GetSpeechRecognitionAvailability,
+                     SpeechRecognitionAvailability());
   MOCK_METHOD0(StartSpeechRecognition, void());
   MOCK_METHOD0(StopSpeechRecognition, void());
   bool GetBaseStoragePath(base::FilePath* result) const override;
diff --git a/ash/public/cpp/test/mock_projector_controller.h b/ash/public/cpp/test/mock_projector_controller.h
index e1b5e30..aa955ee9 100644
--- a/ash/public/cpp/test/mock_projector_controller.h
+++ b/ash/public/cpp/test/mock_projector_controller.h
@@ -21,8 +21,7 @@
   // ProjectorController:
   MOCK_METHOD1(StartProjectorSession, void(const std::string& storageDir));
   MOCK_METHOD1(SetClient, void(ash::ProjectorClient* client));
-  MOCK_METHOD1(OnSpeechRecognitionAvailabilityChanged,
-               void(SpeechRecognitionAvailability availability));
+  MOCK_METHOD0(OnSpeechRecognitionAvailabilityChanged, void());
   MOCK_METHOD1(OnTranscription,
                void(const media::SpeechRecognitionResult& result));
   MOCK_METHOD0(OnTranscriptionError, void());
diff --git a/ash/public/cpp/test/test_app_list_color_provider.cc b/ash/public/cpp/test/test_app_list_color_provider.cc
index 95c4060..543d0b6 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.cc
+++ b/ash/public/cpp/test/test_app_list_color_provider.cc
@@ -9,31 +9,6 @@
 
 namespace ash {
 
-SkColor TestAppListColorProvider::GetSearchBoxBackgroundColor(
-    const views::Widget* widget) const {
-  return gfx::kGoogleGrey900;
-}
-
-SkColor TestAppListColorProvider::GetSearchBoxCardBackgroundColor(
-    const views::Widget* app_list_widget) const {
-  return gfx::kGoogleGrey900;
-}
-
-SkColor TestAppListColorProvider::GetSearchBoxTextColor(
-    const views::Widget* widget) const {
-  return gfx::kGoogleGrey200;
-}
-
-SkColor TestAppListColorProvider::GetSearchBoxSecondaryTextColor(
-    const views::Widget* widget) const {
-  return gfx::kGoogleGrey500;
-}
-
-SkColor TestAppListColorProvider::GetSearchBoxSuggestionTextColor(
-    const views::Widget* widget) const {
-  return gfx::kGoogleGrey600;
-}
-
 SkColor TestAppListColorProvider::GetAppListItemTextColor(
     const views::Widget* app_list_widget) const {
   return gfx::kGoogleGrey200;
@@ -49,11 +24,6 @@
   return gfx::kGoogleGrey700;
 }
 
-SkColor TestAppListColorProvider::GetSearchBoxIconColor(
-    const views::Widget* app_list_widget) const {
-  return gfx::kGoogleGrey200;
-}
-
 SkColor TestAppListColorProvider::GetFolderTitleTextColor(
     const views::Widget* app_list_widget) const {
   return gfx::kGoogleGrey200;
diff --git a/ash/public/cpp/test/test_app_list_color_provider.h b/ash/public/cpp/test/test_app_list_color_provider.h
index 2d5d4397..097c564 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.h
+++ b/ash/public/cpp/test/test_app_list_color_provider.h
@@ -17,22 +17,10 @@
 
  public:
   // AppListColorProvider:
-  SkColor GetSearchBoxBackgroundColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxTextColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxSecondaryTextColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxSuggestionTextColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetAppListItemTextColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxIconColor(
-      const views::Widget* app_list_widget) const override;
-  SkColor GetSearchBoxCardBackgroundColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetFolderBackgroundColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetFolderTitleTextColor(
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller.h b/ash/public/cpp/wallpaper/wallpaper_controller.h
index 87a1f25b..1eb3b76 100644
--- a/ash/public/cpp/wallpaper/wallpaper_controller.h
+++ b/ash/public/cpp/wallpaper/wallpaper_controller.h
@@ -139,24 +139,13 @@
   // downloading the image if it is not on disk yet.
   // Sets wallpaper from the Chrome OS wallpaper picker. If the
   // wallpaper file corresponding to |url| already exists in local file system
-  // (i.e. |SetOnlineWallpaperFromData| was called earlier with the same |url|),
+  // (i.e. |SetOnlineWallpaper| was called earlier with the same |url|),
   // returns true and sets wallpaper for the user, otherwise returns false.
   // |params|: The parameters of the online wallpaper.
   // Responds with true if the wallpaper file exists in local file system.
   virtual void SetOnlineWallpaperIfExists(const OnlineWallpaperParams& params,
                                           SetWallpaperCallback callback) = 0;
 
-  // Sets wallpaper from the Chrome OS wallpaper picker and saves the wallpaper
-  // to local file system. After this, |SetOnlineWallpaperIfExists| will return
-  // true for the same |params.url|, so that there's no need to provide
-  // |image_data| when the same wallpaper needs to be set again or for another
-  // user. |params|: The parameters of the online wallpaper.
-  // Responds with true if the wallpaper is set successfully (i.e. no decoding
-  // error etc.).
-  virtual void SetOnlineWallpaperFromData(const OnlineWallpaperParams& params,
-                                          const std::string& image_data,
-                                          SetWallpaperCallback callback) = 0;
-
   // Sets the user's wallpaper to be the default wallpaper. Note: different user
   // types may have different default wallpapers.
   // |account_id|: The user's account id.
@@ -285,7 +274,7 @@
   virtual void RemovePolicyWallpaper(const AccountId& account_id) = 0;
 
   // Returns the urls of the wallpapers that exist in local file system (i.e.
-  // |SetOnlineWallpaperFromData| was called earlier). The url is used as id
+  // |SetOnlineWallpaper| was called earlier). The url is used as id
   // to identify which wallpapers are available to be set offline.
   using GetOfflineWallpaperListCallback =
       base::OnceCallback<void(const std::vector<std::string>&)>;
diff --git a/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.cc b/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.cc
index 56c385b..65f2ed8 100644
--- a/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.cc
+++ b/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.cc
@@ -11,14 +11,6 @@
   device::BluetoothAdapter::NotifyAdapterPoweredChanged(powered);
 }
 
-bool FakeBluetoothAdapter::IsPowered() const {
-  return is_bluetooth_powered_;
-}
-
-bool FakeBluetoothAdapter::IsPresent() const {
-  return is_bluetooth_present_;
-}
-
 void FakeBluetoothAdapter::SetBluetoothIsPowered(bool powered) {
   is_bluetooth_powered_ = powered;
   NotifyPoweredChanged(powered);
@@ -28,11 +20,6 @@
   is_bluetooth_present_ = present;
 }
 
-device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
-FakeBluetoothAdapter::GetLowEnergyScanSessionHardwareOffloadingStatus() {
-  return hardware_offloading_status_;
-}
-
 void FakeBluetoothAdapter::SetHardwareOffloadingStatus(
     device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
         hardware_offloading_status) {
@@ -41,5 +28,27 @@
       hardware_offloading_status);
 }
 
+bool FakeBluetoothAdapter::IsPowered() const {
+  return is_bluetooth_powered_;
+}
+
+bool FakeBluetoothAdapter::IsPresent() const {
+  return is_bluetooth_present_;
+}
+
+device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
+FakeBluetoothAdapter::GetLowEnergyScanSessionHardwareOffloadingStatus() {
+  return hardware_offloading_status_;
+}
+
+device::BluetoothDevice* FakeBluetoothAdapter::GetDevice(
+    const std::string& address) {
+  for (const auto& it : mock_devices_) {
+    if (it->GetAddress() == address)
+      return it.get();
+  }
+  return nullptr;
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h b/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h
index 1d64fc1..60876e6c 100644
--- a/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h
+++ b/ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h
@@ -16,21 +16,23 @@
  public:
   void NotifyPoweredChanged(bool powered);
 
-  bool IsPowered() const override;
-
-  bool IsPresent() const override;
-
   void SetBluetoothIsPowered(bool powered);
 
   void SetBluetoothIsPresent(bool present);
 
-  device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
-  GetLowEnergyScanSessionHardwareOffloadingStatus() override;
-
   void SetHardwareOffloadingStatus(
       device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
           hardware_offloading_status);
 
+  bool IsPowered() const override;
+
+  bool IsPresent() const override;
+
+  device::BluetoothAdapter::LowEnergyScanSessionHardwareOffloadingStatus
+  GetLowEnergyScanSessionHardwareOffloadingStatus() override;
+
+  device::BluetoothDevice* GetDevice(const std::string& address) override;
+
  private:
   ~FakeBluetoothAdapter() = default;
 
diff --git a/ash/quick_pair/pairing/BUILD.gn b/ash/quick_pair/pairing/BUILD.gn
index a7c85d8..f5fb2622 100644
--- a/ash/quick_pair/pairing/BUILD.gn
+++ b/ash/quick_pair/pairing/BUILD.gn
@@ -79,6 +79,8 @@
     "//ash/quick_pair/common",
     "//ash/quick_pair/fast_pair_handshake",
     "//ash/quick_pair/fast_pair_handshake:test_support",
+    "//ash/quick_pair/feature_status_tracker",
+    "//ash/quick_pair/feature_status_tracker:test_support",
     "//ash/quick_pair/message_stream",
     "//ash/quick_pair/message_stream:test_support",
     "//ash/quick_pair/proto:fastpair_proto",
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index 463f40e..7173e372 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -205,27 +205,6 @@
 
 FastPairPairerImpl::~FastPairPairerImpl() {
   adapter_->RemovePairingDelegate(this);
-  device::BluetoothDevice* bt_device = nullptr;
-  if (device_->classic_address())
-    bt_device = adapter_->GetDevice(device_->classic_address().value());
-  if (!bt_device) {
-    QP_LOG(VERBOSE)
-        << __func__
-        << ": No device found in destructor for failed pairing attempt.";
-    return;
-  }
-
-  if (!bt_device->IsPaired()) {
-    QP_LOG(INFO)
-        << __func__
-        << ": Cancelling pairing in destructor for failed pair attempt.";
-    bt_device->CancelPairing();
-    return;
-  }
-
-  QP_LOG(VERBOSE) << __func__
-                  << ": Not cancelling pairing in destructor for successful "
-                     "pair attempt.";
 }
 
 void FastPairPairerImpl::StartPairing() {
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.cc b/ash/quick_pair/pairing/pairer_broker_impl.cc
index 0cd5e1b..d7dd4951 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl.cc
@@ -27,6 +27,10 @@
 
 constexpr int kMaxFailureRetryCount = 3;
 
+// 1s delay after cancelling pairing was chosen to align with Android's Fast
+// Pair implementation.
+constexpr base::TimeDelta kCancelPairingRetryDelay = base::Seconds(1);
+
 }  // namespace
 
 namespace ash {
@@ -140,6 +144,11 @@
                << ", Failure Count = "
                << pair_failure_counts_[device->ble_address];
 
+  device::BluetoothDevice* bt_device = nullptr;
+  if (device->classic_address()) {
+    bt_device = adapter_->GetDevice(device->classic_address().value());
+  }
+
   if (pair_failure_counts_[device->ble_address] == kMaxFailureRetryCount) {
     QP_LOG(INFO) << __func__
                  << ": Reached max failure count. Notifying observers.";
@@ -147,11 +156,32 @@
       observer.OnPairFailure(device, failure);
     }
 
+    if (bt_device && !bt_device->IsPaired()) {
+      bt_device->CancelPairing();
+    }
+
     EraseHandshakeAndFromPairers(device);
     return;
   }
 
   fast_pair_pairers_.erase(device->ble_address);
+
+  if (bt_device && !bt_device->IsPaired()) {
+    QP_LOG(INFO)
+        << __func__
+        << ": Cancelling pairing and scheduling retry for failed pair attempt.";
+    bt_device->CancelPairing();
+
+    // Create a timer to wait |kCancelPairingRetryDelay| after cancelling
+    // pairing to retry the pairing attempt.
+    cancel_pairing_timer_.Start(
+        FROM_HERE, kCancelPairingRetryDelay,
+        base::BindOnce(&PairerBrokerImpl::PairFastPairDevice,
+                       base::Unretained(this), device));
+
+    return;
+  }
+
   PairFastPairDevice(device);
 }
 
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.h b/ash/quick_pair/pairing/pairer_broker_impl.h
index c4ea1f6..f53644d 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.h
+++ b/ash/quick_pair/pairing/pairer_broker_impl.h
@@ -17,6 +17,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/timer/timer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace device {
@@ -68,6 +69,10 @@
   scoped_refptr<device::BluetoothAdapter> adapter_;
   std::unique_ptr<FastPairUnpairHandler> fast_pair_unpair_handler_;
   base::ObserverList<Observer> observers_;
+
+  // Timer to provide a delay after cancelling pairing.
+  base::OneShotTimer cancel_pairing_timer_;
+
   base::WeakPtrFactory<PairerBrokerImpl> weak_pointer_factory_{this};
 };
 
diff --git a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
index c9c5c55d..2d84666 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
@@ -8,6 +8,7 @@
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h"
 #include "ash/test/ash_test_base.h"
@@ -26,6 +27,9 @@
 
 constexpr char kValidModelId[] = "718c17";
 constexpr char kTestDeviceAddress[] = "test_address";
+constexpr char kDeviceName[] = "test_device_name";
+constexpr char kBluetoothCanonicalizedAddress[] = "0C:0E:4C:C8:05:08";
+constexpr base::TimeDelta kCancelPairingRetryDelay = base::Seconds(1);
 
 const char kFastPairRetryCountMetricName[] =
     "Bluetooth.ChromeOS.FastPair.PairRetry.Count";
@@ -143,11 +147,13 @@
 
 class PairerBrokerImplTest : public AshTestBase, public PairerBroker::Observer {
  public:
+  PairerBrokerImplTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
   void SetUp() override {
     AshTestBase::SetUp();
 
-    adapter_ =
-        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+    adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
 
     device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
 
@@ -200,7 +206,8 @@
   bool device_pair_complete_ = false;
 
   base::HistogramTester histogram_tester_;
-  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
+  scoped_refptr<FakeBluetoothAdapter> adapter_;
+  device::MockBluetoothDevice* mock_bluetooth_device_ptr_ = nullptr;
   std::unique_ptr<FakeFastPairPairerFactory> fast_pair_pairer_factory_;
 
   std::unique_ptr<PairerBroker> pairer_broker_;
@@ -211,11 +218,9 @@
   auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
                                              Protocol::kFastPairInitial);
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(pairer_broker_->IsPairing());
   EXPECT_EQ(device_paired_count_, 1);
@@ -223,7 +228,6 @@
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairingProcedureCompleteCallback();
-  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(pairer_broker_->IsPairing());
 }
 
@@ -234,16 +238,13 @@
 
   pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairing_started_);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerHandshakeCompleteCallback();
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(handshake_complete_);
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(pairer_broker_->IsPairing());
   EXPECT_EQ(device_paired_count_, 1);
@@ -251,7 +252,6 @@
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairingProcedureCompleteCallback();
-  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(pairer_broker_->IsPairing());
   EXPECT_TRUE(device_pair_complete_);
 }
@@ -262,11 +262,9 @@
                                              Protocol::kFastPairRetroactive);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(pairer_broker_->IsPairing());
   EXPECT_EQ(device_paired_count_, 1);
@@ -274,7 +272,6 @@
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairingProcedureCompleteCallback();
-  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(pairer_broker_->IsPairing());
 }
 
@@ -285,38 +282,70 @@
 
   pairer_broker_->PairDevice(device);
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(pairer_broker_->IsPairing());
   EXPECT_EQ(device_paired_count_, 1);
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 1);
 }
 
+TEST_F(PairerBrokerImplTest, PairAfterCancelPairing) {
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
+  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
+                                             Protocol::kFastPairInitial);
+  device->set_classic_address(kBluetoothCanonicalizedAddress);
+
+  // Add a matching mock device to the bluetooth adapter with the
+  // same address to mock the relationship between Device and
+  // device::BluetoothDevice.
+  std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+      mock_bluetooth_device_ =
+          std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
+              adapter_.get(), 0, kDeviceName, kBluetoothCanonicalizedAddress,
+              true, false);
+  mock_bluetooth_device_ptr_ = mock_bluetooth_device_.get();
+  adapter_->AddMockDevice(std::move(mock_bluetooth_device_));
+
+  pairer_broker_->PairDevice(device);
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+  EXPECT_CALL(*mock_bluetooth_device_ptr_, IsPaired())
+      .WillOnce(testing::Return(false));
+
+  // Attempt to pair with a failure.
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairFailureCallback(
+          PairFailure::kPasskeyCharacteristicNotifySession);
+
+  // Fast forward |kCancelPairingDelay| seconds to allow the retry callback to
+  // be called.
+  task_environment()->FastForwardBy(kCancelPairingRetryDelay);
+
+  // Now allow the pairing to succeed.
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_EQ(device_paired_count_, 1);
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 1);
+}
+
 TEST_F(PairerBrokerImplTest, PairDeviceFailureMax_Initial) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
   auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
                                              Protocol::kFastPairInitial);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(pairer_broker_->IsPairing());
   EXPECT_EQ(pair_failure_count_, 1);
@@ -329,20 +358,16 @@
                                              Protocol::kFastPairSubsequent);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(pairer_broker_->IsPairing());
   EXPECT_EQ(pair_failure_count_, 1);
@@ -355,20 +380,16 @@
                                              Protocol::kFastPairRetroactive);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
           PairFailure::kPasskeyCharacteristicNotifySession);
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(pairer_broker_->IsPairing());
   EXPECT_EQ(pair_failure_count_, 1);
@@ -380,7 +401,6 @@
                                              Protocol::kFastPairInitial);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -396,7 +416,6 @@
                                              Protocol::kFastPairSubsequent);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -412,7 +431,6 @@
                                              Protocol::kFastPairRetroactive);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -428,7 +446,6 @@
                                              Protocol::kFastPairInitial);
 
   pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   // Stop Pairing mid pair.
diff --git a/ash/search_box/BUILD.gn b/ash/search_box/BUILD.gn
index 7ccbf13..a407906 100644
--- a/ash/search_box/BUILD.gn
+++ b/ash/search_box/BUILD.gn
@@ -16,6 +16,7 @@
   deps = [
     "//ash/public/cpp",
     "//ash/strings",
+    "//ash/style",
     "//base",
     "//skia",
     "//ui/base",
diff --git a/ash/search_box/search_box_constants.h b/ash/search_box/search_box_constants.h
index 0a62885..ddae924 100644
--- a/ash/search_box/search_box_constants.h
+++ b/ash/search_box/search_box_constants.h
@@ -10,10 +10,6 @@
 
 namespace ash {
 
-// Default color used when wallpaper customized color is not available for
-// searchbox, #000 at 87% opacity.
-constexpr SkColor kDefaultSearchboxColor = gfx::kGoogleGrey200;
-
 // The horizontal padding of the box layout of the search box.
 constexpr int kSearchBoxPadding = 12;
 
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index a62e397..2a33df3 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/ash_typography.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "base/bind.h"
 #include "base/strings/strcat.h"
 #include "third_party/skia/include/core/SkPath.h"
@@ -84,6 +85,8 @@
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->GetViewAccessibility().OverrideIsIgnored(true);
   label->SetBackgroundColor(SK_ColorTRANSPARENT);
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetEnabledColorId(kColorAshTextColorSuggestion);
   label->SetVisible(true);
   label->SetElideBehavior(gfx::ELIDE_TAIL);
   label->SetMultiLine(false);
@@ -177,34 +180,25 @@
     SchedulePaint();
   }
 
-  void OnThemeChanged() override {
-    views::View::OnThemeChanged();
-    UpdateInkDropColors();
-  }
-
   void set_is_showing(bool is_showing) { is_showing_ = is_showing; }
   bool is_showing() { return is_showing_; }
 
+  void UpdateInkDropColors(SkColor background_color) {
+    const views::Widget* app_list_widget = GetWidget();
+    views::InkDrop::Get(this)->SetBaseColor(
+        AppListColorProvider::Get()->GetInkDropBaseColor(app_list_widget,
+                                                         background_color));
+    views::InkDrop::Get(this)->SetVisibleOpacity(
+        AppListColorProvider::Get()->GetInkDropOpacity(app_list_widget,
+                                                       background_color));
+  }
+
  private:
   int GetButtonRadius() const { return width() / 2; }
 
   // Whether the button is showing/shown or hiding/hidden.
   bool is_showing_ = false;
 
-  void UpdateInkDropColors() {
-    const views::Widget* app_list_widget = GetWidget();
-    SkColor search_box_card_background_color =
-        AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor(
-            app_list_widget);
-
-    views::InkDrop::Get(this)->SetBaseColor(
-        AppListColorProvider::Get()->GetInkDropBaseColor(
-            app_list_widget, search_box_card_background_color));
-    views::InkDrop::Get(this)->SetVisibleOpacity(
-        AppListColorProvider::Get()->GetInkDropOpacity(
-            app_list_widget, search_box_card_background_color));
-  }
-
   // views::View:
   void OnPaintBackground(gfx::Canvas* canvas) override {
     if (HasFocus()) {
@@ -617,26 +611,10 @@
 
 void SearchBoxViewBase::OnThemeChanged() {
   views::View::OnThemeChanged();
-  const views::Widget* app_list_widget = GetWidget();
-
   if (features::IsAutocompleteExtendedSuggestionsEnabled()) {
-    auto ghost_text_color =
-        AppListColorProvider::Get()->GetSearchBoxSuggestionTextColor(
-            app_list_widget);
-    autocomplete_ghost_text_->SetEnabledColor(ghost_text_color);
-    separator_label_->SetEnabledColor(ghost_text_color);
-    category_ghost_text_->SetEnabledColor(ghost_text_color);
-    category_separator_label_->SetEnabledColor(ghost_text_color);
     search_box_->SetSelectionBackgroundColor(
         AppListColorProvider::Get()->GetFolderNameSelectionColor(GetWidget()));
   }
-
-  auto* background = GetBackground();
-  if (background) {
-    background->SetNativeControlColor(
-        AppListColorProvider::Get()->GetSearchBoxBackgroundColor(
-            app_list_widget));
-  }
   UpdatePlaceholderTextStyle();
 }
 
@@ -797,11 +775,14 @@
   }
 }
 
-// TODO(crbug.com/755219): Unify this with SetBackgroundColor.
 void SearchBoxViewBase::UpdateBackgroundColor(SkColor color) {
   auto* search_box_background = background();
   if (search_box_background)
     search_box_background->SetNativeControlColor(color);
+  if (close_button_)
+    close_button_->UpdateInkDropColors(color);
+  if (assistant_button_)
+    assistant_button_->UpdateInkDropColors(color);
 }
 
 }  // namespace ash
diff --git a/ash/shortcut_viewer/views/ksv_search_box_view.cc b/ash/shortcut_viewer/views/ksv_search_box_view.cc
index 62b5dc4f..e652691 100644
--- a/ash/shortcut_viewer/views/ksv_search_box_view.cc
+++ b/ash/shortcut_viewer/views/ksv_search_box_view.cc
@@ -16,6 +16,7 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -158,17 +159,8 @@
 }
 
 SkColor KSVSearchBoxView::GetBackgroundColor() {
-  constexpr SkColor kBackgroundDarkColor =
-      SkColorSetARGB(0xFF, 0x32, 0x33, 0x34);
-
-  if (ShouldUseDarkThemeColors()) {
-    return kBackgroundDarkColor;
-  }
-
-  return ShouldUseFocusedColors()
-             ? gfx::kGoogleGrey100
-             : ash::AppListColorProvider::Get()->GetSearchBoxBackgroundColor(
-                   GetWidget());
+  return GetWidget()->GetColorProvider()->GetColor(
+      cros_tokens::kToolbarSearchBgColor);
 }
 
 SkColor KSVSearchBoxView::GetBorderColor() {
diff --git a/ash/style/icon_button.cc b/ash/style/icon_button.cc
index c206bd9..79b7275 100644
--- a/ash/style/icon_button.cc
+++ b/ash/style/icon_button.cc
@@ -25,6 +25,7 @@
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
+#include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/highlight_path_generator.h"
 
@@ -48,32 +49,80 @@
   switch (type) {
     case IconButton::Type::kXSmall:
     case IconButton::Type::kXSmallFloating:
+    case IconButton::Type::kXSmallProminentFloating:
       return kXSmallButtonSize;
     case IconButton::Type::kSmall:
     case IconButton::Type::kSmallFloating:
+    case IconButton::Type::kSmallProminentFloating:
       return kSmallButtonSize;
     case IconButton::Type::kMedium:
     case IconButton::Type::kMediumFloating:
+    case IconButton::Type::kMediumProminentFloating:
       return kMediumButtonSize;
     case IconButton::Type::kLarge:
     case IconButton::Type::kLargeFloating:
+    case IconButton::Type::kLargeProminentFloating:
       return kLargeButtonSize;
   }
 }
 
 int GetIconSizeOnType(IconButton::Type type) {
   if (type == IconButton::Type::kXSmall ||
-      type == IconButton::Type::kXSmallFloating) {
+      type == IconButton::Type::kXSmallFloating ||
+      type == IconButton::Type::kXSmallProminentFloating) {
     return kXSmallIconSize;
   }
   return kIconSize;
 }
 
 bool IsFloatingIconButton(IconButton::Type type) {
-  return type == IconButton::Type::kXSmallFloating ||
-         type == IconButton::Type::kSmallFloating ||
-         type == IconButton::Type::kMediumFloating ||
-         type == IconButton::Type::kLargeFloating;
+  switch (type) {
+    case IconButton::Type::kXSmallFloating:
+    case IconButton::Type::kXSmallProminentFloating:
+    case IconButton::Type::kSmallFloating:
+    case IconButton::Type::kSmallProminentFloating:
+    case IconButton::Type::kMediumFloating:
+    case IconButton::Type::kMediumProminentFloating:
+    case IconButton::Type::kLargeFloating:
+    case IconButton::Type::kLargeProminentFloating:
+      return true;
+    default:
+      break;
+  }
+
+  return false;
+}
+
+bool IsProminentFloatingType(IconButton::Type type) {
+  switch (type) {
+    case IconButton::Type::kXSmallProminentFloating:
+    case IconButton::Type::kSmallProminentFloating:
+    case IconButton::Type::kMediumProminentFloating:
+    case IconButton::Type::kLargeProminentFloating:
+      return true;
+    default:
+      break;
+  }
+
+  return false;
+}
+
+// Create a themed fully rounded rect background for icon button.
+std::unique_ptr<views::Background> CreateThemedBackground(
+    ui::ColorId color_id,
+    IconButton::Type type) {
+  return views::CreateThemedRoundedRectBackground(
+      color_id, GetButtonSizeOnType(type) / 2);
+}
+
+// Create a solid color fully rounded rect background for icon button.
+// TODO(zxdan): Remove this function when the dynamic color migration work is
+// done.
+std::unique_ptr<views::Background> CreateSolidBackground(
+    SkColor color,
+    IconButton::Type type) {
+  return views::CreateRoundedRectBackground(color,
+                                            GetButtonSizeOnType(type) / 2);
 }
 
 }  // namespace
@@ -107,6 +156,9 @@
                                    /*highlight_on_hover=*/false,
                                    /*highlight_on_focus=*/false);
 
+  UpdateBackground();
+  UpdateVectorIcon();
+
   auto* focus_ring = views::FocusRing::Get(this);
   focus_ring->SetColorId(ui::kColorAshFocusRing);
   if (has_border) {
@@ -118,6 +170,9 @@
   }
 
   views::InstallCircleHighlightPathGenerator(this);
+
+  enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
+      &IconButton::UpdateBackground, base::Unretained(this)));
 }
 
 IconButton::IconButton(PressedCallback callback,
@@ -147,7 +202,7 @@
 
 void IconButton::SetVectorIcon(const gfx::VectorIcon& icon) {
   icon_ = &icon;
-  UpdateVectorIcon();
+  UpdateVectorIcon(/*icon_changed=*/true);
 }
 
 void IconButton::SetToggledVectorIcon(const gfx::VectorIcon& icon) {
@@ -160,7 +215,10 @@
     return;
 
   background_color_ = background_color;
-  SchedulePaint();
+  background_color_id_ = absl::nullopt;
+
+  if (GetEnabled() && !IsToggledOn())
+    UpdateBackground();
 }
 
 void IconButton::SetBackgroundToggledColor(
@@ -169,7 +227,10 @@
     return;
 
   background_toggled_color_ = background_toggled_color;
-  SchedulePaint();
+  background_toggled_color_id_ = absl::nullopt;
+
+  if (GetEnabled() && IsToggledOn())
+    UpdateBackground();
 }
 
 void IconButton::SetBackgroundColorId(ui::ColorId background_color_id) {
@@ -177,7 +238,10 @@
     return;
 
   background_color_id_ = background_color_id;
-  SchedulePaint();
+  background_color_ = absl::nullopt;
+
+  if (GetEnabled() && !IsToggledOn())
+    UpdateBackground();
 }
 
 void IconButton::SetBackgroundToggledColorId(
@@ -188,7 +252,10 @@
   }
 
   background_toggled_color_id_ = background_toggled_color_id;
-  SchedulePaint();
+  background_toggled_color_ = absl::nullopt;
+
+  if (GetEnabled() && IsToggledOn())
+    UpdateBackground();
 }
 
 void IconButton::SetBackgroundImage(const gfx::ImageSkia& background_image) {
@@ -201,7 +268,10 @@
   if (icon_color_ == icon_color)
     return;
   icon_color_ = icon_color;
-  UpdateVectorIcon();
+  icon_color_id_ = absl::nullopt;
+
+  if (!IsToggledOn())
+    UpdateVectorIcon();
 }
 
 void IconButton::SetIconToggledColor(const SkColor icon_toggled_color) {
@@ -209,14 +279,21 @@
     return;
 
   icon_toggled_color_ = icon_toggled_color;
-  UpdateVectorIcon();
+  icon_toggled_color_id_ = absl::nullopt;
+
+  if (IsToggledOn())
+    UpdateVectorIcon();
 }
 
 void IconButton::SetIconColorId(ui::ColorId icon_color_id) {
   if (icon_color_id_ == icon_color_id)
     return;
+
   icon_color_id_ = icon_color_id;
-  UpdateVectorIcon();
+  icon_color_ = absl::nullopt;
+
+  if (!IsToggledOn())
+    UpdateVectorIcon();
 }
 
 void IconButton::SetIconToggledColorId(ui::ColorId icon_toggled_color_id) {
@@ -224,7 +301,10 @@
     return;
 
   icon_toggled_color_id_ = icon_toggled_color_id;
-  UpdateVectorIcon();
+  icon_toggled_color_ = absl::nullopt;
+
+  if (IsToggledOn())
+    UpdateVectorIcon();
 }
 
 void IconButton::SetIconSize(int size) {
@@ -243,31 +323,31 @@
   if (delegate_)
     delegate_->OnButtonToggled(this);
 
+  if (GetEnabled())
+    UpdateBackground();
+
   UpdateVectorIcon();
 }
 
+void IconButton::OnFocus() {
+  // Update prominent floating type button's icon color on focus.
+  if (IsProminentFloatingType(type_) && !IsToggledOn())
+    UpdateVectorIcon();
+}
+
+void IconButton::OnBlur() {
+  // Update prominent floating type button's icon color on blur.
+  if (IsProminentFloatingType(type_) && !IsToggledOn())
+    UpdateVectorIcon();
+}
+
 void IconButton::PaintButtonContents(gfx::Canvas* canvas) {
-  if (!GetWidget())
-    return;
-
   if (!IsFloatingIconButton(type_) || IsToggledOn()) {
-    const gfx::Rect rect(GetContentsBounds());
-    cc::PaintFlags flags;
-    flags.setAntiAlias(true);
-
-    SkColor color = GetBackgroundColor();
-
-    // If the button is disabled, apply opacity filter to the color.
-    if (!GetEnabled())
-      color = ColorUtil::GetDisabledColor(color);
-
-    flags.setColor(color);
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), rect.width() / 2,
-                       flags);
-
     // Apply the background image. This is painted on top of the |color|.
     if (!background_image_.isNull()) {
+      const gfx::Rect rect(GetContentsBounds());
+      cc::PaintFlags flags;
+      flags.setAntiAlias(true);
       SkPath mask;
       mask.addCircle(rect.CenterPoint().x(), rect.CenterPoint().y(),
                      rect.width() / 2);
@@ -290,12 +370,6 @@
   }
 }
 
-void IconButton::OnThemeChanged() {
-  views::ImageButton::OnThemeChanged();
-  UpdateVectorIcon();
-  SchedulePaint();
-}
-
 void IconButton::NotifyClick(const ui::Event& event) {
   if (is_togglable_) {
     haptics_util::PlayHapticToggleEffect(
@@ -308,94 +382,133 @@
   views::Button::NotifyClick(event);
 }
 
-void IconButton::UpdateVectorIcon() {
-  if (!icon_ || !GetWidget())
+void IconButton::UpdateBackground() {
+  // The untoggled floating button does not have a background.
+  const bool is_toggled = IsToggledOn();
+  if (IsFloatingIconButton(type_) && !is_toggled) {
+    SetBackground(nullptr);
+    return;
+  }
+
+  // Create a themed rounded rect background when the button is disabled.
+  if (!GetEnabled()) {
+    SetBackground(
+        CreateThemedBackground(cros_tokens::kCrosSysDisabledContainer, type_));
+    return;
+  }
+
+  // Create a themed rounded rect background when the background color is
+  // defined by a color ID. Otherwise, create a solid rounded rect background.
+  // TODO(zxdan): only use themed background when dynamic color migration work
+  // is done.
+
+  // When the button is toggled, create a background with toggled color.
+  const bool is_jellyroll_enabled = features::IsJellyrollEnabled();
+  if (is_toggled) {
+    if (background_toggled_color_id_ || !background_toggled_color_) {
+      const ui::ColorId color_id = background_toggled_color_id_.value_or(
+          is_jellyroll_enabled ? cros_tokens::kCrosSysSystemPrimaryContainer
+                               : static_cast<ui::ColorId>(
+                                     kColorAshControlBackgroundColorActive));
+      SetBackground(CreateThemedBackground(color_id, type_));
+      return;
+    }
+    SetBackground(
+        CreateSolidBackground(background_toggled_color_.value(), type_));
+    return;
+  }
+
+  // When the button is not toggled, create a background with normal color.
+  if (background_color_id_ || !background_color_) {
+    const ui::ColorId color_id = background_color_id_.value_or(
+        is_jellyroll_enabled ? cros_tokens::kCrosSysSystemOnBase
+                             : static_cast<ui::ColorId>(
+                                   kColorAshControlBackgroundColorInactive));
+    SetBackground(CreateThemedBackground(color_id, type_));
+    return;
+  }
+  SetBackground(CreateSolidBackground(background_color_.value(), type_));
+  return;
+}
+
+void IconButton::UpdateVectorIcon(bool icon_changed) {
+  const bool is_toggled = IsToggledOn();
+  const gfx::VectorIcon* icon =
+      is_toggled && toggled_icon_ ? toggled_icon_ : icon_;
+
+  if (!icon)
     return;
 
-  auto* color_provider = GetColorProvider();
+  const int icon_size = icon_size_.value_or(GetIconSizeOnType(type_));
   const bool is_jellyroll_enabled = features::IsJellyrollEnabled();
 
-  // The icon color IDs set by clients takes precedence over the icon colors. If
-  // neither is set, use the default color IDs.
-  SkColor normal_icon_color;
-  if (icon_color_id_) {
-    normal_icon_color = color_provider->GetColor(icon_color_id_.value());
+  ui::ImageModel new_normal_image_model;
+  // When the icon color is defined by a color Id, use the color Id to create an
+  // image model. Otherwise, use the color to create an image model.
+  // TODO(zxdan): only use color Id when the dynamic color migration work is
+  // done.
+  if (is_toggled) {
+    // When the button is toggled, create an image model with toggled color.
+    if (icon_toggled_color_id_ || !icon_toggled_color_) {
+      const ui::ColorId color_id = icon_toggled_color_id_.value_or(
+          is_jellyroll_enabled
+              ? cros_tokens::kCrosSysSystemOnPrimaryContainer
+              : static_cast<ui::ColorId>(kColorAshButtonIconColorPrimary));
+      new_normal_image_model =
+          ui::ImageModel::FromVectorIcon(*icon, color_id, icon_size);
+    } else {
+      new_normal_image_model = ui::ImageModel::FromVectorIcon(
+          *icon, icon_toggled_color_.value(), icon_size);
+    }
   } else {
-    normal_icon_color = icon_color_.value_or(color_provider->GetColor(
-        is_jellyroll_enabled
-            ? cros_tokens::kCrosSysOnSurface
-            : static_cast<ui::ColorId>(kColorAshButtonIconColor)));
+    // When the button is not toggled, create an image model with normal color.
+    if (icon_color_id_ || !icon_color_) {
+      ui::ColorId default_color_id;
+      if (IsProminentFloatingType(type_)) {
+        default_color_id = HasFocus() ? cros_tokens::kCrosSysPrimary
+                                      : cros_tokens::kCrosSysSecondary;
+      } else {
+        default_color_id =
+            is_jellyroll_enabled
+                ? cros_tokens::kCrosSysOnSurface
+                : static_cast<ui::ColorId>(kColorAshButtonIconColor);
+      }
+      const ui::ColorId color_id = icon_color_id_.value_or(default_color_id);
+      new_normal_image_model =
+          ui::ImageModel::FromVectorIcon(*icon, color_id, icon_size);
+    } else {
+      new_normal_image_model =
+          ui::ImageModel::FromVectorIcon(*icon, icon_color_.value(), icon_size);
+    }
   }
 
-  SkColor toggled_icon_color;
-  if (icon_toggled_color_id_) {
-    toggled_icon_color =
-        color_provider->GetColor(icon_toggled_color_id_.value());
-  } else {
-    toggled_icon_color = icon_toggled_color_.value_or(color_provider->GetColor(
-        is_jellyroll_enabled
-            ? cros_tokens::kCrosSysSystemOnPrimaryContainer
-            : static_cast<ui::ColorId>(kColorAshButtonIconColorPrimary)));
+  if (GetWidget()) {
+    // Skip repainting if the incoming icon is the same as the current icon. If
+    // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
+    // grab the ImageSkia from a cache, so it will be cheap. Note that this
+    // assumes that toggled/disabled images changes at the same time as the
+    // normal image, which it currently does.
+    const gfx::ImageSkia new_normal_image =
+        new_normal_image_model.Rasterize(GetColorProvider());
+    const gfx::ImageSkia& old_normal_image =
+        GetImage(views::Button::STATE_NORMAL);
+    if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
+        new_normal_image.BackedBySameObjectAs(old_normal_image)) {
+      return;
+    }
   }
 
-  const gfx::VectorIcon* icon =
-      toggled_ && toggled_icon_ ? toggled_icon_ : icon_;
-  const SkColor icon_color = toggled_ ? toggled_icon_color : normal_icon_color;
-  const int icon_size = icon_size_.value_or(GetIconSizeOnType(type_));
-
-  // Skip repainting if the incoming icon is the same as the current icon. If
-  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
-  // grab the ImageSkia from a cache, so it will be cheap. Note that this
-  // assumes that toggled/disabled images changes at the same time as the normal
-  // image, which it currently does.
-  const gfx::ImageSkia new_normal_image =
-      gfx::CreateVectorIcon(*icon, icon_size, icon_color);
-  const gfx::ImageSkia& old_normal_image =
-      GetImage(views::Button::STATE_NORMAL);
-  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
-      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
-    return;
+  SetImageModel(views::Button::STATE_NORMAL, new_normal_image_model);
+  if (icon_changed) {
+    SetImageModel(views::Button::STATE_DISABLED,
+                  ui::ImageModel::FromVectorIcon(
+                      *icon, cros_tokens::kCrosSysDisabled, icon_size));
   }
-
-  SetImage(views::Button::STATE_NORMAL, new_normal_image);
-  SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(*icon, icon_size,
-                            ColorUtil::GetDisabledColor(normal_icon_color)));
 }
 
 SkColor IconButton::GetBackgroundColor() const {
-  const bool is_jellyroll_enabled = features::IsJellyrollEnabled();
-  auto* color_provider = GetColorProvider();
-
-  // The background color IDs set by clients takes precedence over the
-  // background colors. If neither is set, use the default color IDs.
-  SkColor normal_background_color;
-  if (background_color_id_) {
-    normal_background_color =
-        color_provider->GetColor(background_color_id_.value());
-  } else {
-    normal_background_color =
-        background_color_.value_or(color_provider->GetColor(
-            is_jellyroll_enabled
-                ? cros_tokens::kCrosSysSystemOnBase
-                : static_cast<ui::ColorId>(
-                      kColorAshControlBackgroundColorInactive)));
-  }
-
-  SkColor toggled_background_color;
-  if (background_toggled_color_id_) {
-    toggled_background_color =
-        color_provider->GetColor(background_toggled_color_id_.value());
-  } else {
-    toggled_background_color =
-        background_toggled_color_.value_or(color_provider->GetColor(
-            is_jellyroll_enabled ? cros_tokens::kCrosSysSystemPrimaryContainer
-                                 : static_cast<ui::ColorId>(
-                                       kColorAshControlBackgroundColorActive)));
-  }
-
-  return IsToggledOn() ? toggled_background_color : normal_background_color;
+  DCHECK(background());
+  return background()->get_color();
 }
 
 bool IconButton::IsToggledOn() const {
diff --git a/ash/style/icon_button.h b/ash/style/icon_button.h
index 0121bbeb..79fd045 100644
--- a/ash/style/icon_button.h
+++ b/ash/style/icon_button.h
@@ -23,10 +23,12 @@
 
 // A circular ImageButton that can have small/medium/large different sizes. Each
 // of them has the floating version, which does not have the background. The
-// button can be togglable if `is_togglable` is set to true, the icon inside
-// might change on different toggle states. A fixed size of EmptyBorder will be
-// applied to the button if `has_border` is true, this is done to help
-// differentiating focus ring from the content of the button.
+// prominent-floating buttons have different icon colors when the button is
+// focused and unfocused. The button can be togglable if `is_togglable` is set
+// to true, the icon inside might change on different toggle states. A fixed
+// size of EmptyBorder will be applied to the button if `has_border` is true,
+// this is done to help differentiating focus ring from the content of the
+// button.
 class ASH_EXPORT IconButton : public views::ImageButton {
  public:
   METADATA_HEADER(IconButton);
@@ -39,7 +41,11 @@
     kXSmallFloating,
     kSmallFloating,
     kMediumFloating,
-    kLargeFloating
+    kLargeFloating,
+    kXSmallProminentFloating,
+    kSmallProminentFloating,
+    kMediumProminentFloating,
+    kLargeProminentFloating,
   };
 
   // Used to determine how the button will behave when disabled.
@@ -135,13 +141,15 @@
   void SetToggled(bool toggled);
 
   // views::ImageButton:
+  void OnFocus() override;
+  void OnBlur() override;
   void PaintButtonContents(gfx::Canvas* canvas) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  void OnThemeChanged() override;
   void NotifyClick(const ui::Event& event) override;
 
  protected:
-  void UpdateVectorIcon();
+  void UpdateBackground();
+  void UpdateVectorIcon(bool icon_changed = false);
 
   // Gets the background color of the icon button.
   SkColor GetBackgroundColor() const;
@@ -182,6 +190,9 @@
   // Custom value for icon size (usually used to make the icon smaller).
   absl::optional<int> icon_size_;
 
+  // Called to update background color when the button is enabled/disabled.
+  base::CallbackListSubscription enabled_changed_subscription_;
+
   DisabledButtonBehavior button_behavior_ = DisabledButtonBehavior::kNone;
 };
 
diff --git a/ash/style/style_viewer/icon_button_instances_grid_view_factory.cc b/ash/style/style_viewer/icon_button_instances_grid_view_factory.cc
index ab9e233..d72678f 100644
--- a/ash/style/style_viewer/icon_button_instances_grid_view_factory.cc
+++ b/ash/style/style_viewer/icon_button_instances_grid_view_factory.cc
@@ -6,9 +6,11 @@
 
 #include <memory>
 
+#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/icon_button.h"
 #include "ash/style/style_viewer/system_ui_components_grid_view.h"
+#include "ui/base/resource/resource_bundle.h"
 
 namespace ash {
 
@@ -16,11 +18,19 @@
 
 // Conigure of grid view for `IconButton` instances. We have 4 x 3 instances
 // divided into 3 column groups.
-constexpr size_t kGridViewRowNum = 4;
-constexpr size_t kGridViewColNum = 3;
+constexpr size_t kGridViewRowNum = 8;
+constexpr size_t kGridViewColNum = 4;
 constexpr size_t kGridViewRowGroupSize = 4;
 constexpr size_t kGirdViewColGroupSize = 1;
 
+struct IconButtonInfo {
+  std::u16string name;
+  IconButton::Type type;
+  bool is_toggled;
+  bool is_enabled;
+  gfx::ImageSkia* bg_img;
+};
+
 }  // namespace
 
 std::unique_ptr<SystemUIComponentsGridView>
@@ -29,32 +39,84 @@
       kGridViewRowNum, kGridViewColNum, kGridViewRowGroupSize,
       kGirdViewColGroupSize);
 
+  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+  auto* image =
+      rb.GetImageSkiaNamed(IDR_SETTINGS_RGB_KEYBOARD_RAINBOW_COLOR_48_PNG);
   // The types of instances shown in the grid view. Each type contains the type
-  // name, icon button type, and if the button is toggled.
-  const std::list<std::tuple<std::u16string, IconButton::Type, bool>> types{
-      {u"Default XSmall", IconButton::Type::kXSmall, false},
-      {u"Floating XSmall", IconButton::Type::kXSmallFloating, false},
-      {u"Toggled XSmall", IconButton::Type::kXSmall, true},
-      {u"Default Small", IconButton::Type::kSmall, false},
-      {u"Floating Small", IconButton::Type::kSmallFloating, false},
-      {u"Toggled Small", IconButton::Type::kSmall, true},
-      {u"Default Meduim", IconButton::Type::kMedium, false},
-      {u"Floating Meduim", IconButton::Type::kMediumFloating, false},
-      {u"Toggled Meduim", IconButton::Type::kMedium, true},
-      {u"Default Large", IconButton::Type::kLarge, false},
-      {u"Floating Large", IconButton::Type::kLargeFloating, false},
-      {u"Toggled Large", IconButton::Type::kLarge, true}};
+  // name, icon button type, if the button is toggled, if the button is enabled,
+  // the background image pointer.
+  const std::vector<std::vector<IconButtonInfo>> type_groups{
+      {{u"Default XSmall", IconButton::Type::kXSmall, false, true, nullptr},
+       {u"Default Small", IconButton::Type::kSmall, false, true, nullptr},
+       {u"Default Meduim", IconButton::Type::kMedium, false, true, nullptr},
+       {u"Default Large", IconButton::Type::kLarge, false, true, nullptr},
 
-  for (auto type : types) {
-    auto* button = grid_view->AddInstance(
-        /*name=*/std::get<0>(type),
-        std::make_unique<IconButton>(IconButton::PressedCallback(),
-                                     /*type=*/std::get<1>(type), &kSettingsIcon,
-                                     /*accessible_name=*/std::get<0>(type),
-                                     /*is_togglable=*/true,
-                                     /*has_border=*/true));
-    button->SetToggled(std::get<2>(type));
+       {u"Floating XSmall", IconButton::Type::kXSmallFloating, false, true,
+        nullptr},
+       {u"Floating Small", IconButton::Type::kSmallFloating, false, true,
+        nullptr},
+       {u"Floating Meduim", IconButton::Type::kMediumFloating, false, true,
+        nullptr},
+       {u"Floating Large", IconButton::Type::kLargeFloating, false, true,
+        nullptr},
+
+       {u"Toggled XSmall", IconButton::Type::kXSmall, true, true, nullptr},
+       {u"Toggled Small", IconButton::Type::kSmall, true, true, nullptr},
+       {u"Toggled Meduim", IconButton::Type::kMedium, true, true, nullptr},
+       {u"Toggled Large", IconButton::Type::kLarge, true, true, nullptr},
+
+       {u"Prominent Floating XSmall",
+        IconButton::Type::kXSmallProminentFloating, false, true, nullptr},
+       {u"Prominent Floating Small", IconButton::Type::kSmallProminentFloating,
+        false, true, nullptr},
+       {u"Prominent Floating Medium",
+        IconButton::Type::kMediumProminentFloating, false, true, nullptr},
+       {u"Prominent Floating Large", IconButton::Type::kLargeProminentFloating,
+        false, true, nullptr}},
+
+      {{u"Default XSmall With Background Image", IconButton::Type::kXSmall,
+        false, true, image},
+       {u"Default Small With Background Image", IconButton::Type::kSmall, false,
+        true, image},
+       {u"Default Medium With Background Image", IconButton::Type::kMedium,
+        false, true, image},
+       {u"Default Large With Background Image", IconButton::Type::kLarge, false,
+        true, image},
+
+       {u"Disabled XSmall", IconButton::Type::kXSmall, false, false, nullptr},
+       {u"Disabled Small", IconButton::Type::kSmall, false, false, nullptr},
+       {u"Disabled Meduim", IconButton::Type::kMedium, false, false, nullptr},
+       {u"Disabled Large", IconButton::Type::kLarge, false, false, nullptr}}};
+
+  // Insert the instance in grid view with column-primary order.
+  for (auto types : type_groups) {
+    const size_t group_size = types.size();
+    for (size_t i = 0; i < kGridViewColNum * kGridViewRowGroupSize; i++) {
+      // Transfer index from row-primary order to column-primary order
+      const size_t row_id = i / kGridViewColNum;
+      const size_t col_id = i % kGridViewColNum;
+      const size_t idx = col_id * kGridViewRowGroupSize + row_id;
+      if (idx >= group_size) {
+        grid_view->AddInstance(u"", std::unique_ptr<IconButton>(nullptr));
+      } else {
+        IconButtonInfo type_info = types[idx];
+        auto* button = grid_view->AddInstance(
+            /*name=*/type_info.name,
+            std::make_unique<IconButton>(IconButton::PressedCallback(),
+                                         /*type=*/type_info.type,
+                                         &kSettingsIcon,
+                                         /*accessible_name=*/type_info.name,
+                                         /*is_togglable=*/true,
+                                         /*has_border=*/true));
+        button->SetToggled(type_info.is_toggled);
+        button->SetEnabled(type_info.is_enabled);
+        if (type_info.bg_img) {
+          button->SetBackgroundImage(*type_info.bg_img);
+        }
+      }
+    }
   }
+
   return grid_view;
 }
 
diff --git a/ash/system/cast/cast_zero_state_view.cc b/ash/system/cast/cast_zero_state_view.cc
new file mode 100644
index 0000000..aefcdbbc
--- /dev/null
+++ b/ash/system/cast/cast_zero_state_view.cc
@@ -0,0 +1,67 @@
+// 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/system/cast/cast_zero_state_view.h"
+
+#include <memory>
+
+#include "ash/bubble/bubble_utils.h"
+#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
+#include "ash/public/cpp/style/dark_light_mode_controller.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/rounded_container.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view_class_properties.h"
+
+using views::BoxLayout;
+using views::ImageView;
+using views::Label;
+
+namespace ash {
+
+CastZeroStateView::CastZeroStateView() {
+  SetUseDefaultFillLayout(true);
+  SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(0, 16, 16, 16)));
+
+  // The zero-state view are inside a rounded container.
+  auto* container = AddChildView(std::make_unique<RoundedContainer>());
+  container->SetBorderInsets(gfx::Insets::VH(0, 32));
+
+  // The views are centered vertically.
+  std::unique_ptr<BoxLayout> layout =
+      std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical);
+  layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
+  container->SetLayoutManager(std::move(layout));
+
+  // TODO(b/252872586): UX provided unscaled PNG images as placeholders. They
+  // should be replaced with a themed vector icon.
+  int image_resource_id = DarkLightModeController::Get()->IsDarkModeEnabled()
+                              ? IDR_TRAY_CAST_ZERO_STATE_DARK
+                              : IDR_TRAY_CAST_ZERO_STATE_LIGHT;
+  ImageView* image = container->AddChildView(std::make_unique<ImageView>(
+      ui::ImageModel::FromResourceId(image_resource_id)));
+  // The placeholders are unscaled (2x), so handle sizing here.
+  image->SetImageSize(gfx::Size(176, 170));
+
+  Label* title = container->AddChildView(std::make_unique<Label>(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE)));
+  bubble_utils::ApplyStyle(title, bubble_utils::TypographyStyle::kTitle1);
+  title->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
+  title->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(32, 0, 0, 0));
+
+  Label* subtitle = container->AddChildView(std::make_unique<Label>(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE)));
+  subtitle->SetMultiLine(true);
+  bubble_utils::ApplyStyle(subtitle, bubble_utils::TypographyStyle::kBody1);
+  subtitle->SetEnabledColorId(cros_tokens::kTextColorSecondary);
+  subtitle->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(8, 0, 0, 0));
+}
+
+}  // namespace ash
diff --git a/ash/system/cast/cast_zero_state_view.h b/ash/system/cast/cast_zero_state_view.h
new file mode 100644
index 0000000..caa4d9b
--- /dev/null
+++ b/ash/system/cast/cast_zero_state_view.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 ASH_SYSTEM_CAST_CAST_ZERO_STATE_VIEW_H_
+#define ASH_SYSTEM_CAST_CAST_ZERO_STATE_VIEW_H_
+
+#include "ui/views/view.h"
+
+namespace ash {
+
+// The view shown in the system tray when there are no cast targets available.
+class CastZeroStateView : public views::View {
+ public:
+  CastZeroStateView();
+  CastZeroStateView(const CastZeroStateView&) = delete;
+  CastZeroStateView& operator=(const CastZeroStateView&) = delete;
+  ~CastZeroStateView() override = default;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_CAST_CAST_ZERO_STATE_VIEW_H_
diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc
index b4aa211..30c49bb 100644
--- a/ash/system/cast/tray_cast.cc
+++ b/ash/system/cast/tray_cast.cc
@@ -10,15 +10,14 @@
 #include <vector>
 
 #include "ash/constants/ash_features.h"
-#include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.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_constants.h"
 #include "ash/system/tray/tray_detailed_view.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/utf_string_conversions.h"
@@ -27,12 +26,9 @@
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
-#include "ui/views/controls/button/button.h"
 #include "ui/views/controls/scroll_view.h"
+#include "ui/views/layout/box_layout.h"
 
 namespace ash {
 
@@ -112,6 +108,11 @@
   // Remove all of the existing views.
   view_to_sink_map_.clear();
   scroll_content()->RemoveAllChildViews();
+  add_access_code_device_ = nullptr;
+  if (zero_state_view_) {
+    RemoveChildViewT(zero_state_view_);
+    zero_state_view_ = nullptr;
+  }
 
   // QsRevamp places items in a rounded container.
   views::View* item_container =
@@ -137,10 +138,28 @@
     view_to_sink_map_[container] = sink.id;
   }
 
+  // If there are no receiver views, show the zero state view.
+  if (features::IsQsRevampEnabled() && !add_access_code_device_ &&
+      view_to_sink_map_.empty()) {
+    AddZeroStateView();
+    scroller()->SetVisible(false);
+  } else {
+    scroller()->SetVisible(true);
+  }
+
   scroll_content()->SizeToPreferredSize();
   scroller()->Layout();
 }
 
+void CastDetailedView::AddZeroStateView() {
+  DCHECK(!zero_state_view_);
+  DCHECK(scroller());
+  zero_state_view_ = AddChildViewAt(std::make_unique<CastZeroStateView>(),
+                                    GetIndexOf(scroller()).value());
+  // Make the view fill the entire space below the title row.
+  box_layout()->SetFlexForView(zero_state_view_, 1);
+}
+
 void CastDetailedView::HandleViewClicked(views::View* view) {
   // Find the receiver we are going to cast to.
   auto it = view_to_sink_map_.find(view);
diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h
index 4bf593b1..0bc8dac 100644
--- a/ash/system/cast/tray_cast.h
+++ b/ash/system/cast/tray_cast.h
@@ -13,6 +13,10 @@
 #include "ash/system/tray/tray_detailed_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
+namespace views {
+class View;
+}  // namespace views
+
 namespace ash {
 
 // This view displays a list of cast receivers that can be clicked on and casted
@@ -44,6 +48,9 @@
 
   void UpdateReceiverListFromCachedData();
 
+  // Adds the view shown when no cast devices are available (with QsRevamp).
+  void AddZeroStateView();
+
   // TrayDetailedView:
   void HandleViewClicked(views::View* view) override;
 
@@ -55,6 +62,9 @@
 
   // Special list item that, if clicked, launches the access code casting dialog
   views::View* add_access_code_device_ = nullptr;
+
+  // View shown when no cast devices are available (with QsRevamp).
+  views::View* zero_state_view_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/system/cast/tray_cast_unittest.cc b/ash/system/cast/tray_cast_unittest.cc
index 6f5715b..5c96432 100644
--- a/ash/system/cast/tray_cast_unittest.cc
+++ b/ash/system/cast/tray_cast_unittest.cc
@@ -80,6 +80,8 @@
     return views;
   }
 
+  views::View* GetZeroStateView() { return detailed_view_->zero_state_view_; }
+
   // Adds two simulated cast devices.
   void AddCastDevices() {
     std::vector<SinkAndRoute> devices;
@@ -98,6 +100,9 @@
     detailed_view_->OnDevicesUpdated(devices);
   }
 
+  // Removes simulated cast devices.
+  void ResetCastDevices() { detailed_view_->OnDevicesUpdated({}); }
+
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<views::Widget> widget_;
   TestCastConfigController cast_config_;
@@ -128,4 +133,18 @@
   EXPECT_EQ(delegate_->close_bubble_call_count(), 1u);
 }
 
+TEST_F(CastDetailedViewTest, ZeroStateView) {
+  // The zero state view shows when there are no cast devices.
+  ASSERT_TRUE(GetDeviceViews().empty());
+  EXPECT_TRUE(GetZeroStateView());
+
+  // Adding cast devices hides the zero state view.
+  AddCastDevices();
+  EXPECT_FALSE(GetZeroStateView());
+
+  // Removing cast devices shows the zero state view.
+  ResetCastDevices();
+  EXPECT_TRUE(GetZeroStateView());
+}
+
 }  // namespace ash
diff --git a/ash/system/do_not_disturb_notification_controller.cc b/ash/system/do_not_disturb_notification_controller.cc
new file mode 100644
index 0000000..fed5bde5
--- /dev/null
+++ b/ash/system/do_not_disturb_notification_controller.cc
@@ -0,0 +1,83 @@
+// 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/system/do_not_disturb_notification_controller.h"
+#include "ash/constants/notifier_catalogs.h"
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+#include "ui/message_center/public/cpp/notifier_id.h"
+
+namespace ash {
+
+namespace {
+
+using message_center::MessageCenter;
+
+const char kDoNotDisturbNotificationId[] = "do_not_disturb";
+const char kDoNotDisturbNotifierId[] =
+    "ash.do_not_disturb_notification_controller";
+
+}  // namespace
+
+DoNotDisturbNotificationController::DoNotDisturbNotificationController() {
+  MessageCenter::Get()->AddObserver(this);
+}
+
+DoNotDisturbNotificationController::~DoNotDisturbNotificationController() {
+  MessageCenter::Get()->RemoveObserver(this);
+}
+
+std::unique_ptr<message_center::Notification>
+DoNotDisturbNotificationController::CreateNotification() {
+  message_center::RichNotificationData optional_fields;
+  optional_fields.buttons.emplace_back(
+      l10n_util::GetStringUTF16(IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TURN_OFF));
+  optional_fields.pinned = true;
+  return ash::CreateSystemNotificationPtr(
+      message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
+      kDoNotDisturbNotificationId,
+      l10n_util::GetStringUTF16(IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_TITLE),
+      l10n_util::GetStringUTF16(
+          IDS_ASH_DO_NOT_DISTURB_NOTIFICATION_DESCRIPTION),
+      /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
+      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+                                 kDoNotDisturbNotifierId,
+                                 NotificationCatalogName::kDoNotDisturb),
+      optional_fields,
+      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
+          base::BindRepeating([](absl::optional<int> button_index) {
+            if (!button_index.has_value())
+              return;
+            // The notification only has one button (the "Turn off" button), so
+            // the presence of any value in `button_index` means this is the
+            // button that was pressed.
+            DCHECK_EQ(button_index.value(), 0);
+            MessageCenter::Get()->SetQuietMode(false);
+          })),
+      vector_icons::kSettingsOutlineIcon,
+      message_center::SystemNotificationWarningLevel::NORMAL);
+}
+
+void DoNotDisturbNotificationController::OnQuietModeChanged(
+    bool in_quiet_mode) {
+  auto* message_center = MessageCenter::Get();
+  if (in_quiet_mode) {
+    DCHECK(!message_center->FindNotificationById(kDoNotDisturbNotificationId));
+    message_center->AddNotification(CreateNotification());
+    return;
+  }
+  DCHECK(message_center->FindNotificationById(kDoNotDisturbNotificationId));
+  message_center->RemoveNotification(kDoNotDisturbNotificationId,
+                                     /*by_user=*/false);
+}
+
+}  // namespace ash
diff --git a/ash/system/do_not_disturb_notification_controller.h b/ash/system/do_not_disturb_notification_controller.h
new file mode 100644
index 0000000..d4e513f8
--- /dev/null
+++ b/ash/system/do_not_disturb_notification_controller.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_SYSTEM_DO_NOT_DISTURB_NOTIFICATION_CONTROLLER_H_
+#define ASH_SYSTEM_DO_NOT_DISTURB_NOTIFICATION_CONTROLLER_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "ui/message_center/message_center_observer.h"
+
+namespace message_center {
+class Notification;
+}  // namespace message_center
+
+namespace ash {
+
+// Controller class to manage the "Do not disturb" notification. This class only
+// exists when `IsQsRevampEnabled` is true.
+class ASH_EXPORT DoNotDisturbNotificationController
+    : public message_center::MessageCenterObserver {
+ public:
+  DoNotDisturbNotificationController();
+
+  DoNotDisturbNotificationController(
+      const DoNotDisturbNotificationController&) = delete;
+  DoNotDisturbNotificationController& operator=(
+      const DoNotDisturbNotificationController&) = delete;
+
+  ~DoNotDisturbNotificationController() override;
+
+  // message_center::MessageCenterObserver:
+  void OnQuietModeChanged(bool in_quiet_mode) override;
+
+ private:
+  std::unique_ptr<message_center::Notification> CreateNotification();
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_DO_NOT_DISTURB_NOTIFICATION_CONTROLLER_H_
diff --git a/ash/system/do_not_disturb_notification_controller_unittest.cc b/ash/system/do_not_disturb_notification_controller_unittest.cc
new file mode 100644
index 0000000..5a5b207
--- /dev/null
+++ b/ash/system/do_not_disturb_notification_controller_unittest.cc
@@ -0,0 +1,97 @@
+// 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/system/do_not_disturb_notification_controller.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "ui/message_center/message_center.h"
+
+namespace ash {
+
+namespace {
+
+using message_center::MessageCenter;
+
+const char kDoNotDisturbNotificationId[] = "do_not_disturb";
+
+}  // namespace
+
+class DoNotDisturbNotificationControllerTest
+    : public AshTestBase,
+      public testing::WithParamInterface<bool> {
+ public:
+  DoNotDisturbNotificationControllerTest() = default;
+  DoNotDisturbNotificationControllerTest(
+      const DoNotDisturbNotificationControllerTest&) = delete;
+  DoNotDisturbNotificationControllerTest& operator=(
+      const DoNotDisturbNotificationControllerTest&) = delete;
+  ~DoNotDisturbNotificationControllerTest() override = default;
+
+  void SetUp() override {
+    if (IsQsRevampEnabled()) {
+      scoped_feature_list_.InitWithFeatures(
+          {features::kQsRevamp, features::kQsRevampWip}, {});
+    }
+    AshTestBase::SetUp();
+  }
+
+  bool IsQsRevampEnabled() { return GetParam(); }
+
+  bool IsDoNotDisturbNotificationPresent() {
+    return MessageCenter::Get()->FindNotificationById(
+        kDoNotDisturbNotificationId);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         DoNotDisturbNotificationControllerTest,
+                         testing::Bool() /* IsQsRevampEnabled() */);
+
+// Tests that enabling/disabling Do not disturb mode adds/removes the Do not
+// disturb notification.
+TEST_P(DoNotDisturbNotificationControllerTest, AddRemoveNotification) {
+  auto* message_center = MessageCenter::Get();
+  ASSERT_FALSE(IsDoNotDisturbNotificationPresent());
+
+  // Turn on Do not disturb mode.
+  message_center->SetQuietMode(true);
+  if (IsQsRevampEnabled()) {
+    EXPECT_TRUE(IsDoNotDisturbNotificationPresent());
+  } else {
+    EXPECT_FALSE(IsDoNotDisturbNotificationPresent());
+  }
+
+  // Turn off Do not disturb mode.
+  message_center->SetQuietMode(false);
+  EXPECT_FALSE(IsDoNotDisturbNotificationPresent());
+}
+
+// Tests that clicking the notification's "Turn off" button turns off Do not
+// disturb mode and dismisses the Do not disturb notification.
+TEST_P(DoNotDisturbNotificationControllerTest,
+       NotificationButtonTurnsOffDoNotDisturbMode) {
+  if (!IsQsRevampEnabled()) {
+    // The notification only appears when QsRevamp is enabled.
+    return;
+  }
+
+  // Show the notification by turning on Do not disturb mode.
+  auto* message_center = MessageCenter::Get();
+  message_center->SetQuietMode(true);
+  ASSERT_TRUE(IsDoNotDisturbNotificationPresent());
+
+  // Simulate a click on the notification's "Turn off" button.
+  auto* notification =
+      message_center->FindNotificationById(kDoNotDisturbNotificationId);
+  notification->delegate()->Click(0, absl::nullopt);
+  EXPECT_FALSE(IsDoNotDisturbNotificationPresent());
+  EXPECT_FALSE(message_center->IsQuietMode());
+}
+
+}  // namespace ash
diff --git a/ash/system/phonehub/app_stream_launcher_item.cc b/ash/system/phonehub/app_stream_launcher_item.cc
index fb5efc4..1dd7cc5 100644
--- a/ash/system/phonehub/app_stream_launcher_item.cc
+++ b/ash/system/phonehub/app_stream_launcher_item.cc
@@ -4,7 +4,14 @@
 
 #include "ash/system/phonehub/app_stream_launcher_item.h"
 
+#include "ash/strings/grit/ash_strings.h"
+#include "base/hash/hash.h"
+#include "base/strings/string_piece_forward.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/text_constants.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/label_button.h"
@@ -21,6 +28,7 @@
 constexpr int kEcheAppItemSpacing = 4;
 constexpr int kEcheAppNameLabelLineHeight = 14;
 constexpr int kEcheAppNameLabelFontSize = 11;
+constexpr double kAlphaValueForInhibitedIconOpacity = 0.3;
 
 void ConfigureLabel(views::Label* label, int line_height, int font_size) {
   label->SetLineHeight(line_height);
@@ -62,11 +70,43 @@
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
+  const bool enabled = app_metadata.app_streamability_status ==
+                       phonehub::proto::AppStreamabilityStatus::STREAMABLE;
+  gfx::Image image = app_metadata.icon;
+  if (!enabled) {
+    // Fade the image in order to make it look like grayed out.
+    // TODO(b/261916553): Make grayed out icons "gray" in
+    // addition to 30% transparent.
+    image = gfx::Image(gfx::ImageSkiaOperations::CreateTransparentImage(
+        image.AsImageSkia(), kAlphaValueForInhibitedIconOpacity));
+  }
+
+  std::u16string accessible_name;
+  switch (app_metadata.app_streamability_status) {
+    case phonehub::proto::STREAMABLE:
+      accessible_name = app_metadata.visible_app_name;
+      break;
+    case phonehub::proto::BLOCKED_BY_APP:
+      accessible_name = l10n_util::GetStringUTF16(
+          IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED_BY_APP);
+      break;
+    case phonehub::proto::BLOCK_LISTED:
+    default:
+      accessible_name =
+          l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_STREAM_NOT_SUPPORTED);
+      break;
+  }
   recent_app_button_ = AddChildView(std::make_unique<PhoneHubRecentAppButton>(
-      app_metadata.icon, app_metadata.visible_app_name, callback));
+      image, app_metadata.visible_app_name, callback));
+  recent_app_button_->SetAccessibleName(accessible_name);
+  recent_app_button_->SetTooltipText(accessible_name);
+  recent_app_button_->SetEnabled(enabled);
 
   label_ = AddChildView(
       std::make_unique<AppNameLabel>(callback, app_metadata.visible_app_name));
+  label_->SetEnabled(enabled);
+  label_->SetAccessibleName(accessible_name);
+  label_->SetTooltipText(accessible_name);
 }
 
 AppStreamLauncherItem::~AppStreamLauncherItem() = default;
diff --git a/ash/system/phonehub/app_stream_launcher_view_unittest.cc b/ash/system/phonehub/app_stream_launcher_view_unittest.cc
index 51590cc9..4ebf923 100644
--- a/ash/system/phonehub/app_stream_launcher_view_unittest.cc
+++ b/ash/system/phonehub/app_stream_launcher_view_unittest.cc
@@ -116,7 +116,8 @@
 
   auto app1 = phonehub::Notification::AppMetadata(
       app_visible_name, package_name, CreateTestImage(),
-      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
+      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id,
+      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
   std::vector<phonehub::Notification::AppMetadata> apps;
   apps.push_back(app1);
 
@@ -139,7 +140,8 @@
 
   auto app1 = phonehub::Notification::AppMetadata(
       app_visible_name, package_name, CreateTestImage(),
-      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
+      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id,
+      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
   std::vector<phonehub::Notification::AppMetadata> apps;
   apps.push_back(app1);
 
@@ -170,7 +172,8 @@
 
   auto app1 = phonehub::Notification::AppMetadata(
       app_visible_name, package_name, CreateTestImage(),
-      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
+      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id,
+      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
   std::vector<phonehub::Notification::AppMetadata> apps;
   apps.push_back(app1);
 
@@ -188,6 +191,8 @@
       GetRootWindow(app_stream_launcher_view()->GetWidget()));
 
   EXPECT_TRUE(GetItemView(0)->GetVisible());
+  EXPECT_TRUE(GetItemView(0)->GetIconForTest()->GetEnabled());
+  EXPECT_TRUE(GetItemView(0)->GetLabelForTest()->GetEnabled());
 
   gfx::Point cursor_location =
       GetItemView(0)->GetIconForTest()->GetBoundsInScreen().CenterPoint();
@@ -199,4 +204,36 @@
                     ->HandledRecentAppsCount(package_name));
 }
 
+TEST_F(AppStreamLauncherViewTest, DisabledItem) {
+  const int64_t user_id = 1;
+  const char16_t app_visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+
+  auto app1 = phonehub::Notification::AppMetadata(
+      app_visible_name, package_name, CreateTestImage(),
+      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id,
+      phonehub::proto::AppStreamabilityStatus::BLOCK_LISTED);
+  std::vector<phonehub::Notification::AppMetadata> apps;
+  apps.push_back(app1);
+
+  phonehub::AppStreamLauncherDataModel* data_model =
+      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
+  data_model->SetAppList(apps);
+  widget()->LayoutRootViewIfNecessary();
+
+  EXPECT_EQ(1U, app_stream_launcher_view()
+                    ->items_container_for_test()
+                    ->children()
+                    .size());
+
+  ui::test::EventGenerator generator(
+      GetRootWindow(app_stream_launcher_view()->GetWidget()));
+
+  EXPECT_TRUE(GetItemView(0)->GetVisible());
+  EXPECT_FALSE(GetItemView(0)->GetIconForTest()->GetEnabled());
+  EXPECT_FALSE(GetItemView(0)->GetLabelForTest()->GetEnabled());
+  EXPECT_EQ(u"Not supported",
+            GetItemView(0)->GetIconForTest()->GetTooltipText());
+}
+
 }  // namespace ash
diff --git a/ash/system/phonehub/phone_hub_notification_controller_unittest.cc b/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
index 6b4b836..02341fc3 100644
--- a/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
+++ b/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
@@ -60,7 +60,8 @@
       id,
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName, /*icon=*/gfx::Image(),
-          /*icon_color=*/absl::nullopt, /*icon_is_monochrome =*/true, kUserId),
+          /*icon_color=*/absl::nullopt, /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply,
@@ -75,7 +76,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kIncomingCall,
       {{phonehub::Notification::ActionType::kInlineReply,
@@ -163,7 +165,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -194,7 +197,8 @@
       kPhoneHubNotificationId1,
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName, /*icon=*/gfx::Image(), iconColor,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -214,7 +218,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/false, kUserId),
+          /*icon_is_monochrome =*/false, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -336,7 +341,8 @@
       kPhoneHubNotificationId0,
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName, icon, /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       timestamp, phonehub::Notification::Importance::kHigh,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -443,7 +449,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       old_timestamp, phonehub::Notification::Importance::kHigh,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -472,7 +479,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kHigh,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -503,7 +511,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kMin,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
@@ -533,7 +542,8 @@
       phonehub::Notification::AppMetadata(
           kAppName, kPackageName,
           /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-          /*icon_is_monochrome =*/true, kUserId),
+          /*icon_is_monochrome =*/true, kUserId,
+          phonehub::proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), phonehub::Notification::Importance::kDefault,
       phonehub::Notification::Category::kConversation,
       {{phonehub::Notification::ActionType::kInlineReply, 0}},
diff --git a/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc b/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
index af47136..dd5331d 100644
--- a/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
+++ b/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
@@ -62,7 +62,8 @@
         phonehub::Notification::AppMetadata(
             kAppName, kPackageName,
             /*icon=*/gfx::Image(), /*icon_color =*/absl::nullopt,
-            /*icon_is_monochrome =*/true, kUserId),
+            /*icon_is_monochrome =*/true, kUserId,
+            phonehub::proto::AppStreamabilityStatus::STREAMABLE),
         base::Time::Now());
   }
 
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller.cc b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
index 1baf6cc..8c2524d 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller.cc
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
@@ -124,6 +124,10 @@
   }
 }
 
+void CameraPrivacySwitchController::OnCameraCountChanged(int new_camera_count) {
+  camera_count_ = new_camera_count;
+}
+
 CameraSWPrivacySwitchSetting
 CameraPrivacySwitchController::GetUserSwitchPreference() {
   DCHECK(pref_change_registrar_);
@@ -163,9 +167,10 @@
     frontend->CameraHardwareToggleChanged(state);
   }
   // Issue a notification if camera is disabled by HW switch, but not by the SW
-  // switch
+  // switch and there is multiple cameras.
   if (state == cros::mojom::CameraPrivacySwitchState::ON &&
-      GetUserSwitchPreference() == CameraSWPrivacySwitchSetting::kEnabled) {
+      GetUserSwitchPreference() == CameraSWPrivacySwitchSetting::kEnabled &&
+      camera_count_ > 1) {
     ShowHWCameraSwitchOffSWCameraSwitchOnNotification();
   }
   if (state == cros::mojom::CameraPrivacySwitchState::OFF) {
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller.h b/ash/system/privacy_hub/camera_privacy_switch_controller.h
index 1564488..78313eb5 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller.h
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/public/cpp/session/session_observer.h"
+#include "base/supports_user_data.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
 
@@ -44,7 +45,8 @@
 // preference setting.
 class ASH_EXPORT CameraPrivacySwitchController
     : public SessionObserver,
-      public media::CameraPrivacySwitchObserver {
+      public media::CameraPrivacySwitchObserver,
+      public base::SupportsUserData {
  public:
   CameraPrivacySwitchController();
 
@@ -67,6 +69,9 @@
   // Handles user toggling the camera switch on Privacy Hub UI.
   void OnPreferenceChanged(const std::string& pref_name);
 
+  // Handles the change in the number of cameras
+  void OnCameraCountChanged(int new_camera_count);
+
   // Returns the last observed HW switch state for the camera.
   cros::mojom::CameraPrivacySwitchState HWSwitchState() const;
 
@@ -115,6 +120,7 @@
       cros::mojom::CameraPrivacySwitchState::UNKNOWN;
   int active_applications_using_camera_count_ = 0;
   bool is_camera_observer_added_ = false;
+  int camera_count_ = -1;
 };
 
 }  // namespace ash
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc b/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
index d64a69f..f4d9fb77 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller_unittest.cc
@@ -193,32 +193,44 @@
       cros::mojom::CameraPrivacySwitchState::ON);
 }
 
-TEST_F(PrivacyHubCameraControllerTests, OnCameraHardwarePrivacySwitchChanged) {
+TEST_F(PrivacyHubCameraControllerTests,
+       OnCameraHardwarePrivacySwitchChangedMultipleCameras) {
   EXPECT_CALL(mock_frontend_, CameraHardwareToggleChanged(
                                   cros::mojom::CameraPrivacySwitchState::OFF));
   EXPECT_CALL(mock_frontend_, CameraHardwareToggleChanged(
                                   cros::mojom::CameraPrivacySwitchState::ON));
   CameraPrivacySwitchController& controller =
       Shell::Get()->privacy_hub_controller()->camera_controller();
+  // We have 2 cameras in the system.
+  controller.OnCameraCountChanged(2);
+  // Camera is enabled in Privacy Hub.
   SetUserPref(true);
 
+  // Somebody switched the camera off by the hardware switch.
   controller.OnCameraHWPrivacySwitchStateChanged(
       std::string(), cros::mojom::CameraPrivacySwitchState::OFF);
+  // Controller must know about it.
   EXPECT_EQ(cros::mojom::CameraPrivacySwitchState::OFF,
             controller.HWSwitchState());
   EXPECT_FALSE(FindNotificationById(
       kPrivacyHubHWCameraSwitchOffSWCameraSwitchOnNotificationId));
 
+  // Somebody switched the camera off by the hardware switch.
   controller.OnCameraHWPrivacySwitchStateChanged(
       std::string(), cros::mojom::CameraPrivacySwitchState::ON);
+  // Controller must know about it.
   EXPECT_EQ(cros::mojom::CameraPrivacySwitchState::ON,
             controller.HWSwitchState());
 
   message_center::MessageCenter* const message_center =
       message_center::MessageCenter::Get();
+  // This particular notification ("Do you want to disable all cameras?") should
+  // appear only there are multiple cameras.
   EXPECT_TRUE(FindNotificationById(
       kPrivacyHubHWCameraSwitchOffSWCameraSwitchOnNotificationId));
+  // User pref didn't change.
   EXPECT_TRUE(GetUserPref());
+  // We didn't log any notification clicks so far.
   EXPECT_EQ(histogram_tester_.GetBucketCount(
                 privacy_hub_metrics::
                     kPrivacyHubCameraEnabledFromNotificationHistogram,
@@ -229,11 +241,15 @@
                     kPrivacyHubCameraEnabledFromNotificationHistogram,
                 false),
             0);
+  // Click on the notification button.
   message_center->ClickOnNotificationButton(
       kPrivacyHubHWCameraSwitchOffSWCameraSwitchOnNotificationId, 0);
+  // This must change the user pref for the camera (disabling all cameras).
   EXPECT_FALSE(GetUserPref());
+  // The notification should be cleared after it has been clicked on.
   EXPECT_FALSE(FindNotificationById(
       kPrivacyHubHWCameraSwitchOffSWCameraSwitchOnNotificationId));
+  // The histograms were updated.
   EXPECT_EQ(histogram_tester_.GetBucketCount(
                 privacy_hub_metrics::
                     kPrivacyHubCameraEnabledFromNotificationHistogram,
@@ -246,12 +262,58 @@
             1);
 }
 
+TEST_F(PrivacyHubCameraControllerTests,
+       OnCameraHardwarePrivacySwitchChangedOneCamera) {
+  EXPECT_CALL(mock_frontend_, CameraHardwareToggleChanged(
+                                  cros::mojom::CameraPrivacySwitchState::OFF));
+  EXPECT_CALL(mock_frontend_, CameraHardwareToggleChanged(
+                                  cros::mojom::CameraPrivacySwitchState::ON));
+  CameraPrivacySwitchController& controller =
+      Shell::Get()->privacy_hub_controller()->camera_controller();
+  // We have 1 camera in the system.
+  controller.OnCameraCountChanged(1);
+  // Camera is enabled in Privacy Hub.
+  SetUserPref(true);
+
+  // Somebody switched the camera off by the hardware switch.
+  controller.OnCameraHWPrivacySwitchStateChanged(
+      std::string(), cros::mojom::CameraPrivacySwitchState::OFF);
+  // Controller must know about it.
+  EXPECT_EQ(cros::mojom::CameraPrivacySwitchState::OFF,
+            controller.HWSwitchState());
+  // This particular notification should appear only if there are multiple
+  // cameras.
+  EXPECT_FALSE(message_center::MessageCenter::Get()->FindNotificationById(
+      kPrivacyHubHWCameraSwitchOffSWCameraSwitchOnNotificationId));
+
+  // Switching the hardware switch back again.
+  controller.OnCameraHWPrivacySwitchStateChanged(
+      std::string(), cros::mojom::CameraPrivacySwitchState::ON);
+  // Controller is aware.
+  EXPECT_EQ(cros::mojom::CameraPrivacySwitchState::ON,
+            controller.HWSwitchState());
+  // This didn't cause any change in the setting toggle.
+  EXPECT_TRUE(GetUserPref());
+  // There were no changes to the histograms.
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                privacy_hub_metrics::
+                    kPrivacyHubCameraEnabledFromNotificationHistogram,
+                true),
+            0);
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                privacy_hub_metrics::
+                    kPrivacyHubCameraEnabledFromNotificationHistogram,
+                false),
+            0);
+}
+
 // This test is a regression test for b/253407315
 TEST_F(PrivacyHubCameraControllerTests,
        OnCameraHardwarePrivacySwitchChangedNotificationClearing) {
   CameraPrivacySwitchController& controller =
       Shell::Get()->privacy_hub_controller()->camera_controller();
   SetUserPref(true);
+  controller.OnCameraCountChanged(2);
 
   controller.OnCameraHWPrivacySwitchStateChanged(
       "0", cros::mojom::CameraPrivacySwitchState::ON);
@@ -301,6 +363,7 @@
 TEST_F(PrivacyHubCameraControllerTests,
        CameraOffNotificationRemoveViaClickOnBody) {
   SetUserPref(false);
+  controller_->OnCameraCountChanged(2);
   message_center::MessageCenter* const message_center =
       message_center::MessageCenter::Get();
   ASSERT_TRUE(message_center);
diff --git a/ash/system/system_notification_controller.cc b/ash/system/system_notification_controller.cc
index 8fe1596..29094fd 100644
--- a/ash/system/system_notification_controller.cc
+++ b/ash/system/system_notification_controller.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/system/caps_lock_notification_controller.h"
 #include "ash/system/cast/cast_notification_controller.h"
+#include "ash/system/do_not_disturb_notification_controller.h"
 #include "ash/system/gesture_education/gesture_education_notification_controller.h"
 #include "ash/system/microphone_mute/microphone_mute_notification_controller.h"
 #include "ash/system/network/auto_connect_notifier.h"
@@ -39,6 +40,10 @@
       caps_lock_(std::make_unique<CapsLockNotificationController>()),
       cast_(std::make_unique<CastNotificationController>()),
       cellular_setup_notifier_(std::make_unique<ash::CellularSetupNotifier>()),
+      do_not_disturb_(
+          features::IsQsRevampEnabled()
+              ? std::make_unique<DoNotDisturbNotificationController>()
+              : nullptr),
       gesture_education_(
           std::make_unique<GestureEducationNotificationController>()),
       power_(std::make_unique<PowerNotificationController>(
diff --git a/ash/system/system_notification_controller.h b/ash/system/system_notification_controller.h
index 71afeaa..60c37dae 100644
--- a/ash/system/system_notification_controller.h
+++ b/ash/system/system_notification_controller.h
@@ -15,6 +15,7 @@
 class GestureEducationNotificationController;
 class CastNotificationController;
 class CellularSetupNotifier;
+class DoNotDisturbNotificationController;
 class ManagedSimLockNotifier;
 class MicrophoneMuteNotificationController;
 class PowerNotificationController;
@@ -52,6 +53,7 @@
   const std::unique_ptr<CapsLockNotificationController> caps_lock_;
   const std::unique_ptr<CastNotificationController> cast_;
   const std::unique_ptr<CellularSetupNotifier> cellular_setup_notifier_;
+  const std::unique_ptr<DoNotDisturbNotificationController> do_not_disturb_;
   const std::unique_ptr<GestureEducationNotificationController>
       gesture_education_;
   // TODO(b/228093904): Make |managed_sim_lock_notifier_| const during cleanup.
diff --git a/ash/system/video_conference/video_conference_bubble.cc b/ash/system/video_conference/bubble/video_conference_bubble.cc
similarity index 98%
rename from ash/system/video_conference/video_conference_bubble.cc
rename to ash/system/video_conference/bubble/video_conference_bubble.cc
index e6657ca..be694ba 100644
--- a/ash/system/video_conference/video_conference_bubble.cc
+++ b/ash/system/video_conference/bubble/video_conference_bubble.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 "ash/system/video_conference/video_conference_bubble.h"
+#include "ash/system/video_conference/bubble/video_conference_bubble.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/ash_color_id.h"
diff --git a/ash/system/video_conference/video_conference_bubble.h b/ash/system/video_conference/bubble/video_conference_bubble.h
similarity index 88%
rename from ash/system/video_conference/video_conference_bubble.h
rename to ash/system/video_conference/bubble/video_conference_bubble.h
index 2d8085a..ecc68bf 100644
--- a/ash/system/video_conference/video_conference_bubble.h
+++ b/ash/system/video_conference/bubble/video_conference_bubble.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 ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_BUBBLE_H_
-#define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_BUBBLE_H_
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_VIDEO_CONFERENCE_BUBBLE_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_VIDEO_CONFERENCE_BUBBLE_H_
 
 #include "ash/system/tray/tray_bubble_view.h"
 
@@ -51,4 +51,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_BUBBLE_H_
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_VIDEO_CONFERENCE_BUBBLE_H_
diff --git a/ash/system/video_conference/fake_video_conference_effects.cc b/ash/system/video_conference/effects/fake_video_conference_effects.cc
similarity index 97%
rename from ash/system/video_conference/fake_video_conference_effects.cc
rename to ash/system/video_conference/effects/fake_video_conference_effects.cc
index 0ea6381..527e530e 100644
--- a/ash/system/video_conference/fake_video_conference_effects.cc
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/video_conference/fake_video_conference_effects.h"
+#include "ash/system/video_conference/effects/fake_video_conference_effects.h"
 
 #include <string>
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/system/video_conference/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 #include "base/functional/bind.h"
 #include "ui/views/controls/button/button.h"
 
diff --git a/ash/system/video_conference/fake_video_conference_effects.h b/ash/system/video_conference/effects/fake_video_conference_effects.h
similarity index 93%
rename from ash/system/video_conference/fake_video_conference_effects.h
rename to ash/system/video_conference/effects/fake_video_conference_effects.h
index 6b27d66b..64877a8 100644
--- a/ash/system/video_conference/fake_video_conference_effects.h
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
-#define ASH_SYSTEM_VIDEO_CONFERENCE_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
 
 #include <string>
 
 #include "ash/ash_export.h"
-#include "ash/system/video_conference/video_conference_tray_effects_delegate.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
 #include "ui/views/controls/button/button.h"
 
 namespace gfx {
@@ -162,4 +162,4 @@
 
 }  // namespace ash::fake_video_conference
 
-#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
diff --git a/ash/system/video_conference/video_conference_tray_effects_delegate.cc b/ash/system/video_conference/effects/video_conference_tray_effects_delegate.cc
similarity index 77%
rename from ash/system/video_conference/video_conference_tray_effects_delegate.cc
rename to ash/system/video_conference/effects/video_conference_tray_effects_delegate.cc
index fc1f780..63f7cb14 100644
--- a/ash/system/video_conference/video_conference_tray_effects_delegate.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_delegate.cc
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/video_conference/video_conference_tray_effects_delegate.h"
-#include "ash/system/video_conference/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 
 namespace ash {
 
diff --git a/ash/system/video_conference/video_conference_tray_effects_delegate.h b/ash/system/video_conference/effects/video_conference_tray_effects_delegate.h
similarity index 86%
rename from ash/system/video_conference/video_conference_tray_effects_delegate.h
rename to ash/system/video_conference/effects/video_conference_tray_effects_delegate.h
index da90cbedd..dfeefdff 100644
--- a/ash/system/video_conference/video_conference_tray_effects_delegate.h
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_delegate.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
-#define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
 
 #include <string>
 #include <vector>
 
-#include "ash/system/video_conference/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 
 namespace ash {
 
@@ -60,4 +60,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
\ No newline at end of file
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_DELEGATE_H_
\ No newline at end of file
diff --git a/ash/system/video_conference/video_conference_tray_effects_manager.cc b/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
similarity index 89%
rename from ash/system/video_conference/video_conference_tray_effects_manager.cc
rename to ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
index 1219b17c..8365d72 100644
--- a/ash/system/video_conference/video_conference_tray_effects_manager.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/video_conference/video_conference_tray_effects_manager.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 
-#include "ash/system/video_conference/video_conference_tray_effects_delegate.h"
-#include "ash/system/video_conference/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/containers/cxx20_erase_vector.h"
diff --git a/ash/system/video_conference/video_conference_tray_effects_manager.h b/ash/system/video_conference/effects/video_conference_tray_effects_manager.h
similarity index 87%
rename from ash/system/video_conference/video_conference_tray_effects_manager.h
rename to ash/system/video_conference/effects/video_conference_tray_effects_manager.h
index 410ba97..4f9f4fbd 100644
--- a/ash/system/video_conference/video_conference_tray_effects_manager.h
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager.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 ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
-#define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
 
 #include <string>
 #include <vector>
@@ -60,4 +60,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_H_
diff --git a/ash/system/video_conference/video_conference_tray_effects_manager_types.cc b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
similarity index 85%
rename from ash/system/video_conference/video_conference_tray_effects_manager_types.cc
rename to ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
index 588fc2f..350078d74 100644
--- a/ash/system/video_conference/video_conference_tray_effects_manager_types.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/video_conference/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 
-#include "ash/system/video_conference/video_conference_tray_effects_manager.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 #include "ui/gfx/vector_icon_types.h"
 
 namespace ash {
diff --git a/ash/system/video_conference/video_conference_tray_effects_manager_types.h b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h
similarity index 93%
rename from ash/system/video_conference/video_conference_tray_effects_manager_types.h
rename to ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h
index 1fd6f53..c5418807 100644
--- a/ash/system/video_conference/video_conference_tray_effects_manager_types.h
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.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 ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
-#define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
 
 #include <string>
 #include <vector>
@@ -134,4 +134,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
\ No newline at end of file
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_VIDEO_CONFERENCE_TRAY_EFFECTS_MANAGER_TYPES_H_
\ No newline at end of file
diff --git a/ash/system/video_conference/video_conference_tray_effects_manager_unittest.cc b/ash/system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc
similarity index 97%
rename from ash/system/video_conference/video_conference_tray_effects_manager_unittest.cc
rename to ash/system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc
index 8dd4893..95da996 100644
--- a/ash/system/video_conference/video_conference_tray_effects_manager_unittest.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/video_conference/video_conference_tray_effects_manager.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/system/video_conference/fake_video_conference_effects.h"
+#include "ash/system/video_conference/effects/fake_video_conference_effects.h"
 #include "ash/test/ash_test_base.h"
 #include "ui/views/controls/button/button.h"
 
diff --git a/ash/system/video_conference/video_conference_tray.cc b/ash/system/video_conference/video_conference_tray.cc
index c5200fbc..5357b7d 100644
--- a/ash/system/video_conference/video_conference_tray.cc
+++ b/ash/system/video_conference/video_conference_tray.cc
@@ -17,7 +17,7 @@
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_utils.h"
-#include "ash/system/video_conference/video_conference_bubble.h"
+#include "ash/system/video_conference/bubble/video_conference_bubble.h"
 #include "ash/system/video_conference/video_conference_tray_controller.h"
 #include "base/functional/bind.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -52,17 +52,6 @@
 
   // IconButton:
   void PaintButtonContents(gfx::Canvas* canvas) override {
-    // Only draw the background when the button is toggled.
-    if (toggled()) {
-      const gfx::Rect rect(GetContentsBounds());
-      cc::PaintFlags flags;
-      flags.setAntiAlias(true);
-      flags.setColor(GetBackgroundColor());
-      flags.setStyle(cc::PaintFlags::kFill_Style);
-      canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), rect.width() / 2,
-                         flags);
-    }
-
     // Rotate the canvas to rotate the expand indicator according to toggle
     // state and shelf alignment. Note that when shelf alignment changes,
     // TrayBackgroundView::UpdateLayout() will be triggered and this button will
diff --git a/ash/system/video_conference/video_conference_tray_controller.h b/ash/system/video_conference/video_conference_tray_controller.h
index ec1ce30..1c2a65f1 100644
--- a/ash/system/video_conference/video_conference_tray_controller.h
+++ b/ash/system/video_conference/video_conference_tray_controller.h
@@ -6,8 +6,8 @@
 #define ASH_SYSTEM_VIDEO_CONFERENCE_VIDEO_CONFERENCE_TRAY_CONTROLLER_H_
 
 #include "ash/ash_export.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 #include "ash/system/video_conference/video_conference_media_state.h"
-#include "ash/system/video_conference/video_conference_tray_effects_manager.h"
 #include "base/observer_list_types.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index f5536ff..39d7955f 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -1162,30 +1162,6 @@
   }
 }
 
-void WallpaperControllerImpl::SetOnlineWallpaperFromData(
-    const OnlineWallpaperParams& params,
-    const std::string& image_data,
-    SetWallpaperCallback callback) {
-  if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted() ||
-      !CanSetUserWallpaper(params.account_id)) {
-    std::move(callback).Run(/*success=*/false);
-    return;
-  }
-
-  image_util::DecodeImageCallback decoded_callback =
-      base::BindOnce(&WallpaperControllerImpl::OnOnlineWallpaperDecoded,
-                     weak_factory_.GetWeakPtr(), params, /*save_file=*/true,
-                     std::move(callback));
-  if (bypass_decode_for_testing_) {
-    std::move(decoded_callback)
-        .Run(CreateSolidColorWallpaper(kDefaultWallpaperColor));
-    return;
-  }
-  image_util::DecodeImageData(std::move(decoded_callback),
-                              data_decoder::mojom::ImageCodec::kDefault,
-                              image_data);
-}
-
 void WallpaperControllerImpl::SetGooglePhotosWallpaper(
     const GooglePhotosWallpaperParams& params,
     WallpaperController::SetWallpaperCallback callback) {
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index ce6ec82..0543f1b5 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -255,9 +255,6 @@
                           SetWallpaperCallback callback) override;
   void SetOnlineWallpaperIfExists(const OnlineWallpaperParams& params,
                                   SetWallpaperCallback callback) override;
-  void SetOnlineWallpaperFromData(const OnlineWallpaperParams& params,
-                                  const std::string& image_data,
-                                  SetWallpaperCallback callback) override;
   void SetGooglePhotosWallpaper(const GooglePhotosWallpaperParams& params,
                                 SetWallpaperCallback callback) override;
   void SetGooglePhotosDailyRefreshAlbumId(const AccountId& account_id,
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 703f6c8..de3afb6 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -39,6 +39,7 @@
 #include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/metrics_hashes.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
@@ -678,15 +679,14 @@
     SetBypassDecode();
     SimulateUserLogin(kAccountId1);
     ClearWallpaperCount();
-    controller_->SetOnlineWallpaperFromData(
+    controller_->SetOnlineWallpaper(
         OnlineWallpaperParams(
             kAccountId1, kAssetId, GURL(path),
             /*collection_id=*/std::string(), WALLPAPER_LAYOUT_CENTER_CROPPED,
             /*preview_mode=*/false, /*from_user=*/false,
             /*daily_refresh_enabled=*/false, kUnitId,
             /*variants=*/std::vector<OnlineWallpaperVariant>()),
-        /*image_data=*/std::string(),
-        WallpaperController::SetWallpaperCallback());
+        base::DoNothing());
     RunAllTasksUntilIdle();
 
     // Change the on-screen wallpaper to a different one. (Otherwise the
@@ -1123,45 +1123,6 @@
   EXPECT_EQ(2, observer.colors_changed_count());
 }
 
-TEST_F(WallpaperControllerTest, SetOnlineWallpaperFromDataSavesFile) {
-  SetBypassDecode();
-  gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
-  SimulateUserLogin(kAccountId1);
-
-  // Verify that there's no offline wallpaper available in the beginning.
-  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
-  controller_->GetOfflineWallpaperList(base::BindLambdaForTesting(
-      [&run_loop](const std::vector<std::string>& url_list) {
-        EXPECT_TRUE(url_list.empty());
-        run_loop->Quit();
-      }));
-  run_loop->Run();
-
-  // Set an online wallpaper with image data.
-  ClearWallpaperCount();
-  controller_->SetOnlineWallpaperFromData(
-      OnlineWallpaperParams(kAccountId1, kAssetId, GURL(kDummyUrl),
-                            /*collection_id=*/std::string(),
-                            WALLPAPER_LAYOUT_CENTER_CROPPED,
-                            /*preview_mode=*/false, /*from_user=*/false,
-                            /*daily_refresh_enabled=*/false, kUnitId,
-                            /*variants=*/std::vector<OnlineWallpaperVariant>()),
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
-  RunAllTasksUntilIdle();
-
-  // Verify that the wallpaper with |url| is available offline, and the returned
-  // file name should not contain the small wallpaper suffix.
-  run_loop = std::make_unique<base::RunLoop>();
-  controller_->GetOfflineWallpaperList(base::BindLambdaForTesting(
-      [&run_loop](const std::vector<std::string>& url_list) {
-        EXPECT_EQ(1U, url_list.size());
-        EXPECT_EQ(GURL(kDummyUrl).ExtractFileName(), url_list[0]);
-        run_loop->Quit();
-      }));
-  run_loop->Run();
-}
-
 TEST_F(WallpaperControllerTest,
        UpdatePrimaryUserWallpaperWhileSecondUserActive) {
   SetBypassDecode();
@@ -1177,10 +1138,7 @@
       /*preview_mode=*/false, /*from_user=*/false,
       /*daily_refresh_enabled=*/false, kUnitId,
       /*variants=*/std::vector<OnlineWallpaperVariant>());
-  controller_->SetOnlineWallpaperFromData(
-      params,
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+  controller_->SetOnlineWallpaper(params, base::DoNothing());
   RunAllTasksUntilIdle();
   // Verify that the user wallpaper info is updated.
   EXPECT_TRUE(
@@ -1199,10 +1157,7 @@
       /*preview_mode=*/false, /*from_user=*/false,
       /*daily_refresh_enabled=*/false, kUnitId2,
       /*variants=*/std::vector<OnlineWallpaperVariant>());
-  controller_->SetOnlineWallpaperFromData(
-      new_params,
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+  controller_->SetOnlineWallpaper(new_params, base::DoNothing());
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
   EXPECT_TRUE(
@@ -1835,18 +1790,17 @@
   EXPECT_FALSE(
       pref_manager_->GetUserWallpaperInfo(kAccountId1, &wallpaper_info));
 
-  // Verify that |SetOnlineWallpaperFromData| doesn't set wallpaper in kiosk
+  // Verify that |SetOnlineWallpaper| doesn't set wallpaper in kiosk
   // mode, and |kAccountId1|'s wallpaper info is not updated.
   std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
   ClearWallpaperCount();
-  controller_->SetOnlineWallpaperFromData(
+  controller_->SetOnlineWallpaper(
       OnlineWallpaperParams(kAccountId1, kAssetId, GURL(kDummyUrl),
                             /*collection_id=*/std::string(),
                             WALLPAPER_LAYOUT_CENTER,
                             /*preview_mode=*/false, /*from_user=*/false,
                             /*daily_refresh_enabled=*/false, kUnitId,
                             /*variants=*/std::vector<OnlineWallpaperVariant>()),
-      /*image_data=*/std::string(),
       base::BindLambdaForTesting([&run_loop](bool success) {
         EXPECT_FALSE(success);
         run_loop->Quit();
@@ -1926,18 +1880,17 @@
   }
 
   {
-    // Verify that |SetOnlineWallpaperFromData| doesn't set wallpaper when
+    // Verify that |SetOnlineWallpaper| doesn't set wallpaper when
     // policy is enforced, and the user wallpaper info is not updated.
     std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
     ClearWallpaperCount();
-    controller_->SetOnlineWallpaperFromData(
+    controller_->SetOnlineWallpaper(
         OnlineWallpaperParams(
             kAccountId1, kAssetId, GURL(kDummyUrl),
             /*collection_id=*/std::string(), WALLPAPER_LAYOUT_CENTER_CROPPED,
             /*preview_mode=*/false, /*from_user=*/false,
             /*daily_refresh_enabled=*/false, kUnitId,
             /*variants=*/std::vector<OnlineWallpaperVariant>()),
-        /*image_data=*/std::string(),
         base::BindLambdaForTesting([&run_loop](bool success) {
           EXPECT_FALSE(success);
           run_loop->Quit();
@@ -1998,16 +1951,15 @@
   base::FilePath path;
   EXPECT_FALSE(controller_->GetPathFromCache(kAccountId1, &path));
 
-  // Verify |SetOnlineWallpaperFromData| updates wallpaper cache for |user1|.
-  controller_->SetOnlineWallpaperFromData(
+  // Verify |SetOnlineWallpaper| updates wallpaper cache for |user1|.
+  controller_->SetOnlineWallpaper(
       OnlineWallpaperParams(kAccountId1, kAssetId, GURL(kDummyUrl),
                             /*collection_id=*/std::string(),
                             WALLPAPER_LAYOUT_CENTER,
                             /*preview_mode=*/false, /*from_user=*/false,
                             /*daily_refresh_enabled=*/false, kUnitId,
                             /*variants=*/std::vector<OnlineWallpaperVariant>()),
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+      base::DoNothing());
   RunAllTasksUntilIdle();
   EXPECT_TRUE(
       controller_->GetWallpaperFromCache(kAccountId1, &cached_wallpaper));
@@ -2282,10 +2234,7 @@
                             /*preview_mode=*/false, /*from_user=*/false,
                             /*daily_refresh_enabled=*/false, kUnitId,
                             /*variants=*/std::vector<OnlineWallpaperVariant>());
-  controller_->SetOnlineWallpaperFromData(
-      params,
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+  controller_->SetOnlineWallpaper(params, base::DoNothing());
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kOnline);
@@ -3683,8 +3632,7 @@
   CacheOnlineWallpaper(kDummyUrl);
 
   // Attempt to set an online wallpaper without providing the image data. Verify
-  // it succeeds this time because |SetOnlineWallpaperFromData| has saved the
-  // file.
+  // it succeeds this time because |SetOnlineWallpaper| has saved the file.
   ClearWallpaperCount();
   WallpaperInfo info = WallpaperInfo(OnlineWallpaperParams(
       kAccountId1, kAssetId, GURL(kDummyUrl),
@@ -3706,8 +3654,7 @@
   SimulateUserLogin(kAccountId2);
 
   // Attempt to set an online wallpaper without providing the image data. Verify
-  // it succeeds this time because |SetOnlineWallpaperFromData| has saved the
-  // file.
+  // it succeeds this time because |SetOnlineWallpaper| has saved the file.
   ClearWallpaperCount();
   pref_manager_->SetSyncedWallpaperInfo(kAccountId1,
                                         InfoWithType(WallpaperType::kOnline));
@@ -4168,10 +4115,7 @@
   // Set an online wallpaper with image data. Verify that the wallpaper is set
   // successfully.
   ClearWallpaperCount();
-  controller_->SetOnlineWallpaperFromData(
-      params,
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+  controller_->SetOnlineWallpaper(params, base::DoNothing());
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kOnline);
@@ -4196,7 +4140,7 @@
 
   // Attempt to set an online wallpaper via |SetOnlineWallpaperIfExists| without
   // providing the image data. Verify it succeeds this time because
-  // |SetOnlineWallpaperFromData| has saved the file.
+  // |SetOnlineWallpaper| has saved the file.
   ClearWallpaperCount();
   run_loop = std::make_unique<base::RunLoop>();
   controller_->SetOnlineWallpaperIfExists(
@@ -4322,15 +4266,14 @@
   SimulateUserLogin(kAccountId1);
 
   // Set wallpaper to something a user may have chose.
-  controller_->SetOnlineWallpaperFromData(
+  controller_->SetOnlineWallpaper(
       OnlineWallpaperParams(kAccountId1, kAssetId, GURL(kDummyUrl),
                             /*collection_id=*/std::string(),
                             WALLPAPER_LAYOUT_CENTER,
                             /*preview_mode=*/false, /*from_user=*/false,
                             /*daily_refresh_enabled=*/false, kUnitId,
                             /*variants=*/std::vector<OnlineWallpaperVariant>()),
-      /*image_data=*/std::string(),
-      WallpaperController::SetWallpaperCallback());
+      base::DoNothing());
   // Let the task queue run so that we run `ShowWallpaperImage()`.
   task_environment()->RunUntilIdle();
 
diff --git a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
index c09fdd5..fbba599d 100644
--- a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
+++ b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
@@ -293,8 +293,8 @@
     SetNetworkState(kEth0DevicePath, shill::kStateOnline);
   }
 
-  void SetEthernetDisconnected() {
-    SetNetworkState(kEth0DevicePath, shill::kStateOffline);
+  void SetEthernetIdle() {
+    SetNetworkState(kEth0DevicePath, shill::kStateIdle);
   }
 
   void SetDeviceState(const std::string& type, bool enabled) {
@@ -323,9 +323,7 @@
     SetNetworkState(kWlan0DevicePath, shill::kStateOnline);
   }
 
-  void SetWifiDisconnected() {
-    SetNetworkState(kWlan0DevicePath, shill::kStateOffline);
-  }
+  void SetWifiIdle() { SetNetworkState(kWlan0DevicePath, shill::kStateIdle); }
 
   void SetWifiPortal() {
     SetNetworkState(kWlan0DevicePath, shill::kStateRedirectFound);
@@ -335,8 +333,8 @@
     SetNetworkState(kCellular0DevicePath, shill::kStateReady);
   }
 
-  void SetCellularDisconnected() {
-    SetNetworkState(kCellular0DevicePath, shill::kStateOffline);
+  void SetCellularIdle() {
+    SetNetworkState(kCellular0DevicePath, shill::kStateIdle);
   }
 
   void SetCellularOnline() {
@@ -646,7 +644,7 @@
 
   // Simulate unplug and network goes back to kNotConnected, and the active
   // guid should be cleared.
-  SetEthernetDisconnected();
+  SetEthernetIdle();
   ExpectListObserverFired(list_observer, &list_call_count);
   ExpectStateObserverFired(observer, &state_call_count);
   EXPECT_EQ(observer.GetLatestState()->state,
@@ -740,7 +738,7 @@
 
   // Simulate disconnect and network goes back to kNotConnected, and the
   // active guid should be cleared.
-  SetWifiDisconnected();
+  SetWifiIdle();
   ExpectListObserverFired(list_observer, &list_call_count);
   ExpectStateObserverFired(observer, &state_call_count);
   EXPECT_EQ(observer.GetLatestState()->state,
@@ -833,7 +831,7 @@
 
   // Simulate disconnect and network goes back to kNotConnected, and the
   // active guid should be cleared.
-  SetCellularDisconnected();
+  SetCellularIdle();
   ExpectListObserverFired(list_observer, &list_call_count);
   ExpectStateObserverFired(observer, &state_call_count);
   EXPECT_EQ(observer.GetLatestState()->state,
@@ -1139,7 +1137,7 @@
   EXPECT_EQ(eth_guid, list_observer.active_guid());
 
   // Disconnect ethernet and wifi should become the active network.
-  SetEthernetDisconnected();
+  SetEthernetIdle();
   ExpectListObserverFired(list_observer, &list_call_count);
   ExpectStateObserverFired(eth_observer, &state_call_count);
   EXPECT_EQ(eth_observer.GetLatestState()->state,
@@ -1407,14 +1405,14 @@
 
   // Now that Ethernet is disconnected, WiFi should be active and Ethernet
   // should be the second guid in the list of observer guids.
-  SetEthernetDisconnected();
+  SetEthernetIdle();
   EXPECT_FALSE(list_observer.active_guid().empty());
   EXPECT_EQ(wifi_guid, list_observer.active_guid());
   EXPECT_EQ(eth_guid, list_observer.observer_guids()[1]);
 
   // With both Ethernet and WiFi disconnected, neither of them should be
   // active and the Ethernet guid should be the first observer guid.
-  SetWifiDisconnected();
+  SetWifiIdle();
   EXPECT_TRUE(list_observer.active_guid().empty());
   EXPECT_EQ(eth_guid, list_observer.observer_guids()[0]);
   EXPECT_EQ(wifi_guid, list_observer.observer_guids()[1]);
diff --git a/ash/webui/personalization_app/resources/common/icons.html b/ash/webui/personalization_app/resources/common/icons.html
index 5004aae6..07a5ee2 100644
--- a/ash/webui/personalization_app/resources/common/icons.html
+++ b/ash/webui/personalization_app/resources/common/icons.html
@@ -74,6 +74,9 @@
        <g id="exit_fullscreen">
          <path fill="#E8EAED" d="m6.43718,8.65127l5.01667,-4.76625l-1.27436,-1.23375l-7.17949,7l7.17949,7l1.26538,-1.2338l-5.00769,-4.7662l10.56282,0l0,-2l-10.56282,0z" clip-rule="evenodd" fill-rule="evenodd"/>
        </g>
+       <g id="fullscreen">
+         <path d="M4 16v-4h1.5v2.5H8V16Zm0-8V4h4v1.5H5.5V8Zm8 8v-1.5h2.5V12H16v4Zm2.5-8V5.5H12V4h4v4Z"></path>
+       </g>
        <g id="confirm_selection">
          <path d="M7.49999 13.475L4.02499 9.99999L2.84166 11.175L7.49999 15.8333L17.5 5.83333L16.325 4.65833L7.49999 13.475Z"/>
        </g>
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 56a3ece7..023a595e 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
@@ -25,13 +25,6 @@
     grid-template-rows: auto minmax(158px, 220px) 20px auto 106px 24px;
   }
 
-  .preview-button {
-    bottom: 0;
-    left: 50%;
-    position: absolute;
-    transform: translate(-50%, -50%);
-  }
-
   #messageContainer {
     align-items: center;
     display: flex;
@@ -84,14 +77,13 @@
     grid-template-areas:
       '.              . .'
       'image          . subpage-desc'
+      'image          . buttons'
       '.              . .';
     grid-template-columns: 224px 32px minmax(0,1fr);
-    grid-template-rows: 20px 152px 20px;
+    grid-template-rows: 20px 118px 34px 20px;
   }
 
-  :host(:not([main-page])) #container {
-    border: none;
-    display: grid;
+  :host(:not([main-page])) #container.ambient-mode-disabled {
     grid-template-areas:
       'image          . subpage-desc'
       '.              . .';
@@ -99,12 +91,25 @@
     grid-template-rows: 152px 20px;
   }
 
+  :host(:not([main-page])) #container {
+    border: none;
+    display: grid;
+    grid-template-areas:
+      'image          . subpage-desc'
+      'image          . buttons'
+      '.              . .';
+    grid-template-columns: 224px 32px minmax(0,1fr);
+    grid-template-rows: 118px 34px 20px;
+  }
+
+  :host([main-page]) #buttonContainer,
   :host([main-page]) .currently-set-text {
     display: none;
   }
 
+  :host(:not([main-page])) #buttonContainer,
   :host(:not([main-page])) .currently-set-text {
-    display: block;
+    display: inline-flex;
   }
 
   :host([main-page]) #imageContainer,
@@ -213,6 +218,29 @@
     object-fit: cover;
     width: 100%;
   }
+
+  #buttonContainer {
+    grid-area: buttons;
+  }
+
+  #buttonContainer .text {
+    margin-inline-start: 8px;
+  }
+  #buttonContainer .disabled {
+    cursor: wait;
+    pointer-events: none;
+  }
+
+  #buttonContainer cr-button {
+    border-color: var(--cros-button-stroke-color-secondary);
+    border-radius: 16px;
+  }
+
+  #buttonContainer .spinner {
+    height: 20px;
+    width: 20px;
+  }
+
 </style>
 <div class$="[[getPreviewContainerClass_(ambientModeEnabled_, loading_)]]" id="container">
   <slot></slot>
@@ -293,11 +321,6 @@
                 alt$="[[getAlbumTitle_(firstPreviewAlbum_)]]"
                 is-google-photos>
           </template>
-          <div hidden$="[[!isScreenSaverPreviewEnabled_()]]">
-            <cr-button class="preview-button" on-click="startScreenSaverPreview_">
-              Preview
-            </cr-button>
-          </div>
         </div>
         <h3 id="textContainer" class="preview-text-container album-info-mainpage album-info-subpage"
             aria-label$="[[getPreviewTextAriaLabel_(firstPreviewAlbum_, topicSource_, previewAlbums_)]]">
@@ -314,6 +337,13 @@
             [[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_"
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 ac9fb05..9fe8030 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
@@ -9,6 +9,7 @@
 
 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';
@@ -19,7 +20,7 @@
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 
 import {setErrorAction} from '../personalization_actions.js';
-import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AmbientUiVisibility, TopicSource} from '../personalization_app.mojom-webui.js';
 import {logAmbientModeOptInUMA} from '../personalization_metrics_logger.js';
 import {Paths, PersonalizationRouter} from '../personalization_router_element.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
@@ -86,6 +87,14 @@
         computed:
             'computeCollageImages_(topicSource_, previewAlbums_, googlePhotosAlbumsPreviews_)',
       },
+      ambientUiVisibility_: {
+        type: AmbientUiVisibility,
+        value: null,
+      },
+      screenSaverPreviewActive_: {
+        type: Boolean,
+        computed: 'computeScreenSaverPreviewActive_(ambientUiVisibility_)',
+      },
     };
   }
 
@@ -99,6 +108,8 @@
   private loading_: boolean;
   private googlePhotosAlbumsPreviews_: Url[]|null;
   private collageImages_: Url[];
+  private ambientUiVisibility_: AmbientUiVisibility|null;
+  private screenSaverPreviewActive_: boolean;
 
   private loadingTimeoutId_: number|null = null;
 
@@ -116,6 +127,8 @@
         'googlePhotosAlbumsPreviews_',
         state => state.ambient.googlePhotosAlbumsPreviews);
     this.watch('topicSource_', state => state.ambient.topicSource);
+    this.watch(
+        'ambientUiVisibility_', state => state.ambient.ambientUiVisibility);
     this.updateFromStore();
   }
 
@@ -123,10 +136,24 @@
     return loadTimeData.getBoolean('isScreenSaverPreviewEnabled');
   }
 
-  private startScreenSaverPreview_() {
+  private startScreenSaverPreview_(event: Event) {
+    event.stopPropagation();
     startScreenSaverPreview(getAmbientProvider());
   }
 
+  private getScreenSaverPreviewClass_(): string {
+    return this.screenSaverPreviewActive_ ? 'disabled' : '';
+  }
+
+  private getScreenSaverPreviewText_(): string {
+    // // TODO(b/262277167) replace temporary strings
+    return this.screenSaverPreviewActive_ ? 'Downloading' : 'Preview';
+  }
+
+  private computeScreenSaverPreviewActive_(): boolean {
+    return this.ambientUiVisibility_ === AmbientUiVisibility.kPreview;
+  }
+
   private computeLoading_(): boolean {
     return this.ambientModeEnabled_ === null || this.albums_ === null ||
         this.topicSource_ === null || this.googlePhotosAlbumsPreviews_ === null;
@@ -219,6 +246,11 @@
     if (this.ambientModeEnabled_ || this.loading_) {
       classes.push('zero-state-disabled');
     }
+
+    if (!this.ambientModeEnabled_) {
+      classes.push('ambient-mode-disabled');
+    }
+
     /* TODO(b/253470553): Remove this condition after Ambient subpage UI change
      * is released. */
     if (!loadTimeData.getBoolean('isAmbientSubpageUIChangeEnabled')) {
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
index cc5f54a..011b7178 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
@@ -147,6 +147,10 @@
     --personalization-app-grid-item-background-color: var(--google-grey-200);
   }
 
+  img {
+    user-select: none;
+  }
+
 </style>
 <template is="dom-if" if="[[hasError_]]">
   <wallpaper-error></wallpaper-error>
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_grid_item_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_grid_item_element.html
index 82fce3e6..e462f80 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_grid_item_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_grid_item_element.html
@@ -65,6 +65,7 @@
 
   img {
     object-fit: cover;
+    user-select: none;
   }
 
   :host(:not([collage])) img {
diff --git a/ash/wm/float/float_controller.cc b/ash/wm/float/float_controller.cc
index ebb053ae..3f1a6fc98da 100644
--- a/ash/wm/float/float_controller.cc
+++ b/ash/wm/float/float_controller.cc
@@ -36,6 +36,7 @@
 #include "ui/aura/window_delegate.h"
 #include "ui/aura/window_observer.h"
 #include "ui/display/screen.h"
+#include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
 
@@ -257,8 +258,9 @@
   if (window->GetProperty(app_restore::kLaunchedFromAppRestoreKey))
     return window->bounds();
 
-  auto* work_area_insets = WorkAreaInsets::ForWindow(window->GetRootWindow());
-  const gfx::Rect work_area = work_area_insets->user_work_area_bounds();
+  gfx::Rect work_area = WorkAreaInsets::ForWindow(window->GetRootWindow())
+                            ->user_work_area_bounds();
+  wm::ConvertRectFromScreen(window->GetRootWindow(), &work_area);
 
   gfx::Rect preferred_bounds =
       WindowState::Get(window)->HasRestoreBounds()
@@ -283,9 +285,11 @@
 
 gfx::Rect FloatController::GetPreferredFloatWindowTabletBounds(
     aura::Window* floated_window) const {
-  const gfx::Rect work_area =
+  gfx::Rect work_area =
       WorkAreaInsets::ForWindow(floated_window->GetRootWindow())
           ->user_work_area_bounds();
+  wm::ConvertRectFromScreen(floated_window->GetRootWindow(), &work_area);
+
   const bool landscape =
       chromeos::wm::IsLandscapeOrientationForWindow(floated_window);
   const gfx::Size preferred_size =
diff --git a/ash/wm/float/float_controller.h b/ash/wm/float/float_controller.h
index c8271196..b8532b0f 100644
--- a/ash/wm/float/float_controller.h
+++ b/ash/wm/float/float_controller.h
@@ -54,11 +54,11 @@
   FloatController& operator=(const FloatController&) = delete;
   ~FloatController() override;
 
-  // Returns float window bounds in clamshell mode.
+  // Returns float window bounds in clamshell mode in root window coordinates.
   static gfx::Rect GetPreferredFloatWindowClamshellBounds(aura::Window* window);
 
   // Gets the ideal float bounds of `floated_window` in tablet mode if it were
-  // to be floated.
+  // to be floated, in root window coordinates.
   gfx::Rect GetPreferredFloatWindowTabletBounds(
       aura::Window* floated_window) const;
 
diff --git a/ash/wm/float/float_controller_unittest.cc b/ash/wm/float/float_controller_unittest.cc
index 24ef635e..a2aa888 100644
--- a/ash/wm/float/float_controller_unittest.cc
+++ b/ash/wm/float/float_controller_unittest.cc
@@ -294,6 +294,23 @@
   EXPECT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
 }
 
+// Tests that windows that are floated on non-primary displays are onscreen.
+// Regression test for b/261860554.
+TEST_F(WindowFloatTest, FloatOnOtherDisplay) {
+  UpdateDisplay("1200x800,1201+0-1200x800");
+
+  // Create a window on the secondary display.
+  std::unique_ptr<aura::Window> window =
+      CreateAppWindow(gfx::Rect(1200, 0, 300, 300));
+  ASSERT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
+
+  // After floating, the bounds of `window` should be full contained by the
+  // secondary display bounds.
+  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+  EXPECT_TRUE(
+      gfx::Rect(1200, 0, 1200, 800).Contains(window->GetBoundsInScreen()));
+}
+
 // Test float window per desk logic.
 TEST_F(WindowFloatTest, OneFloatWindowPerDeskLogic) {
   // Test one float window per desk is allowed.
diff --git a/base/allocator/partition_allocator/partition_alloc.gni b/base/allocator/partition_allocator/partition_alloc.gni
index 5d9fb059..04abf46 100644
--- a/base/allocator/partition_allocator/partition_alloc.gni
+++ b/base/allocator/partition_allocator/partition_alloc.gni
@@ -109,7 +109,6 @@
   #   dangling during their lifetime.
   # - backup_ref_ptr_poison_oob_ptr: poison out-of-bounds (OOB) pointers to
   #   generate an exception in the event that an OOB pointer is dereferenced.
-  #   TODO(mdlemay): Does not yet work on Windows.
   put_ref_count_in_previous_slot =
       put_ref_count_in_previous_slot_default && enable_backup_ref_ptr_support
 
diff --git a/base/debug/activity_tracker_unittest.cc b/base/debug/activity_tracker_unittest.cc
index 0d434d2a..0938c69b 100644
--- a/base/debug/activity_tracker_unittest.cc
+++ b/base/debug/activity_tracker_unittest.cc
@@ -30,16 +30,19 @@
 
 namespace {
 
-class TestActivityTracker : public ThreadActivityTracker {
+class TestActivityTracker {
  public:
   TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size)
-      : ThreadActivityTracker(memset(memory.get(), 0, mem_size), mem_size),
-        mem_segment_(std::move(memory)) {}
+      : mem_segment_(std::move(memory)),
+        tracker_(memset(mem_segment_.get(), 0, mem_size), mem_size) {}
 
-  ~TestActivityTracker() override = default;
+  ~TestActivityTracker() = default;
+
+  ThreadActivityTracker& tracker() { return tracker_; }
 
  private:
-  std::unique_ptr<char[]> mem_segment_;
+  std::unique_ptr<char[]> mem_segment_;  // Must outlive `tracker_`
+  ThreadActivityTracker tracker_;
 };
 
 }  // namespace
@@ -62,7 +65,7 @@
     }
   }
 
-  std::unique_ptr<ThreadActivityTracker> CreateActivityTracker() {
+  std::unique_ptr<TestActivityTracker> CreateActivityTracker() {
     std::unique_ptr<char[]> memory(new char[kStackSize]);
     return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize);
   }
@@ -156,17 +159,17 @@
 }
 
 TEST_F(ActivityTrackerTest, PushPopTest) {
-  std::unique_ptr<ThreadActivityTracker> tracker = CreateActivityTracker();
+  std::unique_ptr<TestActivityTracker> tracker = CreateActivityTracker();
   ThreadActivityTracker::Snapshot snapshot;
 
-  ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
+  ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
   ASSERT_EQ(0U, snapshot.activity_stack_depth);
   ASSERT_EQ(0U, snapshot.activity_stack.size());
 
   char origin1;
-  ActivityId id1 = tracker->PushActivity(&origin1, Activity::ACT_TASK,
-                                         ActivityData::ForTask(11));
-  ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
+  ActivityId id1 = tracker->tracker().PushActivity(&origin1, Activity::ACT_TASK,
+                                                   ActivityData::ForTask(11));
+  ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
   ASSERT_EQ(1U, snapshot.activity_stack_depth);
   ASSERT_EQ(1U, snapshot.activity_stack.size());
   EXPECT_NE(0, snapshot.activity_stack[0].time_internal);
@@ -177,9 +180,9 @@
 
   char origin2;
   char lock2;
-  ActivityId id2 = tracker->PushActivity(&origin2, Activity::ACT_LOCK,
-                                         ActivityData::ForLock(&lock2));
-  ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
+  ActivityId id2 = tracker->tracker().PushActivity(
+      &origin2, Activity::ACT_LOCK, ActivityData::ForLock(&lock2));
+  ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
   ASSERT_EQ(2U, snapshot.activity_stack_depth);
   ASSERT_EQ(2U, snapshot.activity_stack.size());
   EXPECT_LE(snapshot.activity_stack[0].time_internal,
@@ -190,8 +193,8 @@
   EXPECT_EQ(reinterpret_cast<uintptr_t>(&lock2),
             snapshot.activity_stack[1].data.lock.lock_address);
 
-  tracker->PopActivity(id2);
-  ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
+  tracker->tracker().PopActivity(id2);
+  ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
   ASSERT_EQ(1U, snapshot.activity_stack_depth);
   ASSERT_EQ(1U, snapshot.activity_stack.size());
   EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
@@ -199,8 +202,8 @@
             snapshot.activity_stack[0].origin_address);
   EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id);
 
-  tracker->PopActivity(id1);
-  ASSERT_TRUE(tracker->CreateSnapshot(&snapshot));
+  tracker->tracker().PopActivity(id1);
+  ASSERT_TRUE(tracker->tracker().CreateSnapshot(&snapshot));
   ASSERT_EQ(0U, snapshot.activity_stack_depth);
   ASSERT_EQ(0U, snapshot.activity_stack.size());
 }
diff --git a/base/i18n/break_iterator.cc b/base/i18n/break_iterator.cc
index e4d019d..a21c24a 100644
--- a/base/i18n/break_iterator.cc
+++ b/base/i18n/break_iterator.cc
@@ -19,24 +19,6 @@
 namespace base {
 namespace i18n {
 
-const size_t npos = static_cast<size_t>(-1);
-
-BreakIterator::BreakIterator(const StringPiece16& str, BreakType break_type)
-    : iter_(nullptr),
-      string_(str),
-      break_type_(break_type),
-      prev_(npos),
-      pos_(0) {}
-
-BreakIterator::BreakIterator(const StringPiece16& str,
-                             const std::u16string& rules)
-    : iter_(nullptr),
-      string_(str),
-      rules_(rules),
-      break_type_(RULE_BASED),
-      prev_(npos),
-      pos_(0) {}
-
 namespace {
 
 // We found the usage pattern of break iterator is to create, use and destroy.
@@ -120,6 +102,12 @@
 
 }  // namespace
 
+BreakIterator::BreakIterator(StringPiece16 str, BreakType break_type)
+    : string_(str), break_type_(break_type) {}
+
+BreakIterator::BreakIterator(StringPiece16 str, const std::u16string& rules)
+    : string_(str), rules_(rules), break_type_(RULE_BASED) {}
+
 BreakIterator::~BreakIterator() {
   if (iter_) {
     UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
diff --git a/base/i18n/break_iterator.h b/base/i18n/break_iterator.h
index 0ca8874..78591a6 100644
--- a/base/i18n/break_iterator.h
+++ b/base/i18n/break_iterator.h
@@ -95,13 +95,15 @@
     IS_LINE_OR_CHAR_BREAK
   };
 
+  static constexpr size_t npos = static_cast<size_t>(-1);
+
   // Requires |str| to live as long as the BreakIterator does.
-  BreakIterator(const StringPiece16& str, BreakType break_type);
+  BreakIterator(StringPiece16 str, BreakType break_type);
   // Make a rule-based iterator. BreakType == RULE_BASED is implied.
   // TODO(andrewhayden): This signature could easily be misinterpreted as
   // "(const std::u16string& str, const std::u16string& locale)". We should do
   // something better.
-  BreakIterator(const StringPiece16& str, const std::u16string& rules);
+  BreakIterator(StringPiece16 str, const std::u16string& rules);
 
   BreakIterator(const BreakIterator&) = delete;
   BreakIterator& operator=(const BreakIterator&) = delete;
@@ -177,7 +179,7 @@
   // This is actually an ICU UBreakiterator* type, which turns out to be
   // a typedef for a void* in the ICU headers. Using void* directly prevents
   // callers from needing access to the ICU public headers directory.
-  raw_ptr<void, DanglingUntriaged> iter_;
+  raw_ptr<void, DanglingUntriaged> iter_ = nullptr;
 
   // The string we're iterating over. Can be changed with SetText(...)
   StringPiece16 string_;
@@ -186,10 +188,11 @@
   const std::u16string rules_;
 
   // The breaking style (word/space/newline). Mutually exclusive with rules_
-  BreakType break_type_;
+  const BreakType break_type_;
 
   // Previous and current iterator positions.
-  size_t prev_, pos_;
+  size_t prev_ = npos;
+  size_t pos_ = 0;
 };
 
 }  // namespace i18n
diff --git a/base/memory/raw_ptr.h b/base/memory/raw_ptr.h
index 155905fe..f2273362 100644
--- a/base/memory/raw_ptr.h
+++ b/base/memory/raw_ptr.h
@@ -537,6 +537,9 @@
   template <typename T>
   static PA_ALWAYS_INLINE T* SafelyUnwrapPtrForDereference(T* wrapped_ptr) {
 #if BUILDFLAG(PA_DCHECK_IS_ON) || BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
+#if defined(PA_USE_OOB_POISON)
+    PA_BASE_CHECK(!IsPtrOOB(wrapped_ptr));
+#endif
     uintptr_t address = partition_alloc::UntagPtr(wrapped_ptr);
     if (IsSupportedAndNotNull(address)) {
       PA_BASE_CHECK(wrapped_ptr != nullptr);
@@ -550,16 +553,31 @@
   // function must handle nullptr gracefully.
   template <typename T>
   static PA_ALWAYS_INLINE T* SafelyUnwrapPtrForExtraction(T* wrapped_ptr) {
-    // This may be used for extracting an end-of-allocation pointer to be used
-    // as an endpoint in an iterative algorithm, so this removes the OOB poison
-    // bit.
-    return UnpoisonPtr(wrapped_ptr);
+    T* unpoisoned_ptr = UnpoisonPtr(wrapped_ptr);
+#if defined(PA_USE_OOB_POISON)
+    // Some code uses invalid pointer values as indicators, so those values must
+    // be passed through unchanged during extraction. The following check will
+    // pass invalid values through if those values do not fall within the BRP
+    // pool after being unpoisoned.
+    if (!IsSupportedAndNotNull(partition_alloc::UntagPtr(unpoisoned_ptr))) {
+      return wrapped_ptr;
+    }
+    // Poison-based OOB checks do not extend to extracted pointers. The
+    // alternative of retaining poison on extracted pointers could introduce new
+    // OOB conditions, e.g., in code that extracts an end-of-allocation pointer
+    // for use in a loop termination condition. The poison bit would make that
+    // pointer appear to reference a very high address.
+#endif
+    return unpoisoned_ptr;
   }
 
   // Unwraps the pointer, without making an assertion on whether memory was
   // freed or not.
   template <typename T>
   static PA_ALWAYS_INLINE T* UnsafelyUnwrapPtrForComparison(T* wrapped_ptr) {
+    // This may be used for unwrapping an end-of-allocation pointer to be used
+    // as an endpoint in an iterative algorithm, so this removes the OOB poison
+    // bit.
     return UnpoisonPtr(wrapped_ptr);
   }
 
@@ -579,7 +597,8 @@
             typename = std::enable_if_t<offset_type<Z>, void>>
   static PA_ALWAYS_INLINE T* Advance(T* wrapped_ptr, Z delta_elems) {
 #if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
-    T* new_ptr = UnpoisonPtr(wrapped_ptr) + delta_elems;
+    T* unpoisoned_ptr = UnpoisonPtr(wrapped_ptr);
+    T* new_ptr = unpoisoned_ptr + delta_elems;
     // First check if the new address didn't migrate in/out the BRP pool, and
     // that it lands within the same allocation. An end-of-allocation address is
     // ok, too, and that may lead to the pointer being poisoned if the relevant
@@ -593,7 +612,7 @@
     // ref-count to go to 0 upon this pointer's destruction, even though there
     // may be another pointer still pointing to it, thus making it lose the BRP
     // protection prematurely.
-    uintptr_t address = partition_alloc::UntagPtr(UnpoisonPtr(wrapped_ptr));
+    uintptr_t address = partition_alloc::UntagPtr(unpoisoned_ptr);
     // TODO(bartekn): Consider adding support for non-BRP pools too (without
     // removing the cross-pool migration check).
     if (IsSupportedAndNotNull(address)) {
diff --git a/base/memory/raw_ptr_unittest.cc b/base/memory/raw_ptr_unittest.cc
index aa9f99e..034232ca 100644
--- a/base/memory/raw_ptr_unittest.cc
+++ b/base/memory/raw_ptr_unittest.cc
@@ -212,6 +212,19 @@
               CountingRawPtrHasCounts());
 }
 
+TEST_F(RawPtrTest, InvalidExtractNoDereference) {
+  // Some code uses invalid pointer values as indicators, so those values must
+  // be accepted by raw_ptr and passed through unchanged during extraction.
+  int* inv_ptr = reinterpret_cast<int*>(~static_cast<uintptr_t>(0));
+  CountingRawPtr<int> ptr = inv_ptr;
+  int* raw = ptr;
+  EXPECT_EQ(raw, inv_ptr);
+  EXPECT_THAT((CountingRawPtrExpectations{.get_for_dereference_cnt = 0,
+                                          .get_for_extraction_cnt = 1,
+                                          .get_for_comparison_cnt = 0}),
+              CountingRawPtrHasCounts());
+}
+
 TEST_F(RawPtrTest, NullCmpExplicit) {
   CountingRawPtr<int> ptr = nullptr;
   EXPECT_TRUE(ptr == nullptr);
@@ -1839,17 +1852,91 @@
   ASSERT_EQ(
       requested_size,
       allocator_.root()->AllocationCapacityFromRequestedSize(requested_size));
+  size_t requested_elements = requested_size / sizeof(int);
 
   int* ptr =
       reinterpret_cast<int*>(allocator_.root()->Alloc(requested_size, ""));
-  raw_ptr<int> protected_ptr = ptr;
+  int* ptr_end = ptr + requested_elements;
+
+  RawPtrCountingImpl::ClearCounters();
+
+  CountingRawPtr<int> protected_ptr = ptr;
+  CountingRawPtr<int> protected_ptr_end = protected_ptr + requested_elements;
+
+#if defined(PA_USE_OOB_POISON)
+  EXPECT_DEATH_IF_SUPPORTED(*protected_ptr_end = 1, "");
+#endif
 
   int gen_val = 1;
-  std::generate(protected_ptr, protected_ptr + requested_size / sizeof(int),
-                [&gen_val]() {
-                  gen_val ^= gen_val + 1;
-                  return gen_val;
-                });
+  std::generate(protected_ptr, protected_ptr_end, [&gen_val]() {
+    gen_val ^= gen_val + 1;
+    return gen_val;
+  });
+
+  EXPECT_THAT((CountingRawPtrExpectations{
+                  .get_for_dereference_cnt = requested_elements,
+                  .get_for_extraction_cnt = 0,
+                  .get_for_comparison_cnt = (requested_elements + 1) * 2,
+              }),
+              CountingRawPtrHasCounts());
+
+  RawPtrCountingImpl::ClearCounters();
+
+  for (CountingRawPtr<int> protected_ptr_i = protected_ptr;
+       protected_ptr_i < protected_ptr_end; protected_ptr_i++) {
+    *protected_ptr_i ^= *protected_ptr_i + 1;
+  }
+
+  EXPECT_THAT((CountingRawPtrExpectations{
+                  .get_for_dereference_cnt = requested_elements * 2,
+                  .get_for_extraction_cnt = 0,
+                  .get_for_comparison_cnt = (requested_elements + 1) * 2,
+              }),
+              CountingRawPtrHasCounts());
+
+  RawPtrCountingImpl::ClearCounters();
+
+  for (CountingRawPtr<int> protected_ptr_i = protected_ptr;
+       protected_ptr_i < ptr_end; protected_ptr_i++) {
+    *protected_ptr_i ^= *protected_ptr_i + 1;
+  }
+
+  EXPECT_THAT((CountingRawPtrExpectations{
+                  .get_for_dereference_cnt = requested_elements * 2,
+                  .get_for_extraction_cnt = 0,
+                  .get_for_comparison_cnt = requested_elements + 1,
+              }),
+              CountingRawPtrHasCounts());
+
+  RawPtrCountingImpl::ClearCounters();
+
+  for (int* ptr_i = ptr; ptr_i < protected_ptr_end; ptr_i++) {
+    *ptr_i ^= *ptr_i + 1;
+  }
+
+  EXPECT_THAT((CountingRawPtrExpectations{
+                  .get_for_dereference_cnt = 0,
+                  .get_for_extraction_cnt = 0,
+                  .get_for_comparison_cnt = requested_elements + 1,
+              }),
+              CountingRawPtrHasCounts());
+
+  RawPtrCountingImpl::ClearCounters();
+
+  size_t iter_cnt = 0;
+  for (int *ptr_i = protected_ptr, *ptr_i_end = protected_ptr_end;
+       ptr_i < ptr_i_end; ptr_i++) {
+    *ptr_i ^= *ptr_i + 1;
+    iter_cnt++;
+  }
+  EXPECT_EQ(iter_cnt, requested_elements);
+
+  EXPECT_THAT((CountingRawPtrExpectations{
+                  .get_for_dereference_cnt = 0,
+                  .get_for_extraction_cnt = 2,
+                  .get_for_comparison_cnt = 0,
+              }),
+              CountingRawPtrHasCounts());
 
   allocator_.root()->Free(ptr);
 }
@@ -1868,6 +1955,14 @@
   // access is disallowed:
   EXPECT_DEATH_IF_SUPPORTED(*protected_ptr2 = ' ', "");
 
+  // Assignment from a poisoned pointer should be allowed.
+  raw_ptr<char> protected_ptr3;
+  protected_ptr3 = protected_ptr1;
+
+  // The poison bit should be propagated via the assignment such that the OOB
+  // access is disallowed:
+  EXPECT_DEATH_IF_SUPPORTED(*protected_ptr3 = ' ', "");
+
   allocator_.root()->Free(ptr);
 }
 #endif  // PA_USE_OOB_POISON
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index ed8d069..d01ce50 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-11.20221209.0.1
+11.20221212.3.1
diff --git a/build/fuchsia/test/log_manager.py b/build/fuchsia/test/log_manager.py
index b3f1a34..eb22e3c 100755
--- a/build/fuchsia/test/log_manager.py
+++ b/build/fuchsia/test/log_manager.py
@@ -13,7 +13,7 @@
 from contextlib import AbstractContextManager
 from typing import Iterable, Optional, TextIO
 
-from common import read_package_paths, register_common_args, \
+from common import SDK_ROOT, read_package_paths, register_common_args, \
                    register_device_args, run_continuous_ffx_command, \
                    run_ffx_command
 from ffx_integration import ScopedFfxConfig
@@ -119,7 +119,11 @@
                                               target_id,
                                               stdout=subprocess.PIPE)
         log_manager.add_log_process(log_proc)
-        symbolize_cmd = (['debug', 'symbolize', '--', '--omit-module-lines'])
+        symbolize_cmd = ([
+            'debug', 'symbolize', '--', '--omit-module-lines',
+            '--build-id-dir',
+            os.path.join(SDK_ROOT, '.build-id')
+        ])
         for symbol_path in symbol_paths:
             symbolize_cmd.extend(['--ids-txt', symbol_path])
         log_manager.add_log_process(
diff --git a/build/fuchsia/test/log_manager_unittests.py b/build/fuchsia/test/log_manager_unittests.py
index a9be7dd..6cc2f98 100755
--- a/build/fuchsia/test/log_manager_unittests.py
+++ b/build/fuchsia/test/log_manager_unittests.py
@@ -4,12 +4,15 @@
 # found in the LICENSE file.
 """File for testing log_manager.py."""
 
+import os
 import sys
 import unittest
 import unittest.mock as mock
 
 import log_manager
 
+from common import SDK_ROOT
+
 _LOGS_DIR = 'test_logs_dir'
 
 
@@ -68,8 +71,9 @@
         self.assertEqual(mock_ffx.call_args_list[0][0][0],
                          ['log', '--no-symbols'])
         self.assertEqual(mock_ffx.call_args_list[1][0][0], [
-            'debug', 'symbolize', '--', '--omit-module-lines', '--ids-txt',
-            'ids.txt'
+            'debug', 'symbolize', '--', '--omit-module-lines',
+            '--build-id-dir',
+            os.path.join(SDK_ROOT, '.build-id'), '--ids-txt', 'ids.txt'
         ])
         self.assertEqual(mock_ffx.call_count, 2)
 
diff --git a/build/fuchsia/test/run_executable_test.py b/build/fuchsia/test/run_executable_test.py
index 023214b6..c01a1b1 100755
--- a/build/fuchsia/test/run_executable_test.py
+++ b/build/fuchsia/test/run_executable_test.py
@@ -13,8 +13,9 @@
 
 from typing import List, Optional
 
-from common import get_component_uri, get_host_arch, register_common_args, \
-                   register_device_args, register_log_args, run_ffx_command
+from common import SDK_ROOT, get_component_uri, get_host_arch, \
+                   register_common_args, register_device_args, \
+                   register_log_args, run_ffx_command
 from compatible_utils import map_filter_file_to_package_file
 from ffx_integration import FfxTestRunner
 from test_runner import TestRunner
@@ -190,7 +191,11 @@
                 get_component_uri(self._test_name), test_args, self._target_id)
 
             # Symbolize output from test process and print to terminal.
-            symbolize_cmd = ['debug', 'symbolize', '--']
+            symbolize_cmd = [
+                'debug', 'symbolize', '--', '--omit-module-lines',
+                '--build-id-dir',
+                os.path.join(SDK_ROOT, '.build-id')
+            ]
             for pkg_path in self._package_deps.values():
                 symbol_path = os.path.join(os.path.dirname(pkg_path),
                                            'ids.txt')
@@ -199,6 +204,7 @@
                             stdin=test_proc.stdout,
                             stdout=sys.stdout,
                             stderr=subprocess.STDOUT)
+
             if test_proc.wait() == 0:
                 logging.info('Process exited normally with status code 0.')
             else:
diff --git a/build/rust/std/BUILD.gn b/build/rust/std/BUILD.gn
index e376118..54b8e985 100644
--- a/build/rust/std/BUILD.gn
+++ b/build/rust/std/BUILD.gn
@@ -30,11 +30,14 @@
 import("//build/config/rust.gni")
 
 if (toolchain_has_rust) {
-  # List of Rust stdlib rlibs which are present in the official
-  # Rust toolchain we are using from the Android team. This is usually
-  # a version or two behind nightly.
-  # See //docs/security/rust-toolchain.md#building-on-non_linux-platforms
-  # for how to maintain this list.
+  # List of Rust stdlib rlibs which are present in the official Rust toolchain
+  # we are using from the Android team. This is usually a version or two behind
+  # nightly. Generally this matches the toolchain we build ourselves, but if
+  # they differ, append or remove libraries based on the
+  # `use_chromium_rust_toolchain` GN variable.
+  #
+  # If the build fails due to missing symbols, it would be because of a missing
+  # library that needs to be added here in a newer stdlib.
   stdlib_files = [
     "std",  # List first because it makes depfiles more debuggable (see below)
     "addr2line",
@@ -60,10 +63,6 @@
     "unwind",
   ]
 
-  if (use_chromium_rust_toolchain) {
-    stdlib_files += [ "cfg_if-2" ]
-  }
-
   # Different Rust toolchains may add or remove files relative to the above
   # list. That can be specified in gn args for anyone using (for instance)
   # nightly or some other experimental toolchain, prior to it becoming official.
diff --git a/cc/paint/paint_op_buffer_iterator.h b/cc/paint/paint_op_buffer_iterator.h
index 9050815..c5c8977 100644
--- a/cc/paint/paint_op_buffer_iterator.h
+++ b/cc/paint/paint_op_buffer_iterator.h
@@ -19,9 +19,9 @@
   explicit Iterator(const PaintOpBuffer* buffer)
       : Iterator(buffer, buffer->data_.get(), 0u) {}
 
-  PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
-  PaintOp* operator->() const { return get(); }
-  PaintOp& operator*() const { return *get(); }
+  const PaintOp* get() const { return reinterpret_cast<const PaintOp*>(ptr_); }
+  const PaintOp* operator->() const { return get(); }
+  const PaintOp& operator*() const { return *get(); }
   Iterator begin() const { return Iterator(buffer_); }
   Iterator end() const {
     return Iterator(buffer_, buffer_->data_.get() + buffer_->used_,
@@ -45,7 +45,7 @@
   explicit operator bool() const { return op_offset_ < buffer_->used_; }
 
  private:
-  Iterator(const PaintOpBuffer* buffer, char* ptr, size_t op_offset)
+  Iterator(const PaintOpBuffer* buffer, const char* ptr, size_t op_offset)
       : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {
     DCHECK(!buffer->are_ops_destroyed());
   }
@@ -53,7 +53,7 @@
   // `buffer_` and `ptr_` are not a raw_ptr<...> for performance reasons
   // (based on analysis of sampling profiler data and tab_search:top100:2020).
   RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
-  RAW_PTR_EXCLUSION char* ptr_ = nullptr;
+  RAW_PTR_EXCLUSION const char* ptr_ = nullptr;
 
   size_t op_offset_ = 0;
 };
@@ -74,9 +74,9 @@
     ptr_ += op_offset_;
   }
 
-  PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
-  PaintOp* operator->() const { return get(); }
-  PaintOp& operator*() const { return *get(); }
+  const PaintOp* get() const { return reinterpret_cast<const PaintOp*>(ptr_); }
+  const PaintOp* operator->() const { return get(); }
+  const PaintOp& operator*() const { return *get(); }
   OffsetIterator begin() const { return OffsetIterator(buffer_, offsets_); }
   OffsetIterator end() const {
     return OffsetIterator(buffer_, buffer_->data_.get() + buffer_->used_,
@@ -116,7 +116,7 @@
 
  private:
   OffsetIterator(const PaintOpBuffer* buffer,
-                 char* ptr,
+                 const char* ptr,
                  size_t op_offset,
                  const std::vector<size_t>* offsets)
       : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {
@@ -127,7 +127,7 @@
   // reasons (based on analysis of sampling profiler data and
   // tab_search:top100:2020).
   RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
-  RAW_PTR_EXCLUSION char* ptr_ = nullptr;
+  RAW_PTR_EXCLUSION const char* ptr_ = nullptr;
   RAW_PTR_EXCLUSION const std::vector<size_t>* offsets_;
 
   size_t op_offset_ = 0;
@@ -143,11 +143,11 @@
   CompositeIterator(const CompositeIterator& other);
   CompositeIterator(CompositeIterator&& other);
 
-  PaintOp* get() const {
+  const PaintOp* get() const {
     return absl::visit([](const auto& iter) { return iter.get(); }, iter_);
   }
-  PaintOp* operator->() const { return get(); }
-  PaintOp& operator*() const { return *get(); }
+  const PaintOp* operator->() const { return get(); }
+  const PaintOp& operator*() const { return *get(); }
   CompositeIterator begin() const {
     return absl::holds_alternative<Iterator>(iter_)
                ? CompositeIterator(absl::get<Iterator>(iter_).begin())
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 610af36a..bc2a4f1f 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -2540,7 +2540,7 @@
   buffer.push<DrawColorOp>(SkColors::kMagenta, SkBlendMode::kSrc);
 
   PaintOpBuffer::Iterator iter(&buffer);
-  PaintOp* op = iter.get();
+  const PaintOp* op = iter.get();
   ASSERT_TRUE(op);
 
   TestOptionsProvider options_provider;
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 8b6bcbf..0b3eab4 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -423,6 +423,7 @@
       PaintHoldingReason,
       absl::optional<PaintHoldingCommitTrigger>) override {}
   void OnPauseRenderingChanged(bool) override {}
+  void OnCommitRequested() override {}
 
   void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks,
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index d61465e..db764acc 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -32,6 +32,7 @@
       PaintHoldingReason,
       absl::optional<PaintHoldingCommitTrigger>) override {}
   void OnPauseRenderingChanged(bool) override {}
+  void OnCommitRequested() override {}
   void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks,
                                ActiveFrameSequenceTrackers) override {}
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 439008f..55295cc 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -724,6 +724,10 @@
   events_metrics_manager_.SaveActiveEventMetrics();
 }
 
+void LayerTreeHost::OnCommitRequested() {
+  client_->OnCommitRequested();
+}
+
 void LayerTreeHost::SetTargetLocalSurfaceId(
     const viz::LocalSurfaceId& target_local_surface_id) {
   DCHECK(IsMainThread());
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 5e26aab0..c263015 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -285,6 +285,9 @@
   // synchronization.
   virtual void SetNeedsCommit();
 
+  // Invoked when a compositing update is first requested and scheduled.
+  void OnCommitRequested();
+
   // Notifies that a new viz::LocalSurfaceId has been set, ahead of it becoming
   // activated. Requests that the compositor thread does not produce new frames
   // until it has activated.
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index 0e05aba..2aae438 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -134,6 +134,9 @@
   // Notification that rendering has been paused or resumed.
   virtual void OnPauseRenderingChanged(bool) = 0;
 
+  // Notification that a compositing update has been requested.
+  virtual void OnCommitRequested() = 0;
+
   // Visual frame-based updates to the state of the LayerTreeHost are expected
   // to happen only in calls to LayerTreeHostClient::UpdateLayerTreeHost, which
   // should mutate/invalidate the layer tree or other page parameters as
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 671573ca..d30995ca 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -4164,8 +4164,7 @@
     CompositorCommitData* commit_data) const {
   commit_data->scrollbars.reserve(scrollbar_animation_controllers_.size());
   for (auto& pair : scrollbar_animation_controllers_) {
-    if (pair.second->visibility_changed() ||
-        !settings_.enable_scroll_update_optimizations) {
+    if (pair.second->visibility_changed()) {
       commit_data->scrollbars.push_back(
           {pair.first, pair.second->ScrollbarsHidden()});
       pair.second->ClearVisibilityChanged();
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 9d94c32..a5ecf4a 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -5816,7 +5816,6 @@
   settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
   settings.scrollbar_fade_delay = base::Milliseconds(20);
   settings.scrollbar_fade_duration = base::Milliseconds(20);
-  settings.enable_scroll_update_optimizations = true;
   gfx::Size viewport_size(50, 50);
   gfx::Size content_size(100, 100);
 
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index e4901842..012e0f78 100644
--- a/cc/trees/layer_tree_settings.h
+++ b/cc/trees/layer_tree_settings.h
@@ -213,9 +213,6 @@
   // to find the link to the Fluent Scrollbar spec and related CLs.
   bool enable_fluent_scrollbar = false;
 
-  // This corresponds to the ScrollUpdateOptimizations feature.
-  bool enable_scroll_update_optimizations = false;
-
   // Whether to disable the frame rate limit in the scheduler.
   bool disable_frame_rate_limit = false;
 
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index f6572655..c3a56f3a 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -794,6 +794,7 @@
   ImplThreadTaskRunner()->PostTask(
       FROM_HERE, base::BindOnce(&ProxyImpl::SetNeedsCommitOnImpl,
                                 base::Unretained(proxy_impl_.get())));
+  layer_tree_host_->OnCommitRequested();
   return true;
 }
 
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index e6a0d44..e5957253 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -192,6 +192,7 @@
   DebugScopedSetImplThread impl(task_runner_provider_);
   if (scheduler_on_impl_thread_)
     scheduler_on_impl_thread_->SetNeedsBeginMainFrame();
+  layer_tree_host_->OnCommitRequested();
 }
 
 void SingleThreadProxy::SetNeedsUpdateLayers() {
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index dd9a7760..5d0dd7f 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2630,7 +2630,6 @@
       "java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
       "java/src/org/chromium/chrome/browser/notifications/NotificationJobService.java",
       "java/src/org/chromium/chrome/browser/notifications/NotificationService.java",
-      "java/src/org/chromium/chrome/browser/omaha/OmahaClient.java",
       "java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java",
       "java/src/org/chromium/chrome/browser/prerender/ChromePrerenderService.java",
       "java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 1ee13aa..631aa76 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -842,7 +842,6 @@
   "java/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskScheduler.java",
   "java/src/org/chromium/chrome/browser/omaha/MarketURLGetter.java",
   "java/src/org/chromium/chrome/browser/omaha/OmahaBase.java",
-  "java/src/org/chromium/chrome/browser/omaha/OmahaClientImpl.java",
   "java/src/org/chromium/chrome/browser/omaha/OmahaDelegate.java",
   "java/src/org/chromium/chrome/browser/omaha/OmahaDelegateBase.java",
   "java/src/org/chromium/chrome/browser/omaha/OmahaService.java",
diff --git a/chrome/android/expectations/monochrome_public_bundle__base.AndroidManifest.expected b/chrome/android/expectations/monochrome_public_bundle__base.AndroidManifest.expected
index 1439c6a4..703ffd6a 100644
--- a/chrome/android/expectations/monochrome_public_bundle__base.AndroidManifest.expected
+++ b/chrome/android/expectations/monochrome_public_bundle__base.AndroidManifest.expected
@@ -1261,7 +1261,6 @@
         android:name="org.chromium.chrome.browser.notifications.NotificationService"
         android:exported="false">
     </service>  # DIFF-ANCHOR: 7fb8c03f
-    <service android:name="org.chromium.chrome.browser.omaha.OmahaClient" android:exported="false"/>
     <service  # DIFF-ANCHOR: 53256720
         android:name="org.chromium.chrome.browser.photo_picker.DecoderService"
         android:description="@string/decoder_description"
diff --git a/chrome/android/expectations/monochrome_public_bundle__chrome.AndroidManifest.expected b/chrome/android/expectations/monochrome_public_bundle__chrome.AndroidManifest.expected
index 7d62371..c76a570 100644
--- a/chrome/android/expectations/monochrome_public_bundle__chrome.AndroidManifest.expected
+++ b/chrome/android/expectations/monochrome_public_bundle__chrome.AndroidManifest.expected
@@ -935,7 +935,6 @@
         android:name="org.chromium.chrome.browser.notifications.NotificationService"
         android:exported="false">
     </service>  # DIFF-ANCHOR: 7fb8c03f
-    <service android:name="org.chromium.chrome.browser.omaha.OmahaClient" android:exported="false"/>
     <service  # DIFF-ANCHOR: 53256720
         android:name="org.chromium.chrome.browser.photo_picker.DecoderService"
         android:description="@string/decoder_description"
diff --git a/chrome/android/expectations/trichrome_chrome_bundle__base.AndroidManifest.expected b/chrome/android/expectations/trichrome_chrome_bundle__base.AndroidManifest.expected
index 8331c17..cfe686a 100644
--- a/chrome/android/expectations/trichrome_chrome_bundle__base.AndroidManifest.expected
+++ b/chrome/android/expectations/trichrome_chrome_bundle__base.AndroidManifest.expected
@@ -1117,7 +1117,6 @@
         android:name="org.chromium.chrome.browser.notifications.NotificationService"
         android:exported="false">
     </service>  # DIFF-ANCHOR: 7fb8c03f
-    <service android:name="org.chromium.chrome.browser.omaha.OmahaClient" android:exported="false"/>
     <service  # DIFF-ANCHOR: 53256720
         android:name="org.chromium.chrome.browser.photo_picker.DecoderService"
         android:description="@string/decoder_description"
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index f4911f86..5e81873 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -177,6 +177,10 @@
     private BrowserControlsStateProvider.Observer mBrowserControlsObserver;
     private ActivityStateChecker mActivityStateChecker;
     private OneshotSupplier<StartSurface> mStartSurfaceSupplier;
+
+    // Only used when the start surface refactoring is enabled. It indicates whether the Start
+    // surface homepage is showing when we no longer calculate StartSurfaceState.
+    private boolean mIsHomepageShown;
     /**
      * Whether a pending observer needed be added to the normal TabModel after the TabModel is
      * initialized.
@@ -273,25 +277,20 @@
             mNormalTabModelObserver = new TabModelObserver() {
                 @Override
                 public void willCloseTab(Tab tab, boolean animate, boolean didCloseAlone) {
-                    if ((mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                                || mIsStartSurfaceRefactorEnabled)
-                            && mTabModelSelector.getModel(false).getCount() <= 1) {
+                    if (isHomepageShown() && mTabModelSelector.getModel(false).getCount() <= 1) {
                         setTabCarouselVisibility(false);
                     }
                 }
                 @Override
                 public void tabClosureUndone(Tab tab) {
-                    if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                            || mIsStartSurfaceRefactorEnabled) {
+                    if (isHomepageShown()) {
                         setTabCarouselVisibility(true);
                     }
                 }
 
                 @Override
                 public void restoreCompleted() {
-                    if (!(mPropertyModel.get(IS_SHOWING_OVERVIEW)
-                                && (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                                        || mIsStartSurfaceRefactorEnabled))) {
+                    if (!(mPropertyModel.get(IS_SHOWING_OVERVIEW) && isHomepageShown())) {
                         return;
                     }
                     setTabCarouselVisibility(
@@ -300,9 +299,7 @@
 
                 @Override
                 public void willAddTab(Tab tab, @TabLaunchType int type) {
-                    if ((mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                                || mIsStartSurfaceRefactorEnabled)
-                            && type != TabLaunchType.FROM_LONGPRESS_BACKGROUND) {
+                    if (isHomepageShown() && type != TabLaunchType.FROM_LONGPRESS_BACKGROUND) {
                         // Log if the creation of this tab will hide the surface and there is an
                         // ongoing feed launch. If the tab creation is due to a feed card tap, "card
                         // tapped" should already have been logged marking the end of the launch.
@@ -324,9 +321,7 @@
                     // Updates the visibility of the tab switcher module if it is invisible and a
                     // new Tab is created in the background without hiding the Start surface
                     // homepage.
-                    if ((mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                                || mIsStartSurfaceRefactorEnabled)
-                            && !mHideOverviewOnTabSelecting
+                    if (isHomepageShown() && !mHideOverviewOnTabSelecting
                             && !mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE)) {
                         setTabCarouselVisibility(!mIsIncognito);
                     }
@@ -367,8 +362,7 @@
                 @Override
                 public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
                         int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
-                    if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                            || mIsStartSurfaceRefactorEnabled) {
+                    if (isHomepageShown()) {
                         // Set the top margin to the top controls min height (indicator height if
                         // it's shown) since the toolbar height as extra margin is handled by top
                         // toolbar placeholder.
@@ -387,8 +381,7 @@
                         int bottomControlsHeight, int bottomControlsMinHeight) {
                     // Only pad single pane home page since tabs grid has already been
                     // padded for the bottom bar.
-                    if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                            || mIsStartSurfaceRefactorEnabled) {
+                    if (isHomepageShown()) {
                         setBottomMargin(bottomControlsHeight);
                     } else {
                         setBottomMargin(0);
@@ -467,8 +460,7 @@
             // and MV tiles haven't been set.
             if (mController.overviewVisible()) {
                 mOmniboxStub.addUrlFocusChangeListener(mUrlFocusChangeListener);
-                if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                        || mIsStartSurfaceRefactorEnabled) {
+                if (isHomepageShown()) {
                     if (mExploreSurfaceCoordinatorFactory != null) {
                         setExploreSurfaceVisibility(!mIsIncognito);
                     }
@@ -497,7 +489,6 @@
 
     void destroy() {
         if (mLogoCoordinator != null) {
-            mLogoCoordinator.removeObserver(this);
             mLogoCoordinator.destroy();
             mLogoCoordinator = null;
         }
@@ -518,6 +509,9 @@
         // This null check is for testing.
         if (mPropertyModel == null) return;
 
+        mIsHomepageShown = true;
+        notifyShowStateChange();
+
         // TODO(crbug.com/1347089): When entering the Start surface by tapping back button or other
         // back gestures, we shouldn't reset the scrolling position. Maybe we could add a boolean
         // |mResetPosition| and set it as false only when this method is called because of back
@@ -1001,6 +995,7 @@
             }
             setStartSurfaceState(StartSurfaceState.NOT_SHOWN);
             RecordUserAction.record("StartSurface.Hidden");
+            mIsHomepageShown = false;
         }
         for (TabSwitcherViewObserver observer : mObservers) {
             observer.startedHiding();
@@ -1148,10 +1143,20 @@
     }
 
     private void notifyStateChange() {
+        notifyShowStateChange();
+        notifyStartSurfaceStateChange();
+    }
+
+    private void notifyShowStateChange() {
         // StartSurface is being supplied with OneShotSupplier, notification sends after
         // StartSurface is available to avoid missing events. More detail see:
         // https://crrev.com/c/2427428.
-        mController.onHomepageChanged(mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE);
+        mController.onHomepageChanged(isHomepageShown());
+        notifyBackPressStateChanged();
+    }
+
+    // TODO(1347089): Remove this when the Start surface refactoring is enabled by default.
+    private void notifyStartSurfaceStateChange() {
         if (mSecondaryTasksSurfaceController != null) {
             mSecondaryTasksSurfaceController.onHomepageChanged(
                     mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE);
@@ -1161,7 +1166,6 @@
                 observer.onStateChanged(mStartSurfaceState, shouldShowTabSwitcherToolbar());
             }
         });
-        notifyBackPressStateChanged();
     }
 
     private boolean hasFakeSearchBox() {
@@ -1228,7 +1232,7 @@
         }
         if (mLogoCoordinator != null) {
             boolean isShowingHomepage = isShowingStartSurfaceHomepage();
-            mLogoCoordinator.maybeLoadSearchProviderLogo(
+            mLogoCoordinator.updateVisibilityAndMaybeCleanUp(
                     isShowingHomepage && isVisible, !isShowingHomepage, false);
         }
     }
@@ -1423,8 +1427,7 @@
 
         mLogoCoordinator = new LogoCoordinator(mContext, logoClickedCallback,
                 mLogoContainerView.findViewById(R.id.search_provider_logo), true, null, null,
-                isShowingStartSurfaceHomepage());
-        mLogoCoordinator.addObserver(this);
+                isShowingStartSurfaceHomepage(), this);
         return mLogoCoordinator;
     }
 
@@ -1495,4 +1498,13 @@
             mLastShownTimeMs = System.currentTimeMillis();
         }
     }
+
+    /**
+     * Returns whether the Start surface homepage is showing.
+     */
+    private boolean isHomepageShown() {
+        return mIsStartSurfaceRefactorEnabled
+                ? mIsHomepageShown
+                : mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE;
+    }
 }
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
index 982b073..15d7dc9 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
@@ -478,7 +478,7 @@
         StartSurfaceTestUtils.waitForStartSurfaceVisible(cta);
         // After the Start surface refactoring is enabled, the StartSurfaceState.SHOWN_TABSWITCHER
         // will go away.
-        if (!ReturnToChromeUtil.isStartSurfaceRefactorEnabled(cta)) {
+        if (!TabUiTestHelper.getIsStartSurfaceRefactorEnabledFromUIThread(cta)) {
             StartSurfaceCoordinator startSurfaceCoordinator =
                     StartSurfaceTestUtils.getStartSurfaceFromUIThread(cta);
             TestThreadUtils.runOnUiThreadBlocking(() -> {
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
index fbeb77e96..8704d2d26 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
@@ -20,8 +20,6 @@
 
 import android.os.Build;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
-import android.support.test.runner.lifecycle.Stage;
 import android.view.View;
 
 import androidx.test.espresso.contrib.RecyclerViewActions;
@@ -261,21 +259,8 @@
         StartSurfaceTestUtils.waitForStartSurfaceVisible(cta);
         TabUiTestHelper.verifyTabModelTabCount(cta, 2, 0);
 
-        // Verifies Chrome is closed.
-        try {
-            StartSurfaceTestUtils.pressBack(mActivityTestRule);
-        } catch (Exception e) {
-        } finally {
-            CriteriaHelper.pollUiThread(
-                    ()
-                            -> {
-                        return ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(
-                                       cta)
-                                == Stage.STOPPED;
-                    },
-                    "Tapping back button should close Chrome.", MAX_TIMEOUT_MS,
-                    CriteriaHelper.DEFAULT_POLLING_INTERVAL);
-        }
+        // Verifies that Chrome goes to the background.
+        StartSurfaceTestUtils.pressBackAndVerifyChromeToBackground(mActivityTestRule);
     }
 
     @Test
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 01a41de..1fb02885 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -204,11 +204,20 @@
         waitForView(allOf(withParent(withId(TabUiTestHelper.getTabSwitcherParentId(cta))),
                 withId(R.id.tab_list_view)));
 
-        StartSurfaceTestUtils.pressBack(mActivityTestRule);
-        onViewWaiting(allOf(withId(R.id.primary_tasks_surface_view), isDisplayed()));
+        // When the start surface refactoring is enabled, tapping the back button on Tab switcher
+        // will show the last tab.
+        if (TabUiTestHelper.getIsStartSurfaceRefactorEnabledFromUIThread(
+                    mActivityTestRule.getActivity())) {
+            StartSurfaceTestUtils.pressBack(mActivityTestRule);
+            // Verifies that the last tab is opening.
+            LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+        } else {
+            StartSurfaceTestUtils.pressBack(mActivityTestRule);
+            onViewWaiting(allOf(withId(R.id.primary_tasks_surface_view), isDisplayed()));
 
-        StartSurfaceTestUtils.clickFirstTabInCarousel();
-        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+            StartSurfaceTestUtils.clickFirstTabInCarousel();
+            LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+        }
     }
 
     @Test
@@ -296,7 +305,6 @@
     @Test
     @LargeTest
     @Feature({"StartSurface"})
-    @DisableFeatures(ChromeFeatureList.START_SURFACE_REFACTOR)
     @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS
             + "open_ntp_instead_of_start/false/open_start_as_homepage/true"})
     // clang-format off
@@ -334,11 +342,21 @@
             // omnibox.
             return;
         }
-        StartSurfaceTestUtils.pressBack(mActivityTestRule);
-        onViewWaiting(withId(R.id.primary_tasks_surface_view));
 
-        onViewWaiting(withId(R.id.single_tab_view)).perform(click());
-        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+        // When the start surface refactoring is enabled, tapping the back button on Tab switcher
+        // will show the last tab.
+        if (TabUiTestHelper.getIsStartSurfaceRefactorEnabledFromUIThread(
+                    mActivityTestRule.getActivity())) {
+            StartSurfaceTestUtils.pressBack(mActivityTestRule);
+            // Verifies that the last tab is opening.
+            LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+        } else {
+            StartSurfaceTestUtils.pressBack(mActivityTestRule);
+            onViewWaiting(withId(R.id.primary_tasks_surface_view));
+
+            onViewWaiting(withId(R.id.single_tab_view)).perform(click());
+            LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+        }
     }
 
     @Test
@@ -357,7 +375,8 @@
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         CriteriaHelper.pollUiThread(()
                                             -> cta.getLayoutManager() != null
-                        && cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
+                        && cta.getLayoutManager().isLayoutVisible(
+                                StartSurfaceTestUtils.getStartSurfaceLayoutType()));
         StartSurfaceTestUtils.waitForTabModel(cta);
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { cta.getTabModelSelector().getModel(false).closeAllTabs(); });
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
index d64f3a1..0a0cbc1 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
@@ -29,6 +29,8 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
 import android.util.Base64;
 import android.view.View;
 import android.view.ViewGroup;
@@ -420,7 +422,8 @@
     public static void pressBack(ChromeTabbedActivityTestRule activityTestRule) {
         // ChromeTabbedActivity expects the native libraries to be loaded when back is pressed.
         activityTestRule.waitForActivityNativeInitializationComplete();
-        TestThreadUtils.runOnUiThreadBlocking(() -> activityTestRule.getActivity().onBackPressed());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> activityTestRule.getActivity().getOnBackPressedDispatcher().onBackPressed());
     }
 
     /**
@@ -553,6 +556,25 @@
     }
 
     /**
+     * Presses the back button and verifies that Chrome goes to the background.
+     */
+    public static void pressBackAndVerifyChromeToBackground(ChromeTabbedActivityTestRule testRule) {
+        // Verifies Chrome is closed.
+        try {
+            pressBack(testRule);
+        } catch (Exception e) {
+        } finally {
+            CriteriaHelper.pollUiThread(
+                    ()
+                            -> ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(
+                                       testRule.getActivity())
+                            == Stage.STOPPED,
+                    "Tapping back button should close Chrome.", MAX_TIMEOUT_MS,
+                    CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
+    }
+
+    /**
      * Toggles the Feed header and checks whether the header has the right status.
      * @param expanded Whether the header should be expanded.
      */
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index f315d8b..23e5d0a 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -1058,8 +1058,6 @@
         <service android:name="org.chromium.chrome.browser.crash.MinidumpUploadService"
             android:exported="false"/>
 
-        <service android:name="org.chromium.chrome.browser.omaha.OmahaClient"
-            android:exported="false"/>
         <service android:name="org.chromium.chrome.browser.incognito.IncognitoNotificationService"
             android:exported="false"/>
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
index ab10f66..2a3ac46a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
@@ -123,11 +123,10 @@
             // Introduced in Q.
             vmPolicy.detectCredentialProtectedWhileLocked().detectImplicitDirectBoot();
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            // File URI leak detection, has false positives when file URI intents are passed between
-            // Chrome activities in separate processes. See http://crbug.com/508282#c11.
-            vmPolicy.detectFileUriExposure();
-        }
+
+        // File URI leak detection, has false positives when file URI intents are passed between
+        // Chrome activities in separate processes. See http://crbug.com/508282#c11.
+        vmPolicy.detectFileUriExposure();
     }
 
     private static void addDefaultThreadPenalties(StrictMode.ThreadPolicy.Builder threadPolicy) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java b/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
index 844d4f8..d4d174fc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
@@ -5,13 +5,10 @@
 package org.chromium.chrome.browser;
 
 import android.content.Context;
-import android.os.Build;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.tab.Tab;
@@ -121,7 +118,6 @@
      *            resource.
      * @return a list of shortcuts organized into groups.
      */
-    @RequiresApi(Build.VERSION_CODES.N)
     public static List<KeyboardShortcutGroup> createShortcutGroup(Context context) {
         final int ctrlShift = KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON;
 
@@ -180,7 +176,6 @@
         return shortcutGroups;
     }
 
-    @RequiresApi(Build.VERSION_CODES.N)
     private static void addShortcut(Context context, KeyboardShortcutGroup shortcutGroup, int resId,
             int keyCode, int keyModifier) {
         shortcutGroup.addItem(new KeyboardShortcutInfo(context.getString(resId), keyCode,
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 8719da6..c3f3118 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -448,11 +448,9 @@
             }
         }
 
-        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
-            Uri extraReferrer = mActivity.getReferrer();
-            if (extraReferrer != null) {
-                newIntent.putExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER, extraReferrer.toString());
-            }
+        Uri extraReferrer = mActivity.getReferrer();
+        if (extraReferrer != null) {
+            newIntent.putExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER, extraReferrer.toString());
         }
 
         String targetActivityClassName = MultiWindowUtils.getInstance()
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 4ab11b2e..ad3bc4a6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -1677,8 +1677,7 @@
         // focus dependency is because doing it earlier can cause drawing bugs, e.g. crbug/673831.
         if (!mNativeInitialized || !hasWindowFocus()) return;
 
-        if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(this)
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(this)) {
             changeBackgroundColorForResizing();
         } else {
             // Post the background update call as a separate task, as doing it synchronously
@@ -1686,13 +1685,9 @@
             // example problems.
             Handler handler = new Handler();
             handler.post(() -> {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                    // The window background color is used as the resizing background color in
-                    // Android N+ multi-window mode. See crbug.com/602366.
-                    changeBackgroundColorForResizing();
-                } else {
-                    removeWindowBackground();
-                }
+                // The window background color is used as the resizing background color in
+                // Android N+ multi-window mode. See crbug.com/602366.
+                changeBackgroundColorForResizing();
             });
         }
         mRemoveWindowBackgroundDone = true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index 35d0c2f..23a5836 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -1740,16 +1740,15 @@
         return tab == mStripTabsVisuallyOrdered[mStripTabsVisuallyOrdered.length - 1];
     }
 
-    private void updateFolioTabAttachState(StripLayoutTab tab, boolean attached) {
-        finishAnimationsAndPushTabUpdates();
-
+    private void updateFolioTabAttachState(
+            StripLayoutTab tab, boolean attached, ArrayList<Animator> animationList) {
         float startValue =
                 attached ? FOLIO_DETACHED_BOTTOM_MARGIN_DP : FOLIO_ATTACHED_BOTTOM_MARGIN_DP;
         float intermediateValue = FOLIO_ANIM_INTERMEDIATE_MARGIN_DP;
         float endValue =
                 attached ? FOLIO_ATTACHED_BOTTOM_MARGIN_DP : FOLIO_DETACHED_BOTTOM_MARGIN_DP;
 
-        ArrayList<Animator> animationList = new ArrayList<>();
+        ArrayList<Animator> attachAnimationList = new ArrayList<>();
         CompositorAnimator dropAnimation = CompositorAnimator.ofFloatProperty(
                 mUpdateHost.getAnimationHandler(), tab, StripLayoutTab.BOTTOM_MARGIN, startValue,
                 intermediateValue, ANIM_FOLIO_DETACH_MS, Interpolators.EMPHASIZED_ACCELERATE);
@@ -1763,13 +1762,17 @@
                 tab.setFolioAttached(attached);
             }
         });
-        animationList.add(dropAnimation);
-        animationList.add(riseAnimation);
+        attachAnimationList.add(dropAnimation);
+        attachAnimationList.add(riseAnimation);
 
         AnimatorSet set = new AnimatorSet();
-        set.playSequentially(animationList);
-        mRunningAnimator = set;
-        mRunningAnimator.start();
+        set.playSequentially(attachAnimationList);
+
+        if (animationList == null) {
+            set.end();
+        } else {
+            animationList.add(set);
+        }
     }
 
     @VisibleForTesting
@@ -1790,7 +1793,7 @@
     }
 
     private void startReorderMode(long time, float currentX, float startX) {
-        if (mInReorderMode || mTabGroupMarginAnimRunning) return;
+        if (mInReorderMode) return;
         RecordUserAction.record("MobileToolbarStartReorderTab");
         // 1. Reset the last pressed close button state.
         if (mLastPressedCloseButton != null && mLastPressedCloseButton.isPressed()) {
@@ -1803,6 +1806,9 @@
         if (mInteractingTab == null || mInteractingTab.isDying()) return;
 
         // 3. Set initial state parameters.
+        finishAnimationsAndPushTabUpdates();
+        ArrayList<Animator> animationList =
+                mAnimationsDisabledForTesting ? null : new ArrayList<>();
         mLastReorderScrollTime = INVALID_TIME;
         mHoverStartTime = INVALID_TIME;
         mHoverStartOffset = 0;
@@ -1829,22 +1835,27 @@
                     getExpandDuration());
         } else if (TabUiFeatureUtilities.isTabletTabGroupsEnabled(mContext)) {
             Tab tab = getTabById(mInteractingTab.getId());
-            computeAndUpdateTabGroupMargins(true, true);
+            computeAndUpdateTabGroupMargins(true, animationList);
             setTabGroupDimmed(mTabGroupModelFilter.getRootId(tab), false);
             performHapticFeedback(tab);
         }
 
         // 7. Lift the TSR folio container off the toolbar.
         if (TabUiFeatureUtilities.isTabStripFolioEnabled()) {
-            updateFolioTabAttachState(mInteractingTab, false);
+            updateFolioTabAttachState(mInteractingTab, false, animationList);
         }
 
-        // 8. Request an update.
+        // 8. Kick-off animations and request an update.
+        if (animationList != null) {
+            startAnimationList(animationList, getTabGroupMarginAnimatorListener(false));
+        }
         mUpdateHost.requestUpdate();
     }
 
     private void stopReorderMode() {
         if (!mInReorderMode) return;
+        ArrayList<Animator> animationList = null;
+        if (!mAnimationsDisabledForTesting) animationList = new ArrayList<>();
 
         // 1. Reset the state variables.
         mReorderState = REORDER_SCROLL_NONE;
@@ -1852,24 +1863,30 @@
 
         // 2. Clear any drag offset.
         finishAnimationsAndPushTabUpdates();
-        mRunningAnimator = CompositorAnimator.ofFloatProperty(mUpdateHost.getAnimationHandler(),
-                mInteractingTab, StripLayoutTab.X_OFFSET, mInteractingTab.getOffsetX(), 0f,
-                ANIM_TAB_MOVE_MS);
-        mRunningAnimator.start();
+        if (animationList != null) {
+            animationList.add(CompositorAnimator.ofFloatProperty(mUpdateHost.getAnimationHandler(),
+                    mInteractingTab, StripLayoutTab.X_OFFSET, mInteractingTab.getOffsetX(), 0f,
+                    ANIM_TAB_MOVE_MS));
+        } else {
+            mInteractingTab.setOffsetX(0f);
+        }
 
         // 3. Un-dim the background tabs and fade-in the new tab & model selector buttons.
         setBackgroundTabsDimmed(false);
         setCompositorButtonsVisible(true);
 
         // 4. Clear any tab group margins if they are enabled.
-        if (TabUiFeatureUtilities.isTabletTabGroupsEnabled(mContext)) resetTabGroupMargins();
+        if (TabUiFeatureUtilities.isTabletTabGroupsEnabled(mContext)) {
+            resetTabGroupMargins(animationList);
+        }
 
         // 5. Reattach the TSR folio container to the toolbar.
         if (TabUiFeatureUtilities.isTabStripFolioEnabled()) {
-            updateFolioTabAttachState(mInteractingTab, true);
+            updateFolioTabAttachState(mInteractingTab, true, animationList);
         }
 
         // 6. Request an update.
+        startAnimationList(animationList, getTabGroupMarginAnimatorListener(true));
         mUpdateHost.requestUpdate();
     }
 
@@ -1885,10 +1902,9 @@
             StripLayoutTab tab, float trailingMargin, List<Animator> animationList) {
         if (tab.getTrailingMargin() != trailingMargin) {
             if (animationList != null) {
-                CompositorAnimator animator = CompositorAnimator.ofFloatProperty(
+                animationList.add(CompositorAnimator.ofFloatProperty(
                         mUpdateHost.getAnimationHandler(), tab, StripLayoutTab.TRAILING_MARGIN,
-                        tab.getTrailingMargin(), trailingMargin, ANIM_TAB_SLIDE_OUT_MS);
-                animationList.add(animator);
+                        tab.getTrailingMargin(), trailingMargin, ANIM_TAB_SLIDE_OUT_MS));
             } else {
                 tab.setTrailingMargin(trailingMargin);
             }
@@ -1921,10 +1937,8 @@
         }
 
         if (animationList != null) {
-            CompositorAnimator scrollAnimator =
-                    CompositorAnimator.ofFloatProperty(mUpdateHost.getAnimationHandler(), this,
-                            SCROLL_OFFSET, startValue, endValue, ANIM_TAB_SLIDE_OUT_MS);
-            animationList.add(scrollAnimator);
+            animationList.add(CompositorAnimator.ofFloatProperty(mUpdateHost.getAnimationHandler(),
+                    this, SCROLL_OFFSET, startValue, endValue, ANIM_TAB_SLIDE_OUT_MS));
         } else {
             mScrollOffset = endValue;
         }
@@ -1945,10 +1959,8 @@
         };
     }
 
-    private void computeAndUpdateTabGroupMargins(boolean autoScroll, boolean animate) {
-        ArrayList<Animator> animationList = null;
-        if (animate && !mAnimationsDisabledForTesting) animationList = new ArrayList<>();
-
+    private void computeAndUpdateTabGroupMargins(
+            boolean autoScroll, ArrayList<Animator> animationList) {
         // 1. Update the trailing margins for each tab.
         boolean pastInteractingTab = false;
         int numMarginsToSlide = 0;
@@ -1993,17 +2005,11 @@
         }
 
         // 4. Begin slide-out and scroll animation. Update tab positions.
-        if (animationList != null) {
-            startAnimationList(animationList, getTabGroupMarginAnimatorListener(false));
-        } else {
-            computeTabInitialPositions();
-        }
+        if (animationList == null) computeTabInitialPositions();
     }
 
-    private void resetTabGroupMargins() {
+    private void resetTabGroupMargins(ArrayList<Animator> animationList) {
         assert !mInReorderMode;
-        ArrayList<Animator> animationList = null;
-        if (!mAnimationsDisabledForTesting) animationList = new ArrayList<>();
 
         // 1. Update the trailing margins for each tab.
         boolean pastInteractingTab = false;
@@ -2021,9 +2027,6 @@
         autoScrollForTabGroupMargins(
                 -mStripStartMarginForReorder, numMarginsToSlide, animationList);
         mStripStartMarginForReorder = 0f;
-
-        // 3. Begin slide-out and scroll animation.
-        startAnimationList(animationList, getTabGroupMarginAnimatorListener(true));
     }
 
     private void setCompositorButtonsVisible(boolean visible) {
@@ -2271,7 +2274,7 @@
             float oldIdealX = mInteractingTab.getIdealX();
             float oldOffset = mScrollOffset;
             if (TabUiFeatureUtilities.isTabletTabGroupsEnabled(mContext)) {
-                computeAndUpdateTabGroupMargins(false, false);
+                computeAndUpdateTabGroupMargins(false, null);
             }
 
             // 3.d. Since we just moved the tab we're dragging, adjust its offset so it stays in
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 d8b7750..49996a1 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
@@ -325,12 +325,17 @@
                 // indicator for the active tab than |mTab|, since the invocation order of
                 // ActivityTabTabObserver and TabModelSelectorTabObserver is not explicitly defined.
                 if (!interactable || tab != modelSelector.getCurrentTab()) return;
-                Runnable enterFullscreen = getEnterFullscreenRunnable(tab);
-                if (enterFullscreen != null) enterFullscreen.run();
+                onTabInteractable(tab);
             }
         };
     }
 
+    @VisibleForTesting
+    void onTabInteractable(Tab tab) {
+        Runnable enterFullscreen = getAndClearEnterFullscreenRunnable(tab);
+        if (enterFullscreen != null) enterFullscreen.run();
+    }
+
     @Override
     public void addObserver(FullscreenManager.Observer observer) {
         mObservers.addObserver(observer);
@@ -354,31 +359,42 @@
 
     @Override
     public void onEnterFullscreen(Tab tab, FullscreenOptions options) {
+        if (shouldSkipEnterFullscreenRequest(options)) return;
         // If enabling fullscreen while the tab is not interactable, fullscreen
         // will be delayed until the tab is interactable.
         Runnable r = () -> {
             enterPersistentFullscreenMode(options);
             destroySelectActionMode(tab);
             setEnterFullscreenRunnable(tab, null);
+            for (FullscreenManager.Observer observer : mObservers) {
+                observer.onEnterFullscreen(tab, options);
+            }
         };
-
         if (tab.isUserInteractable()) {
             r.run();
         } else {
             setEnterFullscreenRunnable(tab, r);
         }
+    }
 
-        for (FullscreenManager.Observer observer : mObservers) {
-            observer.onEnterFullscreen(tab, options);
-        }
+    private boolean shouldSkipEnterFullscreenRequest(FullscreenOptions options) {
+        // Do not process the request again if we're already in fullscreen mode and the request
+        // with the same option (could be in pending state) is received.
+        return getPersistentFullscreenMode()
+                && (ObjectsCompat.equals(mFullscreenOptions, options)
+                        || ObjectsCompat.equals(mPendingFullscreenOptions, options));
     }
 
     @Override
     public void onExitFullscreen(Tab tab) {
+        if (tab != mTab) return;
         setEnterFullscreenRunnable(tab, null);
-        if (tab == mTab) exitPersistentFullscreenMode();
-        for (FullscreenManager.Observer observer : mObservers) {
-            observer.onExitFullscreen(tab);
+        boolean wasInPersistentFullscreenMode = getPersistentFullscreenMode();
+        exitPersistentFullscreenMode();
+        if (wasInPersistentFullscreenMode) {
+            for (FullscreenManager.Observer observer : mObservers) {
+                observer.onExitFullscreen(tab);
+            }
         }
     }
 
@@ -411,8 +427,11 @@
         }
     }
 
-    private Runnable getEnterFullscreenRunnable(Tab tab) {
-        return tab != null ? TabAttributes.from(tab).get(TabAttributeKeys.ENTER_FULLSCREEN) : null;
+    private Runnable getAndClearEnterFullscreenRunnable(Tab tab) {
+        Runnable r =
+                tab != null ? TabAttributes.from(tab).get(TabAttributeKeys.ENTER_FULLSCREEN) : null;
+        if (r != null) setEnterFullscreenRunnable(tab, null);
+        return r;
     }
 
     /**
@@ -422,7 +441,7 @@
      * @param options Options to choose mode of fullscreen.
      */
     private void enterPersistentFullscreenMode(FullscreenOptions options) {
-        if (!getPersistentFullscreenMode() || !ObjectsCompat.equals(mFullscreenOptions, options)) {
+        if (!shouldSkipEnterFullscreenRequest(options)) {
             mPersistentModeSupplier.set(true);
             if (mAreControlsHidden.get()) {
                 // The browser controls are currently hidden.
@@ -624,7 +643,6 @@
         contentView.addOnLayoutChangeListener(mFullscreenOnLayoutChangeListener);
         if (DEBUG_LOGS) Log.i(TAG, "enterFullscreen, systemUiVisibility=" + systemUiVisibility);
         contentView.setSystemUiVisibility(systemUiVisibility);
-        mFullscreenOptions = options;
 
         // Request a layout so the updated system visibility takes affect.
         // The flow will continue in the handler of MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS message.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index 222893d8..3f92ef9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -15,8 +15,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
 import android.view.Display;
 import android.view.Menu;
 import android.view.View;
@@ -55,8 +53,6 @@
 import org.chromium.ui.display.DisplayAndroid;
 import org.chromium.ui.display.DisplayUtil;
 
-import java.lang.reflect.Field;
-
 /**
  * An activity that talks with application and activity level delegates for async initialization.
  */
@@ -806,33 +802,6 @@
     }
 
     /**
-     * Removes the window background.
-     */
-    protected void removeWindowBackground() {
-        boolean removeWindowBackground = true;
-        try {
-            Field field = Settings.Secure.class.getField(
-                    "ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED");
-            field.setAccessible(true);
-
-            if (field.getType() == String.class) {
-                String accessibilityMagnificationSetting = (String) field.get(null);
-                // When Accessibility magnification is turned on, setting a null window
-                // background causes the overlaid android views to stretch when panning.
-                // (crbug/332994)
-                if (Settings.Secure.getInt(
-                        getContentResolver(), accessibilityMagnificationSetting) == 1) {
-                    removeWindowBackground = false;
-                }
-            }
-        } catch (SettingNotFoundException | NoSuchFieldException | IllegalAccessException
-                | IllegalArgumentException ignore) {
-            // Window background is removed if an exception occurs.
-        }
-        if (removeWindowBackground) getWindow().setBackgroundDrawable(null);
-    }
-
-    /**
      * Extending classes should implement this, inflate the layout, set the content view and then
      * call {@link #onInitialLayoutInflationComplete}.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index c20e384..6781d6a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -317,7 +317,8 @@
         boolean shouldFetchDoodle = !FeedPositionUtils.isFeedPullUpEnabled();
         mLogoCoordinator = new LogoCoordinator(mContext, logoClickedCallback,
                 findViewById(R.id.search_provider_logo), shouldFetchDoodle, onLogoAvailableCallback,
-                onCachedLogoRevalidatedRunnable, /*isParentSurfaceShown=*/true);
+                onCachedLogoRevalidatedRunnable, /*isParentSurfaceShown=*/true,
+                /*visibilityObserver=*/null);
         mLogoCoordinator.initWithNative();
         setSearchProviderInfo(searchProviderHasLogo, searchProviderIsGoogle);
     }
@@ -851,17 +852,16 @@
      * Makes the Search Box and Logo as wide as Most Visited.
      */
     private void unifyElementWidths() {
-        View logoView = mLogoCoordinator.getView();
         View searchBoxView = getSearchBoxView();
         if (mMvTilesContainerLayout.getVisibility() != GONE) {
             if (!isScrollableMvtEnabled()) {
                 final int width = mMvTilesContainerLayout.getMeasuredWidth() - mTileGridLayoutBleed;
                 measureExactly(searchBoxView, width, searchBoxView.getMeasuredHeight());
-                measureExactly(logoView, width, logoView.getMeasuredHeight());
+                mLogoCoordinator.measureExactlyLogoView(width);
             } else {
                 final int width = getMeasuredWidth() - mTileGridLayoutBleed;
                 measureExactly(searchBoxView, width, searchBoxView.getMeasuredHeight());
-                measureExactly(logoView, width, logoView.getMeasuredHeight());
+                mLogoCoordinator.measureExactlyLogoView(width);
             }
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
index a0fdbd0..d457a97a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.os.Build;
 import android.text.format.DateUtils;
 
 import androidx.annotation.IntDef;
@@ -101,8 +100,6 @@
     static final String PREF_TIMESTAMP_OF_INSTALL = "timestampOfInstall";
     static final String PREF_TIMESTAMP_OF_REQUEST = "timestampOfRequest";
 
-    static final int MIN_API_JOB_SCHEDULER = Build.VERSION_CODES.M;
-
     private static final int UNKNOWN_DATE = -2;
 
     /** Whether or not the Omaha server should really be contacted. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
deleted file mode 100644
index 461cfc17..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
+++ /dev/null
@@ -1,20 +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.
-
-package org.chromium.chrome.browser.omaha;
-
-import org.chromium.build.annotations.IdentifierNameString;
-import org.chromium.chrome.browser.base.SplitCompatIntentService;
-
-/** See {@link OmahaClientImpl}. */
-public class OmahaClient extends SplitCompatIntentService {
-    private static final String TAG = "omaha";
-
-    @IdentifierNameString
-    private static String sImplClassName = "org.chromium.chrome.browser.omaha.OmahaClientImpl";
-
-    public OmahaClient() {
-        super(sImplClassName, TAG);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClientImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClientImpl.java
deleted file mode 100644
index 540ee127..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClientImpl.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.omaha;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Runs the {@link OmahaBase} pipeline as a {@link IntentService}.
- *
- * NOTE: This class can never be renamed because the user may have Intents floating around that
- *       reference this class specifically.
- */
-public class OmahaClientImpl extends OmahaClient.Impl {
-    @Override
-    protected void onServiceSet() {
-        getService().setIntentRedelivery(true);
-    }
-
-    @Override
-    public void onHandleIntent(Intent intent) {
-        OmahaService.getInstance(getService()).run();
-    }
-
-    static Intent createIntent(Context context) {
-        return new Intent(context, OmahaClient.class);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaService.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaService.java
index b756f38..fe398ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaService.java
@@ -7,7 +7,6 @@
 import android.app.IntentService;
 import android.app.job.JobService;
 import android.content.Context;
-import android.os.Build;
 
 import androidx.annotation.Nullable;
 
@@ -34,20 +33,14 @@
 
         @Override
         public void scheduleService(long currentTimestampMs, long nextTimestampMs) {
-            if (Build.VERSION.SDK_INT < OmahaBase.MIN_API_JOB_SCHEDULER) {
-                getScheduler().createAlarm(
-                        OmahaClientImpl.createIntent(getContext()), nextTimestampMs);
-                Log.i(OmahaBase.TAG, "Scheduled using AlarmManager and IntentService");
-            } else {
-                final long delay = nextTimestampMs - currentTimestampMs;
-                PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
-                    if (scheduleJobService(getContext(), delay)) {
-                        Log.i(OmahaBase.TAG, "Scheduled using JobService");
-                    } else {
-                        Log.e(OmahaBase.TAG, "Failed to schedule job");
-                    }
-                });
-            }
+            final long delay = nextTimestampMs - currentTimestampMs;
+            PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
+                if (scheduleJobService(getContext(), delay)) {
+                    Log.i(OmahaBase.TAG, "Scheduled using JobService");
+                } else {
+                    Log.e(OmahaBase.TAG, "Failed to schedule job");
+                }
+            });
         }
     }
 
@@ -78,11 +71,7 @@
      * Must only be called by {@link OmahaBase#onForegroundSessionStart}.
      */
     static void startServiceImmediately(Context context) {
-        if (Build.VERSION.SDK_INT < OmahaBase.MIN_API_JOB_SCHEDULER) {
-            context.startService(OmahaClientImpl.createIntent(context));
-        } else {
-            scheduleJobService(context, 0);
-        }
+        scheduleJobService(context, 0);
     }
 
     // Incorrectly infers that this is called on a worker thread because of AsyncTask doInBackground
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/README.md b/chrome/android/java/src/org/chromium/chrome/browser/omaha/README.md
index 47af9739..bd26376c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/README.md
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/README.md
@@ -94,9 +94,8 @@
 
 The `PowerBroadcastReceiver` then tells `OmahaBase` that a foreground session
 has started, which immediately triggers the `OmahaService`. The `OmahaService`
-then depending on the OS version either starts an `IntentService` called
-`OmahaClient`, or schedules a `BackgroundTask` with the Chrome
-`BackgroundTaskScheduler` for immediate scheduling (0ms delay).
+then schedules a `BackgroundTask` with the Chrome `BackgroundTaskScheduler` for
+immediate scheduling (0ms delay).
 
 ## Requests and Responses
 
@@ -209,8 +208,7 @@
 **File** | **Description**
 --- | ---
 ExponentialBackoffScheduler.java | Manages a timer that implements exponential backoff for failed attempts.
-OmahaClient.java | The `IntentService` based implementation of the Omaha client.<br><br>Note: This class can not be renamed because it has is referred to by the system, and therefore possibly old intents, etc.
-OmahaService.java | Uses either `AlarmManager` or `BackgroundTaskScheduler` to schedule jobs.<br><br>Also contains `OmahaClientDelegate`. The delegate contains logic for scheduling using the `AlarmManager` or a `BackgroundTask`.
+OmahaService.java | Uses `BackgroundTaskScheduler` to schedule jobs.<br><br>Also contains `OmahaClientDelegate`. The delegate contains logic for scheduling using a `BackgroundTask`.
 
 ### Updates
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/images/omaha-class-diagram.png b/chrome/android/java/src/org/chromium/chrome/browser/omaha/images/omaha-class-diagram.png
index 994aeae..33d6528e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/images/omaha-class-diagram.png
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/images/omaha-class-diagram.png
Binary files differ
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
index 21182ec..b32661b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
@@ -32,6 +32,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.app.ChromeActivity;
@@ -54,6 +55,7 @@
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.DropdownPopupWindowInterface;
 import org.chromium.ui.R;
+import org.chromium.ui.test.util.UiDisableIf;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -356,6 +358,7 @@
     @MediumTest
     @Feature({"autofill"})
     @EnableFeatures(ChromeFeatureList.AUTOFILL_REFRESH_STYLE_ANDROID)
+    @DisableIf.Device(type = UiDisableIf.TABLET) // https://crbug.com/1399871
     public void testScreenOrientationPortrait() throws TimeoutException {
         runTestScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
     }
@@ -364,6 +367,7 @@
     @MediumTest
     @Feature({"autofill"})
     @EnableFeatures(ChromeFeatureList.AUTOFILL_REFRESH_STYLE_ANDROID)
+    @DisableIf.Device(type = UiDisableIf.TABLET) // https://crbug.com/1399871
     public void testScreenOrientationLandscape() throws TimeoutException {
         runTestScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java
index 8fcd8e4..a525817 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import android.os.Build;
 import android.os.Bundle;
 
 import androidx.test.filters.MediumTest;
@@ -23,7 +22,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -39,7 +37,6 @@
 /** Tests {@link CloseTabDirectActionHandler}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
 @Batch(Batch.PER_CLASS)
 public class CloseTabDirectActionHandlerTest {
     @ClassRule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityCustomTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityCustomTabTest.java
index 0627d215..31e1cce7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityCustomTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityCustomTabTest.java
@@ -6,10 +6,8 @@
 import static org.junit.Assert.assertThat;
 
 import android.content.Intent;
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -19,7 +17,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsIntentTestUtils;
@@ -34,8 +31,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
-@RequiresApi(24) // For java.util.function.Consumer.
 public class DirectActionAvailabilityCustomTabTest {
     @Rule
     public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTabbedTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTabbedTest.java
index e68210f..2e538a8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTabbedTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTabbedTest.java
@@ -5,9 +5,6 @@
 package org.chromium.chrome.browser.directactions;
 import static org.junit.Assert.assertThat;
 
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -18,7 +15,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -33,8 +29,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
-@RequiresApi(24) // For java.util.function.Consumer.
 public class DirectActionAvailabilityTabbedTest {
     @Rule
     public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityWebappTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityWebappTest.java
index 2e2f9d36..4e4f4af 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityWebappTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityWebappTest.java
@@ -5,9 +5,6 @@
 package org.chromium.chrome.browser.directactions;
 import static org.junit.Assert.assertThat;
 
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -17,7 +14,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.webapps.WebappActivityTestRule;
@@ -31,8 +27,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
-@RequiresApi(24) // For java.util.function.Consumer.
 public class DirectActionAvailabilityWebappTest {
     @Rule
     public WebappActivityTestRule mWebAppActivityTestRule = new WebappActivityTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java
index 690a8a3..df9c656 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/DirectActionsInActivityTest.java
@@ -9,10 +9,8 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
-import android.os.Build;
 import android.os.Bundle;
 
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -26,7 +24,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MetricsUtils.HistogramDelta;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -41,8 +38,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
-@RequiresApi(24) // For java.util.function.Consumer.
 @Batch(Batch.PER_CLASS)
 public class DirectActionsInActivityTest {
     @Rule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java
index f6f50c2..219ccb0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandlerTest.java
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.Build;
 import android.os.Bundle;
 
 import androidx.test.filters.MediumTest;
@@ -28,7 +27,6 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -46,7 +44,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
 @Batch(Batch.PER_CLASS)
 public class MenuDirectActionHandlerTest {
     @ClassRule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadDialogIncognitoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadDialogIncognitoTest.java
index ca56eef..a58456b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadDialogIncognitoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadDialogIncognitoTest.java
@@ -122,9 +122,9 @@
 
     @Test
     @LargeTest
-    public void testMixedContentDownloadDownloadDoNotShowIncognitoWarning() throws Exception {
-        // Showing a mixed content download dialog with a regular profile.
-        showMixedContentDialog();
+    public void testInsecureDownloadDownloadDoNotShowIncognitoWarning() throws Exception {
+        // Showing an insecure download dialog with a regular profile.
+        showInsecureDownloadDialog();
 
         // Verify the Incognito warning message is NOT shown.
         waitForWarningVisibilityToBe(GONE);
@@ -156,10 +156,10 @@
         });
     }
 
-    private void showMixedContentDialog() {
+    private void showInsecureDownloadDialog() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             Context mContext = mActivityTestRule.getActivity().getApplicationContext();
-            new MixedContentDownloadDialog().show(
+            new InsecureDownloadDialog().show(
                     mContext, mModalDialogManager, FILE_NAME, TOTAL_BYTES, mResultCallback);
         });
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoHistoryLeakageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoHistoryLeakageTest.java
index ce68168..7f6a1dc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoHistoryLeakageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoHistoryLeakageTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
-import android.os.Build.VERSION_CODES;
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.LargeTest;
@@ -28,7 +27,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.browser.customtabs.CustomTabsIntentTestUtils;
 import org.chromium.chrome.browser.customtabs.IncognitoCustomTabActivityTestRule;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -127,8 +125,6 @@
 
     @Test
     @LargeTest
-    @DisableIf.Build(message = "Flaky on Android M, see https://crbug.com/1184759",
-            sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N)
     public void
     testBrowsingHistoryDoNotLeakFromIncognitoTabbedActivity() throws TimeoutException {
         mChromeActivityTestRule.startMainActivityOnBlankPage();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
index 974b92d..36e1bcd7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
@@ -11,7 +11,6 @@
 import android.app.PendingIntent.CanceledException;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.util.Pair;
@@ -31,7 +30,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.chrome.browser.customtabs.CustomTabsIntentTestUtils;
 import org.chromium.chrome.browser.customtabs.IncognitoCustomTabActivityTestRule;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -211,7 +209,6 @@
     @Test
     @MediumTest
     @Feature("Incognito")
-    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
     public void testCloseAllIncognitoNotificationIsDisplayed() {
         launchIncognitoTabAndEnsureNotificationDisplayed();
     }
@@ -219,7 +216,6 @@
     @Test
     @MediumTest
     @Feature("Incognito")
-    @MinAndroidSdkLevel(Build.VERSION_CODES.M)
     @Features.EnableFeatures(ChromeFeatureList.CCT_INCOGNITO)
     public void testCloseAllIncognitoNotificationForIncognitoCCT_DoesNotCloseCCT()
             throws PendingIntent.CanceledException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index 6f3ffcb..ffe28f78 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -459,13 +459,13 @@
                 // Mock to notify the template URL service observer.
                 when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(false);
                 when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);
-                ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+                ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
                 Assert.assertEquals(View.GONE, logoView.getVisibility());
 
                 ntpLayout.setSearchProviderInfo(/* hasLogo = */ true, /* isGoogle */ true);
                 // Mock to notify the template URL service observer.
                 when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(true);
-                ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+                ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
                 Assert.assertEquals(View.VISIBLE, logoView.getVisibility());
             }
         });
@@ -501,7 +501,7 @@
             when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);
             ntpLayout.setSearchProviderInfo(/* hasLogo = */ false, /* isGoogle */ true);
             // Mock to notify the template URL service observer.
-            ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+            ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
 
             Assert.assertEquals(View.GONE, logoView.getVisibility());
             Assert.assertEquals(View.GONE, searchBoxView.getVisibility());
@@ -527,7 +527,7 @@
             when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);
             ntpLayout.setSearchProviderInfo(/* hasLogo = */ true, /* isGoogle */ true);
             // Mock to notify the template URL service observer.
-            ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+            ntpLayout.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
 
             Assert.assertEquals(View.VISIBLE, logoView.getVisibility());
             Assert.assertEquals(View.VISIBLE, searchBoxView.getVisibility());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
index eeba6cc3..56e6506e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
@@ -6,7 +6,6 @@
 
 import android.app.Activity;
 import android.app.Instrumentation.ActivityResult;
-import android.os.Build;
 
 import androidx.test.espresso.intent.Intents;
 import androidx.test.espresso.intent.matcher.IntentMatchers;
@@ -23,7 +22,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 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.Restriction;
 import org.chromium.chrome.R;
@@ -186,8 +184,6 @@
     @Test
     @MediumTest
     @Feature({"Omaha"})
-    @DisableIf.
-    Build(sdk_is_less_than = Build.VERSION_CODES.M, message = "https://crbug.com/1256725")
     public void testCurrentVersionIsSame() throws Exception {
         checkUpdateMenuItemIsNotShowing("1.2.3.4", "1.2.3.4");
     }
@@ -195,8 +191,6 @@
     @Test
     @MediumTest
     @Feature({"Omaha"})
-    @DisableIf.
-    Build(sdk_is_less_than = Build.VERSION_CODES.M, message = "https://crbug.com/1256725")
     public void testCurrentVersionIsNewer() throws Exception {
         checkUpdateMenuItemIsNotShowing("27.0.1453.42", "26.0.1410.49");
     }
@@ -204,8 +198,6 @@
     @Test
     @MediumTest
     @Feature({"Omaha"})
-    @DisableIf.
-    Build(sdk_is_less_than = Build.VERSION_CODES.M, message = "https://crbug.com/1256725")
     public void testNoVersionKnown() throws Exception {
         checkUpdateMenuItemIsNotShowing("1.2.3.4", "0");
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
index a68f446..43a12ba2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
@@ -13,7 +13,6 @@
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 import android.text.TextUtils;
 import android.view.ViewGroup;
@@ -38,7 +37,6 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
 import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -292,7 +290,6 @@
 
     @Test
     @MediumTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.N)
     @CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
     @DisabledTest(message = "https://crbug.com/1291136")
     public void testSwitchToTabSuggestionWhenIncognitoTabOnTop() throws InterruptedException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
index dca4ee83..7ded42c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
@@ -9,11 +9,9 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.LargeTest;
 
 import org.hamcrest.Matchers;
@@ -33,7 +31,6 @@
 import org.chromium.base.test.util.DisableIf;
 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.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -63,8 +60,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@RequiresApi(Build.VERSION_CODES.N)
-@MinAndroidSdkLevel(Build.VERSION_CODES.N)
 public class TabModelMergingTest {
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
index 7b53d3c7..2e86220 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.tabmodel;
 
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.MediumTest;
@@ -265,7 +264,6 @@
      */
     @Test
     @MediumTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.N)
     @CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
     public void testOpenRecentlyClosedTabMultiWindow() throws TimeoutException {
         final ChromeTabbedActivity2 secondActivity =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkThemeColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkThemeColorTest.java
index 55abbab..938a4172 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkThemeColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkThemeColorTest.java
@@ -6,7 +6,6 @@
 
 import android.content.Intent;
 import android.graphics.Color;
-import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 
@@ -16,7 +15,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 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.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -38,7 +36,6 @@
 
     @Test
     @SmallTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     // Customizing status bar color is disallowed for tablets.
     @Feature({"WebApk"})
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActionsNotificationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActionsNotificationTest.java
index 2a0e985..bab45cc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActionsNotificationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActionsNotificationTest.java
@@ -31,7 +31,6 @@
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
@@ -53,7 +52,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@MinAndroidSdkLevel(Build.VERSION_CODES.M) // NotificationManager.getActiveNotifications
 public class WebappActionsNotificationTest {
     private static final String WEB_APP_PATH = "/chrome/test/data/banners/manifest_test_page.html";
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
index 51a0856..288b183 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java
@@ -6,7 +6,6 @@
 
 import android.content.Intent;
 import android.graphics.Color;
-import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 
@@ -16,7 +15,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 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.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -39,7 +37,6 @@
 
     @Test
     @SmallTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     // Customizing status bar color is disallowed for tablets.
     @Feature({"StatusBar", "Webapps"})
@@ -56,7 +53,6 @@
 
     @Test
     @SmallTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     // Customizing status bar color is disallowed for tablets.
     @Feature({"StatusBar", "Webapps"})
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandlerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandlerUnitTest.java
index 173b823..c8b4de8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandlerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandlerUnitTest.java
@@ -9,6 +9,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -33,9 +34,11 @@
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.cc.input.BrowserControlsState;
+import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabAttributes;
 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.browser_ui.util.DimensionCompat;
 import org.chromium.components.embedder_support.view.ContentView;
@@ -66,6 +69,10 @@
     private ContentView mContentView;
     @Mock
     DimensionCompat mDimensionCompat;
+    @Mock
+    private ActivityTabProvider mActivityTabProvider;
+    @Mock
+    private TabModelSelector mTabModelSelector;
 
     private FullscreenHtmlApiHandler mFullscreenHtmlApiHandler;
     private ObservableSupplierImpl<Boolean> mAreControlsHidden;
@@ -144,6 +151,85 @@
     }
 
     @Test
+    public void testFullscreenObserverCalledOncePerSession() {
+        // avoid calling GestureListenerManager/SelectionPopupController
+        doReturn(null).when(mTab).getWebContents();
+        doReturn(true).when(mTab).isUserInteractable();
+
+        mAreControlsHidden.set(false);
+        mFullscreenHtmlApiHandler.setTabForTesting(mTab);
+        FullscreenManager.Observer observer = Mockito.mock(FullscreenManager.Observer.class);
+        mFullscreenHtmlApiHandler.addObserver(observer);
+        FullscreenOptions fullscreenOptions = new FullscreenOptions(false, false);
+
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer, times(1)).onEnterFullscreen(mTab, fullscreenOptions);
+
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        verify(observer, times(1)).onExitFullscreen(mTab);
+
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer, times(2)).onEnterFullscreen(mTab, fullscreenOptions);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        verify(observer, times(2)).onExitFullscreen(mTab);
+
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer, times(3)).onEnterFullscreen(mTab, fullscreenOptions);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        verify(observer, times(3)).onExitFullscreen(mTab);
+    }
+
+    @Test
+    public void testNoObserverWhenCanceledBeforeBeingInteractable() {
+        // avoid calling GestureListenerManager/SelectionPopupController
+        doReturn(null).when(mTab).getWebContents();
+        doReturn(false).when(mTab).isUserInteractable();
+
+        mAreControlsHidden.set(false);
+        mFullscreenHtmlApiHandler.setTabForTesting(mTab);
+        FullscreenManager.Observer observer = Mockito.mock(FullscreenManager.Observer.class);
+        mFullscreenHtmlApiHandler.addObserver(observer);
+        FullscreenOptions fullscreenOptions = new FullscreenOptions(false, false);
+
+        // Before the tab becomes interactable, fullscreen exit gets requested.
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+
+        verify(observer, never()).onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer, never()).onExitFullscreen(mTab);
+    }
+
+    @Test
+    public void testFullscreenObserverInTabNonInteractableState() {
+        doReturn(null).when(mTab).getWebContents();
+        doReturn(false).when(mTab).isUserInteractable(); // Tab not interactable at first.
+
+        mAreControlsHidden.set(false);
+        mFullscreenHtmlApiHandler.setTabForTesting(mTab);
+        mFullscreenHtmlApiHandler.initialize(mActivityTabProvider, mTabModelSelector);
+        FullscreenManager.Observer observer = Mockito.mock(FullscreenManager.Observer.class);
+        mFullscreenHtmlApiHandler.addObserver(observer);
+        FullscreenOptions fullscreenOptions = new FullscreenOptions(false, false);
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer, never()).onEnterFullscreen(mTab, fullscreenOptions);
+
+        // Only after the tab turns interactable does the fullscreen mode is entered.
+        mFullscreenHtmlApiHandler.onTabInteractable(mTab);
+        mFullscreenHtmlApiHandler.onEnterFullscreen(mTab, fullscreenOptions);
+        verify(observer).onEnterFullscreen(mTab, fullscreenOptions);
+
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        mFullscreenHtmlApiHandler.onExitFullscreen(mTab);
+        verify(observer, times(1)).onExitFullscreen(mTab);
+
+        mFullscreenHtmlApiHandler.destroy();
+    }
+
+    @Test
     public void testToastIsShownInFullscreenButNotPictureInPicture() {
         doReturn(mWebContents).when(mTab).getWebContents();
         doReturn(mContentView).when(mTab).getContentView();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/ExponentialBackoffSchedulerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/ExponentialBackoffSchedulerTest.java
index 473cf31..3de7d00 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/ExponentialBackoffSchedulerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/ExponentialBackoffSchedulerTest.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omaha;
 
 import android.content.Context;
-import android.content.Intent;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -59,20 +58,16 @@
         MockExponentialBackoffScheduler scheduler =
                 new MockExponentialBackoffScheduler(PREFERENCE_NAME, context, BACKOFF_MS, MAX_MS);
 
-        Intent intent = new Intent(INTENT_STRING);
-        scheduler.createAlarm(intent, scheduler.calculateNextTimestamp());
-
         // With no failures, expect the base backoff delay.
-        long delay = scheduler.getAlarmTimestamp() - scheduler.getCurrentTime();
+        long delay = scheduler.calculateNextTimestamp() - scheduler.getCurrentTime();
         Assert.assertEquals(
                 "Expected delay of " + BACKOFF_MS + " milliseconds.", BACKOFF_MS, delay);
 
         // With two failures, expect a delay within [BACKOFF_MS, BACKOFF_MS * 2^2].
         scheduler.increaseFailedAttempts();
         scheduler.increaseFailedAttempts();
-        scheduler.createAlarm(intent, scheduler.calculateNextTimestamp());
 
-        delay = scheduler.getAlarmTimestamp() - scheduler.getCurrentTime();
+        delay = scheduler.calculateNextTimestamp() - scheduler.getCurrentTime();
         final long minDelay = BACKOFF_MS;
         final long maxDelay = BACKOFF_MS * (1 << scheduler.getNumFailedAttempts());
         Assert.assertTrue("Expected delay greater than the minimum.", delay >= minDelay);
@@ -80,23 +75,6 @@
     }
 
     /**
-     * Check that the alarm is being set by the class.
-     */
-    @Test
-    @Feature({"Omaha", "Sync"})
-    public void testExponentialBackoffSchedulerAlarmCreation() {
-        TestContext context = new TestContext(RuntimeEnvironment.getApplication());
-
-        MockExponentialBackoffScheduler scheduler =
-                new MockExponentialBackoffScheduler(PREFERENCE_NAME, context, BACKOFF_MS, MAX_MS);
-
-        Intent intent = new Intent(INTENT_STRING);
-        scheduler.createAlarm(intent, scheduler.calculateNextTimestamp());
-        Assert.assertTrue("Never requested the alarm manager.", context.mRequestedAlarmManager);
-        Assert.assertTrue("Never received a call to set the alarm.", scheduler.getAlarmWasSet());
-    }
-
-    /**
      * Ensures that the AlarmManager is the only service requested.
      */
     private static class TestContext extends InMemorySharedPreferencesContext {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/MockExponentialBackoffScheduler.java b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/MockExponentialBackoffScheduler.java
index 33b8d4d..c89bc58 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omaha/MockExponentialBackoffScheduler.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omaha/MockExponentialBackoffScheduler.java
@@ -4,18 +4,12 @@
 
 package org.chromium.chrome.browser.omaha;
 
-import static junit.framework.Assert.assertNotNull;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
 import android.content.Context;
 
 /**
- * Overrides the setAlarm function and allows changing the clock.
+ * Allows changing the clock.
  */
 public class MockExponentialBackoffScheduler extends ExponentialBackoffScheduler {
-    private boolean mAlarmWasSet;
-    private long mAlarmTimestamp;
     private long mCurrentTimestamp;
 
     public MockExponentialBackoffScheduler(
@@ -24,16 +18,6 @@
     }
 
     @Override
-    protected void setAlarm(AlarmManager am, long timestamp, PendingIntent retryPIntent) {
-        // Getting the Intent from the PendingIntent is not straightforward.
-        // The delay is checked by another unit test.
-        assertNotNull(am);
-        assertNotNull(retryPIntent);
-        mAlarmWasSet = true;
-        mAlarmTimestamp = timestamp;
-    }
-
-    @Override
     public long getCurrentTime() {
         return mCurrentTimestamp;
     }
@@ -41,12 +25,4 @@
     public void setCurrentTime(long timestamp) {
         mCurrentTimestamp = timestamp;
     }
-
-    public boolean getAlarmWasSet() {
-        return mAlarmWasSet;
-    }
-
-    public long getAlarmTimestamp() {
-        return mAlarmTimestamp;
-    }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
index dadb3e2e9..a00b552 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
@@ -357,12 +357,12 @@
 
         // If default search engine doesn't have logo, logo shouldn't be visible.
         when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(false);
-        mMediator.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+        mMediator.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
         assertFalse(mMediator.isLogoVisibleForTesting());
 
         // If default search engine has logo, logo should be visible.
         when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(true);
-        mMediator.getLogoCoordinatorForTesting().onTemplateURLServiceChanged();
+        mMediator.getLogoCoordinatorForTesting().onTemplateURLServiceChangedForTesting();
         assertTrue(mMediator.isLogoVisibleForTesting());
     }
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2bdb307..efcdff8 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1927,8 +1927,8 @@
         desc="Message shown in the download shelf when a download is being scanned for an Advanced Protection user">
         <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> is being scanned.
       </message>
-      <message name="IDS_PROMPT_CONFIRM_MIXED_CONTENT_DOWNLOAD"
-        desc="Message shown to the user to validate the download when the download content is a mixed-content download.">
+      <message name="IDS_PROMPT_CONFIRM_INSECURE_DOWNLOAD"
+        desc="Message shown to the user to validate the download when the download content is an insecure download.">
         <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> can't be downloaded securely
       </message>
       <message name="IDS_PROMPT_DOWNLOAD_BLOCKED_TOO_LARGE"
@@ -1955,12 +1955,12 @@
         desc="Message shown in the download shelf when a download is finished being scanned, and issues were found">
         This file is dangerous
       </message>
-      <message name="IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING"
-        desc="Message shown in the download shelf when a download is mixed content, and Chrome needs to show a warning.">
+      <message name="IDS_PROMPT_DOWNLOAD_INSECURE_WARNING"
+        desc="Message shown in the download shelf when a download is insecure, and Chrome needs to show a warning.">
         <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> can't be downloaded securely
       </message>
-      <message name="IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED"
-        desc="Message shown in the download shelf when a download is mixed content, and Chrome has blocked it.">
+      <message name="IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED"
+        desc="Message shown in the download shelf when a download is insecure, and Chrome has blocked it.">
         <ph name="FILE_NAME">$1<ex>bla.exe</ex></ph> can't be downloaded securely
       </message>
       <message name="IDS_PROMPT_APP_DEEP_SCANNING"
@@ -1983,8 +1983,8 @@
          desc="Message shown to the user on chrome://downloads page to explain that this download is blocked because it may be dangerous.">
         This type of file may harm your computer.
       </message>
-      <message name="IDS_BLOCK_REASON_MIXED_CONTENT"
-         desc="Message shown to the user on chrome://downloads page to explain that this download is blocked because it is mixed-content.">
+      <message name="IDS_BLOCK_REASON_INSECURE_DOWNLOAD"
+         desc="Message shown to the user on chrome://downloads page to explain that this download is blocked because it was insecurely delivered.">
         This file can't be downloaded securely
       </message>
       <message name="IDS_BLOCK_REASON_DEEP_SCANNING"
@@ -2017,7 +2017,7 @@
       </message>
 
       <if expr="is_chromeos">
-        <message name="IDS_PROMPT_BLOCKED_MIXED_DOWNLOAD_TITLE"
+        <message name="IDS_PROMPT_BLOCKED_INSECURE_DOWNLOAD_TITLE"
                  desc="In the download notification, a title of the message shown on a download blocked for being insecure.">
           Insecure download blocked
         </message>
@@ -2277,19 +2277,19 @@
         </else>
       </if>
       <message name="IDS_DEEP_SCANNING_ACCESSIBLE_ALERT"
-               desc="The title of a download notification inidcating that the file may be malicious, and the Advanced Protection Program recommends sending the file to Google. This message is for screen reader users">
+               desc="The title of a download notification indicating that the file may be malicious, and the Advanced Protection Program recommends sending the file to Google. This message is for screen reader users">
         <ph name="FILE_NAME">$1<ex>file.exe</ex></ph> is being scanned.
       </message>
       <if expr="is_macosx">
         <then>
-          <message name="IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED_ACCESSIBLE_ALERT"
-                   desc="The title of a download notification inidcating that the download was delivered insecurely. This message is for screen reader users">
+          <message name="IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT"
+                   desc="The title of a download notification indicating that the download was delivered insecurely. This message is for screen reader users">
             <ph name="FILE_NAME">$1<ex>file.exe</ex></ph> can't be downloaded securely.
           </message>
         </then>
         <else>
-          <message name="IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED_ACCESSIBLE_ALERT"
-                   desc="The title of a download notification inidcating that the download was delivered insecurely. This message is for screen reader users">
+          <message name="IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT"
+                   desc="The title of a download notification indicating that the download was delivered insecurely. This message is for screen reader users">
             <ph name="FILE_NAME">$1<ex>file.exe</ex></ph> can't be downloaded securely. Press Shift+F6 to cycle to the downloads bar area.
           </message>
         </else>
@@ -2366,8 +2366,8 @@
                    desc="Download context menu: Show information about interrupted downloads">
             &amp;More information
           </message>
-          <message name="IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT"
-                   desc="Download context menu: Show information about mixed content downloads">
+          <message name="IDS_DOWNLOAD_MENU_LEARN_MORE_INSECURE"
+                   desc="Download context menu: Show information about insecure downloads">
             &amp;Learn more
           </message>
         </if>
@@ -2434,8 +2434,8 @@
                    desc="In Title Case: Download context menu: Show information about interrupted downloads">
             &amp;More Information
           </message>
-          <message name="IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT"
-                   desc="In Title Case: Download context menu: Show information about mixed content downloads">
+          <message name="IDS_DOWNLOAD_MENU_LEARN_MORE_INSECURE"
+                   desc="In Title Case: Download context menu: Show information about insecure downloads">
             &amp;Learn More
           </message>
         </if>  <!-- not use_titlecase -->
@@ -11575,9 +11575,12 @@
         <message name="IDS_MIC_TURNED_OFF_IN_MACOS" desc="Title for the media status bubble that is used when a site requests microphone access, but microphone is blocked on an OS level in macOS.">
           Microphone is turned off in Mac System Preferences
         </message>
-        <message name="IDS_OPEN_PREFERENCES_LINK" desc="Title for the button that takes the user to the Security and Privacy settings in macOS, in order for the user to be able to modify the ability to access camera and microphone for Chrome.">
+        <message name="IDS_OPEN_PREFERENCES_LINK" desc="Title for the button that takes the user to the Security and Privacy settings in macOS, in order for the user to be able to modify the ability to access camera and microphone for Chrome. System Preferences was renamed to System Settings in macOS 13 so this string shouldn't be used past macOS 12.">
           Open Preferences
         </message>
+        <message name="IDS_OPEN_SETTINGS_LINK" desc="Title for the button that takes the user to System Settings in macOS >= 13. The translation for this string should match the translation of the System Settings app title in the macOS target locale. You can find this by selecting the locale from https://support.apple.com/guide/mac-help/mh15217/mac/localeselector. The title of the next page after that includes the desired translation. Note that, for size reasons, the English string omits 'System' and the same contraction may be suitable in other languages.">
+          Open Settings
+        </message>
         <message name="IDS_CAMERA" desc="Description text for the camera.">
           Camera
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_BLOCK_REASON_MIXED_CONTENT.png.sha1 b/chrome/app/generated_resources_grd/IDS_BLOCK_REASON_INSECURE_DOWNLOAD.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_BLOCK_REASON_MIXED_CONTENT.png.sha1
rename to chrome/app/generated_resources_grd/IDS_BLOCK_REASON_INSECURE_DOWNLOAD.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_MENU_LEARN_MORE_INSECURE.png.sha1
similarity index 100%
copy from chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING.png.sha1
copy to chrome/app/generated_resources_grd/IDS_DOWNLOAD_MENU_LEARN_MORE_INSECURE.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT.png.sha1 b/chrome/app/generated_resources_grd/IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT.png.sha1
deleted file mode 100644
index 0a6e9fa..0000000
--- a/chrome/app/generated_resources_grd/IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7ad5285a7c6b795fa53f3b0da9f76ea668a3abe2
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_OPEN_SETTINGS_LINK.png.sha1 b/chrome/app/generated_resources_grd/IDS_OPEN_SETTINGS_LINK.png.sha1
new file mode 100644
index 0000000..3c9aea6a
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_OPEN_SETTINGS_LINK.png.sha1
@@ -0,0 +1 @@
+f89a9005293ca987ac692256d4776a214ee487e7
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_BLOCKED_MIXED_DOWNLOAD_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROMPT_BLOCKED_INSECURE_DOWNLOAD_TITLE.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_PROMPT_BLOCKED_MIXED_DOWNLOAD_TITLE.png.sha1
rename to chrome/app/generated_resources_grd/IDS_PROMPT_BLOCKED_INSECURE_DOWNLOAD_TITLE.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_CONFIRM_MIXED_CONTENT_DOWNLOAD.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROMPT_CONFIRM_INSECURE_DOWNLOAD.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_PROMPT_CONFIRM_MIXED_CONTENT_DOWNLOAD.png.sha1
rename to chrome/app/generated_resources_grd/IDS_PROMPT_CONFIRM_INSECURE_DOWNLOAD.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED.png.sha1
rename to chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED_ACCESSIBLE_ALERT.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED_ACCESSIBLE_ALERT.png.sha1
rename to chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_WARNING.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING.png.sha1
rename to chrome/app/generated_resources_grd/IDS_PROMPT_DOWNLOAD_INSECURE_WARNING.png.sha1
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index c67da58f..011f386 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -1245,6 +1245,9 @@
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION" desc="In the Select-to-speak settings subpage, the label for the control where the user can choose a voice.">
     Voice
   </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TEXT_TO_SPEECH_SETTINGS_LINK" desc="In the Select-to-speak settings subpage, the label for the link to the Text-to-Speech settings page.">
+    System voice settings
+  </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE" desc="In the Select-to-speak settings subpage, the label for the menu option for the voice name for the system default Text-to-Speech voice">
     System voice
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TEXT_TO_SPEECH_SETTINGS_LINK.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TEXT_TO_SPEECH_SETTINGS_LINK.png.sha1
new file mode 100644
index 0000000..644329c
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TEXT_TO_SPEECH_SETTINGS_LINK.png.sha1
@@ -0,0 +1 @@
+c20d2a9d0cdeb347cb8bb9e3c87b30b093c081a5
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 80980eb..a340cb3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -465,8 +465,8 @@
     "download/download_ui_model.cc",
     "download/download_ui_model.h",
     "download/drag_download_item.h",
-    "download/mixed_content_download_blocking.cc",
-    "download/mixed_content_download_blocking.h",
+    "download/insecure_download_blocking.cc",
+    "download/insecure_download_blocking.h",
     "download/notification/multi_profile_download_notifier.cc",
     "download/notification/multi_profile_download_notifier.h",
     "download/offline_item_utils.cc",
@@ -2966,13 +2966,13 @@
       "download/android/duplicate_download_dialog_bridge_delegate.h",
       "download/android/duplicate_download_infobar_delegate.cc",
       "download/android/duplicate_download_infobar_delegate.h",
+      "download/android/insecure_download_dialog_bridge.cc",
+      "download/android/insecure_download_dialog_bridge.h",
+      "download/android/insecure_download_infobar_delegate.cc",
+      "download/android/insecure_download_infobar_delegate.h",
       "download/android/intercept_oma_download_navigation_throttle.cc",
       "download/android/intercept_oma_download_navigation_throttle.h",
       "download/android/items/offline_content_aggregator_factory_android.cc",
-      "download/android/mixed_content_download_dialog_bridge.cc",
-      "download/android/mixed_content_download_dialog_bridge.h",
-      "download/android/mixed_content_download_infobar_delegate.cc",
-      "download/android/mixed_content_download_infobar_delegate.h",
       "download/android/service/download_background_task.cc",
       "download/android/service/download_task_scheduler.cc",
       "download/android/service/download_task_scheduler.h",
@@ -5875,6 +5875,15 @@
       "device_reauth/win/biometric_authenticator_win.h",
       "download/download_status_updater_win.cc",
       "download/trusted_sources_manager_win.cc",
+      "enterprise/platform_auth/cloud_ap_provider_win.cc",
+      "enterprise/platform_auth/cloud_ap_provider_win.h",
+      "enterprise/platform_auth/cloud_ap_utils_win.cc",
+      "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_provider.h",
+      "enterprise/platform_auth/platform_auth_provider_manager.cc",
+      "enterprise/platform_auth/platform_auth_provider_manager.h",
       "enterprise/signals/device_info_fetcher_win.cc",
       "enterprise/signals/device_info_fetcher_win.h",
       "first_run/first_run_internal_win.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a8fbdfe..dac02f0 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -8128,6 +8128,10 @@
      flag_descriptions::kFedCmMultiIdpDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kFedCmMultipleIdentityProviders)},
 
+    {"fedcm-user-info", flag_descriptions::kFedCmUserInfoName,
+     flag_descriptions::kFedCmUserInfoDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kFedCmUserInfo)},
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"bluetooth-sessionized-metrics",
      flag_descriptions::kBluetoothSessionizedMetricsName,
diff --git a/chrome/browser/android/metrics/BUILD.gn b/chrome/browser/android/metrics/BUILD.gn
index 3981aa1..8ad49bc 100644
--- a/chrome/browser/android/metrics/BUILD.gn
+++ b/chrome/browser/android/metrics/BUILD.gn
@@ -45,6 +45,8 @@
       "//base:base_java_test_support",
       "//chrome/android:chrome_java",
       "//chrome/browser/flags:java",
+      "//chrome/browser/profiles/android:java",
+      "//chrome/browser/signin/services/android:java",
       "//chrome/browser/tab:java",
       "//chrome/test/android:chrome_java_integration_test_support",
       "//components/metrics:metrics_java",
diff --git a/chrome/browser/android/metrics/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java b/chrome/browser/android/metrics/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
index fafd9d5..6627be1 100644
--- a/chrome/browser/android/metrics/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
+++ b/chrome/browser/android/metrics/javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java
@@ -14,6 +14,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge;
@@ -22,6 +23,8 @@
 import org.chromium.chrome.browser.browsing_data.TimePeriod;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.metrics.util.UkmUtilsForTest;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
@@ -32,6 +35,7 @@
  * Android UKM tests.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
+@Batch(Batch.PER_CLASS)
 @CommandLineFlags.
 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, MetricsSwitches.FORCE_ENABLE_METRICS_REPORTING})
 public class UkmTest {
@@ -54,8 +58,11 @@
         ChromeTabUtils.closeAllTabs(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
 
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> { Assert.assertTrue(UkmUtilsForTest.isEnabled()); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Profile profile = Profile.getLastUsedRegularProfile();
+            UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(profile, true);
+            Assert.assertTrue(UkmUtilsForTest.isEnabled());
+        });
 
         long originalClientId =
                 TestThreadUtils
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
index 191b93b..73b0092 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
@@ -494,7 +494,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void GetWebAppCompressedIconData(content::BrowserContext* context,
                                  const std::string& web_app_id,
-                                 IconType icon_type,
                                  int size_in_dip,
                                  ui::ResourceScaleFactor scale_factor,
                                  LoadIconCallback callback) {
@@ -507,7 +506,7 @@
   DCHECK(web_app_provider);
   scoped_refptr<AppIconLoader> icon_loader =
       base::MakeRefCounted<AppIconLoader>(
-          icon_type, size_in_dip, /*is_placeholder_icon=*/false,
+          IconType::kCompressed, size_in_dip, /*is_placeholder_icon=*/false,
           IconEffects::kNone, kInvalidIconResource, std::move(callback));
   icon_loader->GetWebAppCompressedIconData(web_app_id, scale_factor,
                                            web_app_provider->icon_manager());
@@ -515,7 +514,6 @@
 
 void GetChromeAppCompressedIconData(content::BrowserContext* context,
                                     const std::string& extension_id,
-                                    IconType icon_type,
                                     int size_in_dip,
                                     ui::ResourceScaleFactor scale_factor,
                                     LoadIconCallback callback) {
@@ -523,7 +521,7 @@
 
   scoped_refptr<AppIconLoader> icon_loader =
       base::MakeRefCounted<AppIconLoader>(
-          icon_type, size_in_dip, /*is_placeholder_icon=*/false,
+          IconType::kCompressed, size_in_dip, /*is_placeholder_icon=*/false,
           IconEffects::kNone, kInvalidIconResource, std::move(callback));
   icon_loader->GetChromeAppCompressedIconData(
       extensions::ExtensionRegistry::Get(context)->GetInstalledExtension(
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_factory.h b/chrome/browser/apps/app_service/app_icon/app_icon_factory.h
index 1b2ce3d..5d9734a 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_factory.h
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_factory.h
@@ -110,7 +110,6 @@
 // Requests a compressed icon data for an web app identified by `web_app_id`.
 void GetWebAppCompressedIconData(content::BrowserContext* context,
                                  const std::string& web_app_id,
-                                 IconType icon_type,
                                  int size_in_dip,
                                  ui::ResourceScaleFactor scale_factor,
                                  LoadIconCallback callback);
@@ -119,7 +118,6 @@
 // `extension_id`.
 void GetChromeAppCompressedIconData(content::BrowserContext* context,
                                     const std::string& extension_id,
-                                    IconType icon_type,
                                     int size_in_dip,
                                     ui::ResourceScaleFactor scale_factor,
                                     LoadIconCallback callback);
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_test_util.cc b/chrome/browser/apps/app_service/app_icon/app_icon_test_util.cc
index bda689a..e9207e5 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_test_util.cc
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_test_util.cc
@@ -97,13 +97,11 @@
 
 void FakePublisherForIconTest::GetCompressedIconData(
     const std::string& app_id,
-    apps::IconType icon_type,
     int32_t size_in_dip,
     ui::ResourceScaleFactor scale_factor,
     apps::LoadIconCallback callback) {
-  apps::GetWebAppCompressedIconData(proxy()->profile(), app_id, icon_type,
-                                    size_in_dip, scale_factor,
-                                    std::move(callback));
+  apps::GetWebAppCompressedIconData(proxy()->profile(), app_id, size_in_dip,
+                                    scale_factor, std::move(callback));
 }
 #endif
 
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_test_util.h b/chrome/browser/apps/app_service/app_icon/app_icon_test_util.h
index a7b6716..4076402 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_test_util.h
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_test_util.h
@@ -78,7 +78,6 @@
                 apps::LoadIconCallback callback) override {}
 
   void GetCompressedIconData(const std::string& app_id,
-                             apps::IconType icon_type,
                              int32_t size_in_dip,
                              ui::ResourceScaleFactor scale_factor,
                              apps::LoadIconCallback callback) override;
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_writer.cc b/chrome/browser/apps/app_service/app_icon/app_icon_writer.cc
index 08c3362..b0fddd57 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_writer.cc
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_writer.cc
@@ -117,7 +117,7 @@
 
     pending_results_it->second.scale_factors.insert(scale_factor);
     publisher->GetCompressedIconData(
-        app_id, icon_type, size_in_dip, scale_factor,
+        app_id, size_in_dip, scale_factor,
         base::BindOnce(&AppIconWriter::OnIconLoad,
                        weak_ptr_factory_.GetWeakPtr(), app_id, size_in_dip,
                        icon_effects, icon_type, scale_factor));
diff --git a/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc b/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
index a402c96..57b015e3 100644
--- a/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
+++ b/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
@@ -120,12 +120,10 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   apps::IconValuePtr GetCompressedIconData(
       const std::string& app_id,
-      IconType icon_type,
       ui::ResourceScaleFactor scale_factor) {
     base::test::TestFuture<apps::IconValuePtr> result;
-    apps::GetChromeAppCompressedIconData(profile(), app_id, icon_type,
-                                         kSizeInDip, scale_factor,
-                                         result.GetCallback());
+    apps::GetChromeAppCompressedIconData(profile(), app_id, kSizeInDip,
+                                         scale_factor, result.GetCallback());
     return result.Take();
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -181,7 +179,7 @@
 }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-TEST_F(ChromeAppsIconFactoryTest, GetCompressedIconDataForUncompressedIcon) {
+TEST_F(ChromeAppsIconFactoryTest, GetCompressedIconData) {
   // Generate the source uncompressed icon for comparing.
   std::vector<uint8_t> src_data1;
   std::vector<uint8_t> src_data2;
@@ -190,47 +188,14 @@
   GenerateExtensionAppCompressedIcon(kPackagedApp1Id, /*scale=*/2.0, src_data2,
                                      /*skip_effects=*/true);
 
-  IconValuePtr icon1 =
-      GetCompressedIconData(kPackagedApp1Id, IconType::kUncompressed,
-                            ui::ResourceScaleFactor::k100Percent);
-  IconValuePtr icon2 =
-      GetCompressedIconData(kPackagedApp1Id, IconType::kUncompressed,
-                            ui::ResourceScaleFactor::k200Percent);
+  IconValuePtr icon1 = GetCompressedIconData(
+      kPackagedApp1Id, ui::ResourceScaleFactor::k100Percent);
+  IconValuePtr icon2 = GetCompressedIconData(
+      kPackagedApp1Id, ui::ResourceScaleFactor::k200Percent);
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
 }
 
-TEST_F(ChromeAppsIconFactoryTest, GetCompressedIconDataForStandardIcon) {
-  // Generate the source uncompressed icon for comparing.
-  std::vector<uint8_t> src_data1;
-  std::vector<uint8_t> src_data2;
-  GenerateExtensionAppCompressedIcon(kPackagedApp1Id, /*scale=*/1.0, src_data1,
-                                     /*skip_effects=*/true);
-  GenerateExtensionAppCompressedIcon(kPackagedApp1Id, /*scale=*/2.0, src_data2,
-                                     /*skip_effects=*/true);
-
-  IconValuePtr icon1 =
-      GetCompressedIconData(kPackagedApp1Id, IconType::kStandard,
-                            ui::ResourceScaleFactor::k100Percent);
-  IconValuePtr icon2 =
-      GetCompressedIconData(kPackagedApp1Id, IconType::kStandard,
-                            ui::ResourceScaleFactor::k200Percent);
-  VerifyCompressedIcon(src_data1, *icon1);
-  VerifyCompressedIcon(src_data2, *icon2);
-}
-
-TEST_F(ChromeAppsIconFactoryTest, GetCompressedIconDataForCompressedIcon) {
-  // Generate the source compressed icon for comparing.
-  std::vector<uint8_t> src_data;
-  GenerateExtensionAppCompressedIcon(kPackagedApp1Id, /*scale=*/1.0, src_data,
-                                     /*skip_effects=*/true);
-
-  IconValuePtr icon =
-      GetCompressedIconData(kPackagedApp1Id, IconType::kCompressed,
-                            ui::ResourceScaleFactor::k100Percent);
-  VerifyCompressedIcon(src_data, *icon);
-}
-
 class AppServiceChromeAppIconTest : public ChromeAppsIconFactoryTest {
  public:
   void SetUp() override {
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 405b8f4..ae361942 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
@@ -279,10 +279,9 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   apps::IconValuePtr GetWebAppCompressedIconData(
       const std::string& app_id,
-      IconType icon_type,
       ui::ResourceScaleFactor scale_factor) {
     base::test::TestFuture<apps::IconValuePtr> result;
-    apps::GetWebAppCompressedIconData(profile(), app_id, icon_type, kSizeInDip,
+    apps::GetWebAppCompressedIconData(profile(), app_id, kSizeInDip,
                                       scale_factor, result.GetCallback());
     return result.Take();
   }
@@ -790,11 +789,9 @@
   // Verify getting the compressed icon data for the compressed icon with icon
   // effects.
   auto icon1 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
   auto icon2 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
@@ -832,34 +829,12 @@
   // Verify getting the compressed icon data for the compressed icon with icon
   // effects.
   auto icon1 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
   auto icon2 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
-
-  // Verify getting the compressed icon data for the uncompressed icon.
-  auto icon3 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
-  auto icon4 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon3);
-  VerifyCompressedIcon(src_data2, *icon4);
-
-  // Verify getting the compressed icon data for the standard icon.
-  auto icon5 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k100Percent);
-  auto icon6 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon5);
-  VerifyCompressedIcon(src_data2, *icon6);
 }
 
 TEST_F(WebAppIconFactoryTest, GetNonMaskableNonEffectCompressedIcon) {
@@ -891,11 +866,9 @@
                                scale_to_size_in_px, scale2, src_data2);
 
   auto icon1 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
   auto icon2 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
@@ -932,34 +905,12 @@
 
   // Verify getting the compressed icon data for the compressed icon.
   auto icon1 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
   auto icon2 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
-
-  // Verify getting the compressed icon data for the uncompressed icon.
-  auto icon3 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
-  auto icon4 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon3);
-  VerifyCompressedIcon(src_data2, *icon4);
-
-  // Verify getting the compressed icon data for the standard icon.
-  auto icon5 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k100Percent);
-  auto icon6 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon5);
-  VerifyCompressedIcon(src_data2, *icon6);
 }
 
 TEST_F(WebAppIconFactoryTest, GetMaskableCompressedIcon) {
@@ -995,34 +946,12 @@
 
   // Verify getting the compressed icon data for the compressed icon.
   auto icon1 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k100Percent);
   auto icon2 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kCompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
+      GetWebAppCompressedIconData(app_id, ui::ResourceScaleFactor::k200Percent);
 
   VerifyCompressedIcon(src_data1, *icon1);
   VerifyCompressedIcon(src_data2, *icon2);
-
-  // Verify getting the compressed icon data for the uncompressed icon.
-  auto icon3 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k100Percent);
-  auto icon4 =
-      GetWebAppCompressedIconData(app_id, apps::IconType::kUncompressed,
-                                  ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon3);
-  VerifyCompressedIcon(src_data2, *icon4);
-
-  // Verify getting the compressed icon data for the standard icon.
-  auto icon5 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k100Percent);
-  auto icon6 = GetWebAppCompressedIconData(
-      app_id, apps::IconType::kStandard, ui::ResourceScaleFactor::k200Percent);
-
-  VerifyCompressedIcon(src_data1, *icon5);
-  VerifyCompressedIcon(src_data2, *icon6);
 }
 
 class AppServiceWebAppIconTest : public WebAppIconFactoryTest {
diff --git a/chrome/browser/apps/app_service/publishers/app_publisher.cc b/chrome/browser/apps/app_service/publishers/app_publisher.cc
index d696366..e77ed10 100644
--- a/chrome/browser/apps/app_service/publishers/app_publisher.cc
+++ b/chrome/browser/apps/app_service/publishers/app_publisher.cc
@@ -47,7 +47,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void AppPublisher::GetCompressedIconData(const std::string& app_id,
-                                         apps::IconType icon_type,
                                          int32_t size_in_dip,
                                          ui::ResourceScaleFactor scale_factor,
                                          LoadIconCallback callback) {
diff --git a/chrome/browser/apps/app_service/publishers/app_publisher.h b/chrome/browser/apps/app_service/publishers/app_publisher.h
index adadd41..3ea347b 100644
--- a/chrome/browser/apps/app_service/publishers/app_publisher.h
+++ b/chrome/browser/apps/app_service/publishers/app_publisher.h
@@ -84,7 +84,6 @@
   // is identified by `size_in_dip` and `scale_factor`. Calls `callback` with
   // the result.
   virtual void GetCompressedIconData(const std::string& app_id,
-                                     IconType icon_type,
                                      int32_t size_in_dip,
                                      ui::ResourceScaleFactor scale_factor,
                                      LoadIconCallback callback);
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
index e3ebe3d..0a61b3a 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
@@ -183,13 +183,11 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void ExtensionAppsChromeOs::GetCompressedIconData(
     const std::string& app_id,
-    apps::IconType icon_type,
     int32_t size_in_dip,
     ui::ResourceScaleFactor scale_factor,
     LoadIconCallback callback) {
-  apps::GetChromeAppCompressedIconData(profile(), app_id, icon_type,
-                                       size_in_dip, scale_factor,
-                                       std::move(callback));
+  apps::GetChromeAppCompressedIconData(profile(), app_id, size_in_dip,
+                                       scale_factor, std::move(callback));
 }
 #endif
 
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
index ddeb0b0e..6ef6a0f 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
@@ -81,7 +81,6 @@
   // is identified by `size_in_dip` and `scale_factor`. Calls `callback` with
   // the result.
   void GetCompressedIconData(const std::string& app_id,
-                             IconType icon_type,
                              int32_t size_in_dip,
                              ui::ResourceScaleFactor scale_factor,
                              LoadIconCallback callback) override;
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index 249323f8..f6b1558 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -914,6 +914,10 @@
 
   event_router->DispatchEventWithLazyListener(
       extension_misc::kAccessibilityCommonExtensionId, std::move(event));
+
+  if (magnifier_bounds_observer_for_test_) {
+    magnifier_bounds_observer_for_test_.Run();
+  }
 }
 
 void AccessibilityManager::EnableVirtualKeyboard(bool enabled) {
@@ -2131,6 +2135,11 @@
   caret_bounds_observer_for_test_ = observer;
 }
 
+void AccessibilityManager::SetMagnifierBoundsObserverForTest(
+    base::RepeatingCallback<void()> observer) {
+  magnifier_bounds_observer_for_test_ = observer;
+}
+
 void AccessibilityManager::SetSwitchAccessKeysForTest(
     const std::set<int>& action_keys,
     const std::string& pref_name) {
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.h b/chrome/browser/ash/accessibility/accessibility_manager.h
index 514774a..fc1f3830 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.h
+++ b/chrome/browser/ash/accessibility/accessibility_manager.h
@@ -390,6 +390,8 @@
       base::RepeatingCallback<void()> observer);
   void SetCaretBoundsObserverForTest(
       base::RepeatingCallback<void(const gfx::Rect&)> observer);
+  void SetMagnifierBoundsObserverForTest(
+      base::RepeatingCallback<void()> observer);
   void SetSwitchAccessKeysForTest(const std::set<int>& action_keys,
                                   const std::string& pref_name);
 
@@ -619,6 +621,7 @@
   base::RepeatingCallback<void()> select_to_speak_state_observer_for_test_;
   base::RepeatingCallback<void(const gfx::Rect&)>
       caret_bounds_observer_for_test_;
+  base::RepeatingCallback<void()> magnifier_bounds_observer_for_test_;
   base::OnceClosure enhanced_network_tts_waiter_for_test_;
 
   // Used to set the audio focus enforcement type for ChromeVox.
diff --git a/chrome/browser/ash/accessibility/autoclick_browsertest.cc b/chrome/browser/ash/accessibility/autoclick_browsertest.cc
index dcc2422..0ecd8e7 100644
--- a/chrome/browser/ash/accessibility/autoclick_browsertest.cc
+++ b/chrome/browser/ash/accessibility/autoclick_browsertest.cc
@@ -104,9 +104,10 @@
     base::ScopedAllowBlockingForTesting allow_blocking;
     std::string script = base::StringPrintf(R"JS(
       (async function() {
-        window.accessibilityCommon.setAutoclickLoadCallbackForTest(() => {
-            window.domAutomationController.send('ready');
-          });
+        window.accessibilityCommon.setFeatureLoadCallbackForTest('autoclick',
+            () => {
+              window.domAutomationController.send('ready');
+            });
       })();
     )JS");
     std::string result =
diff --git a/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc b/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc
index 74600fa..053e5a6c 100644
--- a/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc
+++ b/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc
@@ -5,18 +5,30 @@
 #include <string>
 
 #include "ash/accessibility/magnifier/fullscreen_magnifier_controller.h"
+#include "ash/constants/ash_pref_names.h"
 #include "ash/shell.h"
 #include "base/command_line.h"
+#include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
+#include "chrome/browser/ash/accessibility/accessibility_manager.h"
+#include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
 #include "chrome/browser/ash/accessibility/html_test_utils.h"
 #include "chrome/browser/ash/accessibility/magnification_manager.h"
 #include "chrome/browser/ash/accessibility/magnifier_animation_waiter.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/accessibility_notification_waiter.h"
 #include "content/public/test/browser_test.h"
+#include "extensions/browser/browsertest_util.h"
+#include "extensions/browser/extension_host_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/events/test/event_generator.h"
 
 namespace ash {
 
@@ -26,7 +38,7 @@
 const char kTestHtmlContent[] =
     "<body style=\"margin-top:0;margin-left:0\">"
     "<button type=\"button\" name=\"test_button_1\" id=\"test_button\" "
-    "style=\"margin-left:200;margin-top:200;width:100;height:50\">"
+    "style=\"margin-left:0;margin-top:0;width:100;height:50\">"
     "Big Button 1</button>"
     "</body>";
 
@@ -34,18 +46,6 @@
   return Shell::Get()->fullscreen_magnifier_controller();
 }
 
-bool IsMagnifierEnabled() {
-  return MagnificationManager::Get()->IsMagnifierEnabled();
-}
-
-void SetMagnifierEnabled(bool enabled) {
-  MagnificationManager::Get()->SetMagnifierEnabled(true);
-}
-
-void MoveMagnifierWindow(int x, int y) {
-  GetFullscreenMagnifierController()->MoveWindow(x, y, false);
-}
-
 gfx::Rect GetViewPort() {
   return GetFullscreenMagnifierController()->GetViewportRect();
 }
@@ -69,19 +69,102 @@
   }
 
   void SetUpOnMainThread() override {
-    SetMagnifierEnabled(true);
+    console_observer_ = std::make_unique<ExtensionConsoleErrorObserver>(
+        browser()->profile(), extension_misc::kAccessibilityCommonExtensionId);
 
-    // Confirms that magnifier is enabled.
-    EXPECT_TRUE(IsMagnifierEnabled());
-    EXPECT_EQ(2.0f, GetFullscreenMagnifierController()->GetScale());
+    aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
+    generator_ = std::make_unique<ui::test::EventGenerator>(root_window);
+    AccessibilityManager::Get()->SetMagnifierBoundsObserverForTest(
+        base::BindRepeating(
+            &FullscreenMagnifierControllerTest::MagnifierBoundsChanged,
+            weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  // Loads a page with the given URL and then starts up Magnifier.
+  void LoadURLAndMagnifier(const std::string& url) {
+    content::AccessibilityNotificationWaiter waiter(
+        GetWebContents(), ui::kAXModeComplete, ax::mojom::Event::kLoadComplete);
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url)));
+    ASSERT_TRUE(waiter.WaitForNotification());
+
+    LoadMagnifier();
+  }
+
+  void LoadMagnifier() {
+    extensions::ExtensionHostTestHelper host_helper(
+        browser()->profile(), extension_misc::kAccessibilityCommonExtensionId);
+    MagnificationManager::Get()->SetMagnifierEnabled(true);
 
     // FullscreenMagnifierController moves the magnifier window with animation
-    // when the magnifier is set to be enabled. It will move the mouse cursor
+    // when the magnifier is first enabled. It will move the mouse cursor
     // when the animation completes. Wait until the animation completes, so that
     // the mouse movement won't affect the position of magnifier window later.
-    MagnifierAnimationWaiter waiter(GetFullscreenMagnifierController());
-    waiter.Wait();
-    base::RunLoop().RunUntilIdle();
+    MagnifierAnimationWaiter magnifier_waiter(
+        GetFullscreenMagnifierController());
+    magnifier_waiter.Wait();
+    host_helper.WaitForHostCompletedFirstLoad();
+
+    // Start in a known location, centered on the screen.
+    MoveMagnifierWindow(600, 400);
+    ASSERT_EQ(GetViewPort().CenterPoint(), gfx::Point(600, 400));
+
+    WaitForMagnifierJSReady();
+
+    // Confirms that magnifier is enabled.
+    ASSERT_TRUE(MagnificationManager::Get()->IsMagnifierEnabled());
+    // Check default scale is as expected.
+    EXPECT_EQ(2.0f, GetFullscreenMagnifierController()->GetScale());
+  }
+
+  void WaitForMagnifierJSReady() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    std::string script = base::StringPrintf(R"JS(
+      (async function() {
+        window.accessibilityCommon.setFeatureLoadCallbackForTest('magnifier',
+            () => {
+              window.accessibilityCommon.magnifier_.setIsInitializingForTest(
+                  false);
+              window.domAutomationController.send('ready');
+            });
+      })();
+    )JS");
+    std::string result =
+        extensions::browsertest_util::ExecuteScriptInBackgroundPage(
+            browser()->profile(),
+            extension_misc::kAccessibilityCommonExtensionId, script);
+    ASSERT_EQ("ready", result);
+  }
+
+  void MoveMagnifierWindow(int x_center, int y_center) {
+    gfx::Rect bounds = GetViewPort();
+    GetFullscreenMagnifierController()->MoveWindow(
+        x_center - bounds.width() / 2, y_center - bounds.height() / 2,
+        /*animate=*/false);
+    WaitForMagnifierBoundsChangedTo(gfx::Point(x_center, y_center));
+  }
+
+  void WaitForMagnifierBoundsChangedTo(gfx::Point center_point) {
+    while (GetViewPort().CenterPoint() != center_point) {
+      WaitForMagnifierBoundsChanged();
+    }
+  }
+
+  void WaitForMagnifierBoundsChanged() {
+    base::RunLoop loop;
+    bounds_changed_waiter_ = loop.QuitClosure();
+    loop.Run();
+  }
+
+  void MagnifierBoundsChanged() {
+    if (!bounds_changed_waiter_)
+      return;
+
+    std::move(bounds_changed_waiter_).Run();
+
+    // Wait for any additional animation to complete.
+    MagnifierAnimationWaiter magnifier_waiter(
+        GetFullscreenMagnifierController());
+    magnifier_waiter.Wait();
   }
 
   content::WebContents* GetWebContents() {
@@ -89,32 +172,129 @@
   }
 
   void SetFocusOnElement(const std::string& element_id) {
-    ExecuteScript(GetWebContents(),
-                  "document.getElementById('" + element_id + "').focus();");
+    ExecuteScript(
+        GetWebContents(),
+        base::StringPrintf(R"(document.getElementById('%s').focus();)",
+                           element_id.c_str()));
   }
+
+  std::unique_ptr<ui::test::EventGenerator> generator_;
+
+ private:
+  std::unique_ptr<ExtensionConsoleErrorObserver> console_observer_;
+  base::OnceClosure bounds_changed_waiter_;
+  base::WeakPtrFactory<FullscreenMagnifierControllerTest> weak_ptr_factory_{
+      this};
 };
 
-// Test is flaky on ChromeOS: crbug.com/1150753
 IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
-                       DISABLED_FollowFocusOnWebButtonContained) {
-  DCHECK(IsMagnifierEnabled());
-  ASSERT_NO_FATAL_FAILURE(EXPECT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), GURL(std::string(kDataURIPrefix) + kTestHtmlContent))));
+                       FollowFocusOnWebButton) {
+  LoadURLAndMagnifier(std::string(kDataURIPrefix) + kTestHtmlContent);
 
-  // Move magnifier window to contain the button.
+  // Move magnifier window to exclude the button.
   const gfx::Rect button_bounds =
       GetControlBoundsInRoot(GetWebContents(), "test_button");
-  MoveMagnifierWindow(button_bounds.x() - 100, button_bounds.y() - 100);
+  MoveMagnifierWindow(button_bounds.right() + GetViewPort().width(),
+                      button_bounds.bottom() + GetViewPort().height());
   const gfx::Rect view_port_before_focus = GetViewPort();
-  EXPECT_TRUE(view_port_before_focus.Contains(button_bounds));
+  EXPECT_FALSE(view_port_before_focus.Contains(button_bounds));
 
   // Set the focus on the button.
   SetFocusOnElement("test_button");
+  WaitForMagnifierBoundsChanged();
 
-  // Verify the magnifier window is not moved and still contains the button.
+  // Verify the magnifier window has moved to contain the button.
   const gfx::Rect view_port_after_focus = GetViewPort();
   EXPECT_TRUE(view_port_after_focus.Contains(button_bounds));
-  EXPECT_EQ(view_port_before_focus, view_port_after_focus);
 }
 
+IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
+                       MovesContinuouslyWithMouse) {
+  browser()->profile()->GetPrefs()->SetInteger(
+      prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
+      static_cast<int>(MagnifierMouseFollowingMode::kContinuous));
+  LoadMagnifier();
+
+  // Screen resolution 1200x800.
+  gfx::Point center_point(600, 400);
+  EXPECT_EQ(GetViewPort().CenterPoint(), center_point);
+
+  for (int i = 0; i < 3; i++) {
+    // Move left and down. The viewport should move towards the mouse but not
+    // all the way to it.
+    gfx::Point mouse_point = gfx::Point(500 - i * 50, 500 + i * 50);
+    generator_->MoveMouseTo(mouse_point);
+    // No need to wait: Without going through the extension loop needed for
+    // focus observation, the movement is all within the same process.
+    EXPECT_GT(GetViewPort().CenterPoint().x(), mouse_point.x());
+    EXPECT_LT(GetViewPort().CenterPoint().x(), center_point.x());
+    EXPECT_LT(GetViewPort().CenterPoint().y(), mouse_point.y());
+    EXPECT_GT(GetViewPort().CenterPoint().y(), center_point.y());
+    center_point = GetViewPort().CenterPoint();
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
+                       MovesWithMouseAtEdge) {
+  browser()->profile()->GetPrefs()->SetInteger(
+      prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
+      static_cast<int>(MagnifierMouseFollowingMode::kEdge));
+  LoadMagnifier();
+
+  // Screen resolution 1200x800.
+  gfx::Point initial_center = GetViewPort().CenterPoint();
+  gfx::Point mouse_point(550, 450);
+  generator_->MoveMouseTo(mouse_point);
+  // No need to wait: Without going through the extension loop needed for focus
+  // observation, the movement is all within the same process.
+  EXPECT_EQ(GetViewPort().CenterPoint(), initial_center);
+
+  // Move left and down. The viewport should not move as we are still within it.
+  gfx::Point new_mouse_point = gfx::Point(500, 500);
+  generator_->MoveMouseTo(new_mouse_point);
+  EXPECT_EQ(GetViewPort().CenterPoint(), initial_center);
+
+  // Move mouse to the left/bottom edge. The viewport should move in that
+  // direction. Note we have to scale the mouse point based on the magnifer
+  // scale to actually reach the edge of the viewport.
+  new_mouse_point = gfx::Point(0, 800);
+  generator_->MoveMouseTo(new_mouse_point);
+  EXPECT_GT(GetViewPort().CenterPoint().x(), new_mouse_point.x());
+  EXPECT_LT(GetViewPort().CenterPoint().x(), initial_center.x());
+  EXPECT_LT(GetViewPort().CenterPoint().y(), new_mouse_point.y());
+  EXPECT_GT(GetViewPort().CenterPoint().y(), initial_center.y());
+}
+
+IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
+                       ChangeZoomWithAccelerator) {
+  LoadMagnifier();
+
+  // Press keyboard shortcut to zoom in. Default zoom is 2.0.
+  generator_->PressAndReleaseKey(ui::VKEY_BRIGHTNESS_UP,
+                                 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
+  float scale = GetFullscreenMagnifierController()->GetScale();
+  EXPECT_LT(2.0f, scale);
+
+  // Keyboard shortcut to zoom out.
+  generator_->PressAndReleaseKey(ui::VKEY_BRIGHTNESS_DOWN,
+                                 ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
+  // Note the scale might not be 2.0 again.
+  EXPECT_GT(scale, GetFullscreenMagnifierController()->GetScale());
+}
+
+IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, ChangeZoomWithPrefs) {
+  LoadMagnifier();
+
+  // Change the bounds pref.
+  browser()->profile()->GetPrefs()->SetDouble(
+      prefs::kAccessibilityScreenMagnifierScale, 4.0);
+  EXPECT_EQ(4.0, GetFullscreenMagnifierController()->GetScale());
+
+  browser()->profile()->GetPrefs()->SetDouble(
+      prefs::kAccessibilityScreenMagnifierScale, 10.5);
+  EXPECT_EQ(10.5, GetFullscreenMagnifierController()->GetScale());
+}
+
+// TODO(crbug.com/261462562): Add centered mouse following mode browsertest.
+
 }  // namespace ash
diff --git a/chrome/browser/ash/app_mode/kiosk_app_data.cc b/chrome/browser/ash/app_mode/kiosk_app_data.cc
index 35a77acc..73a3a07 100644
--- a/chrome/browser/ash/app_mode/kiosk_app_data.cc
+++ b/chrome/browser/ash/app_mode/kiosk_app_data.cc
@@ -100,7 +100,7 @@
   // extensions::SandboxedUnpackerClient
   void OnUnpackSuccess(const base::FilePath& temp_dir,
                        const base::FilePath& extension_root,
-                       std::unique_ptr<base::DictionaryValue> original_manifest,
+                       std::unique_ptr<base::Value::Dict> original_manifest,
                        const extensions::Extension* extension,
                        const SkBitmap& install_icon,
                        extensions::declarative_net_request::RulesetInstallPrefs
diff --git a/chrome/browser/ash/app_mode/kiosk_external_update_validator.cc b/chrome/browser/ash/app_mode/kiosk_external_update_validator.cc
index f90b0c1..d90ca35a 100644
--- a/chrome/browser/ash/app_mode/kiosk_external_update_validator.cc
+++ b/chrome/browser/ash/app_mode/kiosk_external_update_validator.cc
@@ -54,7 +54,7 @@
 void KioskExternalUpdateValidator::OnUnpackSuccess(
     const base::FilePath& temp_dir,
     const base::FilePath& extension_dir,
-    std::unique_ptr<base::DictionaryValue> original_manifest,
+    std::unique_ptr<base::Value::Dict> original_manifest,
     const extensions::Extension* extension,
     const SkBitmap& install_icon,
     extensions::declarative_net_request::RulesetInstallPrefs
diff --git a/chrome/browser/ash/app_mode/kiosk_external_update_validator.h b/chrome/browser/ash/app_mode/kiosk_external_update_validator.h
index 256b6d3f..12edd44 100644
--- a/chrome/browser/ash/app_mode/kiosk_external_update_validator.h
+++ b/chrome/browser/ash/app_mode/kiosk_external_update_validator.h
@@ -57,7 +57,7 @@
   void OnUnpackFailure(const extensions::CrxInstallError& error) override;
   void OnUnpackSuccess(const base::FilePath& temp_dir,
                        const base::FilePath& extension_dir,
-                       std::unique_ptr<base::DictionaryValue> original_manifest,
+                       std::unique_ptr<base::Value::Dict> original_manifest,
                        const extensions::Extension* extension,
                        const SkBitmap& install_icon,
                        extensions::declarative_net_request::RulesetInstallPrefs
diff --git a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher.cc b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher.cc
index cf07469..fc9d5d6d 100644
--- a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher.cc
+++ b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher.cc
@@ -55,7 +55,7 @@
   // temporary |app_id| which will be changed to the correct |app_id| once the
   // authentication is done. The only key that is safe to be used as identifier
   // for Kiosk web apps is |install_url|.
-  auto app_id = web_app_provider_->registrar().LookUpAppIdByInstallUrl(
+  auto app_id = web_app_provider_->registrar_unsafe().LookUpAppIdByInstallUrl(
       GetCurrentApp()->install_url());
   if (!app_id || app_id->empty()) {
     delegate_->InitializeNetwork();
@@ -64,8 +64,9 @@
 
   // If the installed app is a placeholder (similar to failed installation in
   // the old launcher), try to install again to replace it.
-  bool is_placeholder_app = web_app_provider_->registrar().IsPlaceholderApp(
-      app_id.value(), web_app::WebAppManagement::Type::kKiosk);
+  bool is_placeholder_app =
+      web_app_provider_->registrar_unsafe().IsPlaceholderApp(
+          app_id.value(), web_app::WebAppManagement::Type::kKiosk);
   base::UmaHistogramBoolean(kWebAppIsPlaceholderUMA, is_placeholder_app);
   if (is_placeholder_app) {
     SYSLOG(INFO) << "Placeholder app installed. Trying to reinstall...";
diff --git a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher_unittest.cc b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher_unittest.cc
index a73706d..565afc02 100644
--- a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher_unittest.cc
+++ b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_service_launcher_unittest.cc
@@ -120,7 +120,7 @@
                   /*manifest_id=*/absl::nullopt, start_url);
               // Uninstall placeholder if reinstall_placeholder is set to true.
               auto placeholder_id =
-                  web_app_provider()->registrar().LookupPlaceholderAppId(
+                  web_app_provider()->registrar_unsafe().LookupPlaceholderAppId(
                       install_url, web_app::WebAppManagement::Type::kKiosk);
               if (placeholder_id.has_value()) {
                 if (install_options.reinstall_placeholder) {
@@ -234,11 +234,11 @@
   }
 
   web_app::WebAppRegistrar& app_registrar() {
-    return web_app_provider()->registrar();
+    return web_app_provider()->registrar_unsafe();
   }
 
   web_app::WebAppSyncBridge& sync_bridge() {
-    return web_app_provider()->sync_bridge();
+    return web_app_provider()->sync_bridge_unsafe();
   }
 
   web_app::WebAppUiManager& ui_manager() {
@@ -358,7 +358,7 @@
 
   set_install_placeholder(true);
   SetupAppData(/*installed=*/true);
-  EXPECT_TRUE(web_app_provider()->registrar().LookupPlaceholderAppId(
+  EXPECT_TRUE(web_app_provider()->registrar_unsafe().LookupPlaceholderAppId(
       GURL(kAppInstallUrl), web_app::WebAppManagement::Type::kKiosk));
 
   set_install_placeholder(false);
@@ -374,7 +374,7 @@
 
   EXPECT_EQ(app_data()->status(), WebKioskAppData::Status::kInstalled);
   EXPECT_EQ(app_data()->launch_url(), kAppLaunchUrl);
-  EXPECT_FALSE(web_app_provider()->registrar().LookupPlaceholderAppId(
+  EXPECT_FALSE(web_app_provider()->registrar_unsafe().LookupPlaceholderAppId(
       GURL(kAppInstallUrl), web_app::WebAppManagement::Type::kKiosk));
 
   // App isn't always ready by the time it's being launched. Therefore we check
diff --git a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer.cc b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer.cc
index d1d8b61..3ce94ddff 100644
--- a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer.cc
+++ b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer.cc
@@ -49,7 +49,7 @@
     return;
   }
 
-  if (web_app_provider_->registrar().IsPlaceholderApp(
+  if (web_app_provider_->registrar_unsafe().IsPlaceholderApp(
           update.AppId(), web_app::WebAppManagement::Type::kKiosk)) {
     SYSLOG(INFO) << "Ignoring web app update of placeholder app";
     return;
diff --git a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer_unittest.cc b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer_unittest.cc
index d3fe9bc..711b261 100644
--- a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer_unittest.cc
+++ b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_update_observer_unittest.cc
@@ -173,7 +173,7 @@
   }
 
   web_app::WebAppSyncBridge& sync_bridge() {
-    return web_app_provider()->sync_bridge();
+    return web_app_provider()->sync_bridge_unsafe();
   }
 
   const WebKioskAppData* app_data() {
diff --git a/chrome/browser/ash/camera_presence_notifier.h b/chrome/browser/ash/camera_presence_notifier.h
index 751c408..db7ed0c 100644
--- a/chrome/browser/ash/camera_presence_notifier.h
+++ b/chrome/browser/ash/camera_presence_notifier.h
@@ -7,6 +7,7 @@
 
 #include "base/callback.h"
 #include "base/sequence_checker.h"
+#include "base/supports_user_data.h"
 #include "base/timer/timer.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
@@ -15,7 +16,7 @@
 
 // Monitors Camera sources. Establishes connection to source on creation. Fires
 // callbacks on state changes after Start() is called until Stop().
-class CameraPresenceNotifier {
+class CameraPresenceNotifier : public base::SupportsUserData::Data {
  public:
   // |callback| for notification of camera count changes. Only one
   // client may monitor per instance.
@@ -30,7 +31,7 @@
   CameraPresenceNotifier(const CameraPresenceNotifier&) = delete;
   CameraPresenceNotifier& operator=(const CameraPresenceNotifier&) = delete;
 
-  ~CameraPresenceNotifier();
+  ~CameraPresenceNotifier() override;
 
   // Start polling for camera presence changes. A callback always fires after
   // Start() is called since the first result is always a change.
diff --git a/chrome/browser/ash/drive/drive_integration_service.cc b/chrome/browser/ash/drive/drive_integration_service.cc
index 8f11339..a51d2d63 100644
--- a/chrome/browser/ash/drive/drive_integration_service.cc
+++ b/chrome/browser/ash/drive/drive_integration_service.cc
@@ -450,9 +450,17 @@
     }
 
     VLOG(1) << "Updating the bulk pinning state";
-    if (integration_service_->GetDriveFsPinManager()) {
-      integration_service_->GetDriveFsPinManager()->SetBulkPinningEnabled(
-          pref_service_->GetBoolean(prefs::kDriveFsBulkPinningEnabled));
+    auto* pin_manager = integration_service_->GetDriveFsPinManager();
+    if (!pin_manager) {
+      return;
+    }
+    bool enabled = pref_service_->GetBoolean(prefs::kDriveFsBulkPinningEnabled);
+    integration_service_->GetDriveFsPinManager()->SetBulkPinningEnabled(
+        enabled);
+    if (enabled) {
+      pin_manager->Start(base::DoNothing());
+    } else {
+      pin_manager->Stop();
     }
   }
 
@@ -956,6 +964,12 @@
     }
   }
   drivefs_holder_->drivefs_host()->Unmount();
+
+  if (ash::features::IsDriveFsBulkPinningEnabled() && pin_manager_) {
+    pin_manager_->Stop();
+    drivefs_holder_->drivefs_host()->RemoveObserver(pin_manager_.get());
+    pin_manager_.reset();
+  }
 }
 
 void DriveIntegrationService::MaybeRemountFileSystem(
@@ -1034,16 +1048,12 @@
     pin_manager_ = std::make_unique<drivefs::pinning::DriveFsPinManager>(
         profile_->GetPrefs()->GetBoolean(prefs::kDriveFsBulkPinningEnabled),
         profile_->GetPath(), GetDriveFsInterface());
+    drivefs_holder_->drivefs_host()->AddObserver(pin_manager_.get());
   }
 }
 
 void DriveIntegrationService::OnUnmounted(
     absl::optional<base::TimeDelta> remount_delay) {
-  if (ash::features::IsDriveFsBulkPinningEnabled() && pin_manager_) {
-    pin_manager_->Stop();
-    drivefs_holder_->drivefs_host()->RemoveObserver(pin_manager_.get());
-    pin_manager_.reset();
-  }
   UmaEmitUnmountOutcome(remount_delay ? DriveMountStatus::kTemporaryUnavailable
                                       : DriveMountStatus::kUnknownFailure);
   MaybeRemountFileSystem(remount_delay, false);
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 9dd85d6..adc3a64 100644
--- a/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc
+++ b/chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.h"
 
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "base/debug/crash_logging.h"
 #include "chrome/browser/ash/extensions/speech/speech_recognition_private_manager.h"
 #include "chrome/browser/profiles/profile.h"
@@ -107,7 +108,8 @@
   // Choose which type of speech recognition, either on-device or network.
   Profile* profile = Profile::FromBrowserContext(context_);
   if (SpeechRecognitionRecognizerClientImpl::
-          IsOnDeviceSpeechRecognizerAvailable(locale_)) {
+          GetOnDeviceSpeechRecognitionAvailability(locale_) ==
+      ash::SpeechRecognitionAvailability::kSodaAvailable) {
     type_ = speech::SpeechRecognitionType::kOnDevice;
     speech_recognizer_ =
         std::make_unique<SpeechRecognitionRecognizerClientImpl>(
diff --git a/chrome/browser/ash/input_method/autocorrect_enums.h b/chrome/browser/ash/input_method/autocorrect_enums.h
index 0efe041..5431c626 100644
--- a/chrome/browser/ash/input_method/autocorrect_enums.h
+++ b/chrome/browser/ash/input_method/autocorrect_enums.h
@@ -59,6 +59,28 @@
   kMaxValue = kFastExitField,
 };
 
+// Must match with IMEAutocorrectRejectionBreakdown in enums.xml
+//
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class AutocorrectRejectionBreakdown {
+  kSuggestionRejected = 0,
+  kRejectionOther = 1,
+  kUndoWithoutKeyboard = 2,
+  kUndoWithKeyboard = 3,
+  kUndoCtrlZ = 4,
+  kRejectedBackspace = 5,
+  kRejectedCtrlBackspace = 6,
+  kRejectedTypingFull = 7,
+  kRejectedTypingPartial = 8,
+  kRejectedTypingFullWithExternal = 9,
+  kRejectedTypingPartialWithExternal = 10,
+  kRemovedLetters = 11,
+  kRejectedTypingNoSelection = 12,
+  kRejectedSelectedInvalidRange = 13,
+  kMaxValue = kRejectedSelectedInvalidRange,
+};
+
 }  // namespace input_method
 }  // namespace ash
 
diff --git a/chrome/browser/ash/input_method/autocorrect_manager.cc b/chrome/browser/ash/input_method/autocorrect_manager.cc
index 6ac4959f..92eefb5a 100644
--- a/chrome/browser/ash/input_method/autocorrect_manager.cc
+++ b/chrome/browser/ash/input_method/autocorrect_manager.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ash/input_method/autocorrect_prefs.h"
 #include "chrome/browser/ash/input_method/ime_rules_config.h"
 #include "chrome/browser/ash/input_method/suggestion_enums.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
@@ -240,6 +241,39 @@
   }
 }
 
+AutocorrectRejectionBreakdown LogControlInteractions(
+    const ui::DomCode& last_key_press,
+    const std::string& histogram_name) {
+  switch (last_key_press) {
+    case ui::DomCode::BACKSPACE:
+    case ui::DomCode::DEL:
+      return AutocorrectRejectionBreakdown::kRejectedCtrlBackspace;
+    case ui::DomCode::US_Z:
+      return AutocorrectRejectionBreakdown::kUndoCtrlZ;
+    default:
+      return AutocorrectRejectionBreakdown::kRejectionOther;
+  }
+}
+
+AutocorrectRejectionBreakdown LogSelectionEditInteractions(
+    const gfx::Range& last_autocorrect_range,
+    const gfx::Range& last_selection_range,
+    const std::string& histogram_name) {
+  if (last_autocorrect_range.IsBoundedBy(last_selection_range)) {
+    if (last_selection_range.IsBoundedBy(last_autocorrect_range)) {
+      return AutocorrectRejectionBreakdown::kRejectedTypingFull;
+    }
+    return AutocorrectRejectionBreakdown::kRejectedTypingFullWithExternal;
+  }
+  if (last_selection_range.IsBoundedBy(last_autocorrect_range)) {
+    return AutocorrectRejectionBreakdown::kRejectedTypingPartial;
+  }
+  if (last_selection_range.Intersects(last_autocorrect_range)) {
+    return AutocorrectRejectionBreakdown::kRejectedTypingPartialWithExternal;
+  }
+  return AutocorrectRejectionBreakdown::kRejectedSelectedInvalidRange;
+}
+
 // Returns the Levenshtein distance between |str1| and |str2|.
 // Which is the minimum number of single-character edits (i.e. insertions,
 // deletions or substitutions) required to change one word into the other.
@@ -458,6 +492,7 @@
 
     LogAutocorrectAppCompatibilityUkm(
         action, latency, pending_autocorrect_->virtual_keyboard_visible);
+    LogRejectionInteractions(action);
   }
 
   if (pending_autocorrect_.has_value() &&
@@ -483,6 +518,68 @@
   }
 }
 
+void AutocorrectManager::LogRejectionInteractions(
+    const AutocorrectActions action) {
+  if ((action != AutocorrectActions::kUserActionClearedUnderline &&
+       action != AutocorrectActions::kReverted) ||
+      !pending_autocorrect_.has_value()) {
+    return;
+  }
+  const ui::DomCode& last_key_press =
+      pending_autocorrect_->last_key_event.has_value()
+          ? pending_autocorrect_->last_key_event->code()
+          : ui::DomCode::NONE;
+  const std::string histogram_name =
+      pending_autocorrect_->virtual_keyboard_visible
+          ? "InputMethod.Assistive.AutocorrectV2.Rejection.VK"
+          : "InputMethod.Assistive.AutocorrectV2.Rejection.PK";
+  base::UmaHistogramEnumeration(
+      histogram_name, AutocorrectRejectionBreakdown::kSuggestionRejected);
+
+  if (action == AutocorrectActions::kReverted) {
+    if (last_key_press == ui::DomCode::ENTER) {
+      base::UmaHistogramEnumeration(
+          histogram_name, AutocorrectRejectionBreakdown::kUndoWithKeyboard);
+      return;
+    }
+    base::UmaHistogramEnumeration(
+        histogram_name, AutocorrectRejectionBreakdown::kUndoWithoutKeyboard);
+    return;
+  }
+  if (pending_autocorrect_->last_key_event.has_value() &&
+      pending_autocorrect_->last_key_event->IsControlDown()) {
+    base::UmaHistogramEnumeration(
+        histogram_name, LogControlInteractions(last_key_press, histogram_name));
+    return;
+  }
+  if (!pending_autocorrect_->last_selection_range.is_empty()) {
+    base::UmaHistogramEnumeration(
+        histogram_name,
+        LogSelectionEditInteractions(
+            pending_autocorrect_->last_autocorrect_range,
+            pending_autocorrect_->last_selection_range, histogram_name));
+    return;
+  }
+  if (pending_autocorrect_->text_length_diff < 0) {
+    base::UmaHistogramEnumeration(
+        histogram_name, AutocorrectRejectionBreakdown::kRemovedLetters);
+    if (last_key_press == ui::DomCode::BACKSPACE ||
+        last_key_press == ui::DomCode::DEL) {
+      base::UmaHistogramEnumeration(
+          histogram_name, AutocorrectRejectionBreakdown::kRejectedBackspace);
+    }
+    return;
+  }
+  if (pending_autocorrect_->text_length_diff > 0) {
+    base::UmaHistogramEnumeration(
+        histogram_name,
+        AutocorrectRejectionBreakdown::kRejectedTypingNoSelection);
+    return;
+  }
+  base::UmaHistogramEnumeration(histogram_name,
+                                AutocorrectRejectionBreakdown::kRejectionOther);
+}
+
 void AutocorrectManager::MeasureAndLogAssistiveAutocorrectQualityBreakdown(
     AutocorrectActions action) {
   if (!pending_autocorrect_.has_value() ||
@@ -575,7 +672,13 @@
       GetPhysicalKeyboardAutocorrectPref(*pref_service, engine_id);
   if (base::FeatureList::IsEnabled(features::kAutocorrectByDefault) &&
       autocorrect_pref == AutocorrectPreference::kDefault &&
-      IsUsEnglishId(engine_id)) {
+      IsUsEnglishId(engine_id) &&
+      // This class is instantiated with NativeInputMethodEngineObserver, which
+      // must exist at all times in the system to provide typing (including
+      // login screens, guest sessions, etc). Make sure we are only recording
+      // this metric when a real user has logged into their profile.
+      ProfileHelper::IsUserProfile(profile_) && profile_->IsRegularProfile() &&
+      !profile_->IsGuestSession()) {
     SetPhysicalKeyboardAutocorrectAsEnabledByDefault(pref_service, engine_id);
   }
 }
@@ -595,10 +698,15 @@
     pending_user_pref_metric_ = absl::nullopt;
   }
 
+  if (!pending_autocorrect_.has_value() || event.type() != ui::ET_KEY_PRESSED) {
+    return false;
+  }
+  // TODO(b:253549747): call pending_autocorrect_->last_key_event.reset() if
+  // user changes the text using Mouse or Touch Screen
+  pending_autocorrect_->last_key_event = event;
+
   // OnKeyEvent is only used for interacting with the undo UI.
-  if (!pending_autocorrect_.has_value() ||
-      !pending_autocorrect_->undo_window_visible ||
-      event.type() != ui::ET_KEY_PRESSED) {
+  if (!pending_autocorrect_->undo_window_visible) {
     return false;
   }
 
@@ -644,6 +752,7 @@
     return;
   }
 
+  const gfx::Range range = input_context->GetAutocorrectRange();
   if (!pending_autocorrect_->is_validated) {
     // Validate that the surrounding text matches with pending autocorrect
     // suggestion. Because of delays in update of surrounding text and
@@ -652,8 +761,7 @@
     // implementation of autocorrect interactions such as implicit acceptance.
     pending_autocorrect_->is_validated =
         IsAutocorrectSuggestionInSurroundingText(
-            text, input_context->GetAutocorrectRange(),
-            pending_autocorrect_->suggested_text);
+            text, range, pending_autocorrect_->suggested_text);
     pending_autocorrect_->validation_tries++;
 
     if (!pending_autocorrect_->is_validated) {
@@ -667,7 +775,11 @@
     }
   }
 
-  const gfx::Range range = input_context->GetAutocorrectRange();
+  pending_autocorrect_->text_length_diff =
+      pending_autocorrect_->text_length != -1
+          ? text.length() - pending_autocorrect_->text_length
+          : 0;
+
   const uint32_t cursor_pos_unsigned
       = base::checked_cast<uint32_t>(cursor_pos);
 
@@ -712,6 +824,12 @@
     // range.
     HideUndoWindow();
   }
+
+  // Only update at the end so that the metrics can use the cursor selection
+  // just before the edit
+  pending_autocorrect_->last_autocorrect_range = range;
+  pending_autocorrect_->last_selection_range = gfx::Range(
+      std::min(cursor_pos, anchor_pos), std::max(cursor_pos, anchor_pos));
 }
 
 void AutocorrectManager::OnFocus(int context_id) {
diff --git a/chrome/browser/ash/input_method/autocorrect_manager.h b/chrome/browser/ash/input_method/autocorrect_manager.h
index 9014baf..e1406025 100644
--- a/chrome/browser/ash/input_method/autocorrect_manager.h
+++ b/chrome/browser/ash/input_method/autocorrect_manager.h
@@ -189,6 +189,7 @@
 
  private:
   void LogAssistiveAutocorrectAction(AutocorrectActions action);
+  void LogRejectionInteractions(AutocorrectActions action);
   void MeasureAndLogAssistiveAutocorrectQualityBreakdown(
       AutocorrectActions action);
 
@@ -272,6 +273,21 @@
     // Specifies if virtual keyboard was visible when suggesting the pending
     // autocorrect or not.
     bool virtual_keyboard_visible = false;
+
+    // Records the most recent keypress and if control was down for use in
+    // metrics.
+    absl::optional<ui::KeyEvent> last_key_event;
+
+    // The range of the current pending autocorrect.
+    gfx::Range last_autocorrect_range = gfx::Range();
+
+    // The range of the selected text or (cursor_pos, cursor_pos] if no text is
+    // selected.
+    gfx::Range last_selection_range = gfx::Range();
+
+    // Records the difference in length between the previous text and the
+    // current |current text| - |prev text|.
+    int text_length_diff = 0;
   };
 
   struct PendingPhysicalKeyboardUserPrefMetric {
diff --git a/chrome/browser/ash/input_method/autocorrect_manager_unittest.cc b/chrome/browser/ash/input_method/autocorrect_manager_unittest.cc
index 959cf88..5200d33 100644
--- a/chrome/browser/ash/input_method/autocorrect_manager_unittest.cc
+++ b/chrome/browser/ash/input_method/autocorrect_manager_unittest.cc
@@ -95,6 +95,10 @@
     "InputMethod.Assistive.AutocorrectV2.PkUserPreference.All";
 constexpr char kAutocorrectV2PkUserPreferenceEnglish[] =
     "InputMethod.Assistive.AutocorrectV2.PkUserPreference.English";
+constexpr char kAutocorrectV2PkRejectionHistName[] =
+    "InputMethod.Assistive.AutocorrectV2.Rejection.PK";
+constexpr char kAutocorrectV2VkRejectionHistName[] =
+    "InputMethod.Assistive.AutocorrectV2.Rejection.VK";
 
 constexpr char kUsEnglishEngineId[] = "xkb:us::eng";
 constexpr char kUsInternationalEngineId[] = "xkb:us:intl:eng";
@@ -322,6 +326,12 @@
                       key, ui::EventTimeForNow());
 }
 
+ui::KeyEvent PressKeyWithCtrl(const ui::DomCode& code) {
+  return ui::KeyEvent(ui::EventType::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, code,
+                      ui::EF_CONTROL_DOWN, ui::DomKey::NONE,
+                      ui::EventTimeForNow());
+}
+
 ui::KeyEvent KeyA() {
   return CreateKeyEvent(ui::DomKey::FromCharacter('a'), ui::DomCode::US_A);
 }
@@ -2259,6 +2269,283 @@
                                      1);
 }
 
+TEST_F(AutocorrectManagerTest, RecordRejectionForPkUndoWithKeyboard) {
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  {
+    ::testing::InSequence seq;
+
+    AssistiveWindowProperties shown_properties =
+        CreateVisibleUndoWindowProperties(u"teh", u"the");
+
+    EXPECT_CALL(mock_suggestion_handler_,
+                SetAssistiveWindowProperties(_, shown_properties, _));
+
+    ui::ime::AssistiveWindowButton button = CreateHighlightedUndoButton(u"teh");
+    EXPECT_CALL(mock_suggestion_handler_,
+                SetButtonHighlighted(_, button, true, _));
+
+    AssistiveWindowProperties hidden_properties =
+        CreateHiddenUndoWindowProperties();
+    EXPECT_CALL(mock_suggestion_handler_,
+                SetAssistiveWindowProperties(_, hidden_properties, _));
+  }
+
+  manager_.OnSurroundingTextChanged(u"the ", 1, 1);
+  manager_.OnKeyEvent(CreateKeyEvent(ui::DomKey::NONE, ui::DomCode::ARROW_UP));
+  manager_.OnKeyEvent(CreateKeyEvent(ui::DomKey::NONE, ui::DomCode::ENTER));
+
+  histogram_tester_.ExpectBucketCount(
+      kAutocorrectV2PkRejectionHistName,
+      AutocorrectRejectionBreakdown::kUndoWithKeyboard, 1);
+  histogram_tester_.ExpectBucketCount(
+      kAutocorrectV2PkRejectionHistName,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(kAutocorrectV2PkRejectionHistName, 2);
+}
+
+TEST_F(AutocorrectManagerTest, RecordRejectionForPkUndoControlZ) {
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnKeyEvent(PressKeyWithCtrl(ui::DomCode::US_Z));
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"teh ", 4, 4);
+
+  histogram_tester_.ExpectBucketCount(kAutocorrectV2PkRejectionHistName,
+                                      AutocorrectRejectionBreakdown::kUndoCtrlZ,
+                                      1);
+  histogram_tester_.ExpectBucketCount(
+      kAutocorrectV2PkRejectionHistName,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(kAutocorrectV2PkRejectionHistName, 2);
+}
+
+TEST_F(AutocorrectManagerTest, RecordRejectionForPkControlBackspace) {
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnKeyEvent(PressKeyWithCtrl(ui::DomCode::BACKSPACE));
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"", 0, 0);
+
+  histogram_tester_.ExpectBucketCount(
+      kAutocorrectV2PkRejectionHistName,
+      AutocorrectRejectionBreakdown::kRejectedCtrlBackspace, 1);
+  histogram_tester_.ExpectBucketCount(
+      kAutocorrectV2PkRejectionHistName,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(kAutocorrectV2PkRejectionHistName, 2);
+}
+
+struct RejectCase {
+  std::string test_name;
+  bool vk_visible;
+  std::string histogram_name;
+};
+
+class RejectMetric : public AutocorrectManagerTest,
+                     public testing::WithParamInterface<RejectCase> {};
+
+TEST_P(RejectMetric, RecordRejectionForMetricOther) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+
+  // Accept autocorrect implicitly.
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+  // Clear range.
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"teh ", 4, 4);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name, AutocorrectRejectionBreakdown::kRejectionOther,
+      1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForVkUndo) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.UndoAutocorrect();
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kUndoWithoutKeyboard, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForBackspace) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  if (!test_case.vk_visible) {
+    manager_.OnKeyEvent(
+        CreateKeyEvent(ui::DomKey::NONE, ui::DomCode::BACKSPACE));
+  }
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"th", 2, 2);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedBackspace,
+      test_case.vk_visible ? 0 : 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name, AutocorrectRejectionBreakdown::kRemovedLetters,
+      1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name,
+                                     test_case.vk_visible ? 2 : 3);
+}
+
+TEST_P(RejectMetric, RecordRejectionForFullSelectionTyping) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnSurroundingTextChanged(u"the ", 0, 3);
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"new ", 4, 4);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedTypingFull, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForPartialSelectionTyping) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnSurroundingTextChanged(u"the ", 0, 2);
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"newe ", 3, 3);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedTypingPartial, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForFullWithExternalSelectionTyping) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnSurroundingTextChanged(u"the ", 0, 4);
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"new ", 4, 4);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedTypingFullWithExternal, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForPartialWithExternalSelectionTyping) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  manager_.OnSurroundingTextChanged(u"the ", 2, 4);
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"thnew", 5, 5);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedTypingPartialWithExternal, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+TEST_P(RejectMetric, RecordRejectionForTypingNoSelection) {
+  const RejectCase& test_case = GetParam();
+  keyboard_client_->set_keyboard_visible_for_test(test_case.vk_visible);
+  manager_.HandleAutocorrect(gfx::Range(0, 3), u"teh", u"the");
+  manager_.OnSurroundingTextChanged(u"the ", 4, 4);
+
+  {
+    ::testing::InSequence seq;
+
+    AssistiveWindowProperties shown_properties =
+        CreateVisibleUndoWindowProperties(u"teh", u"the");
+
+    EXPECT_CALL(mock_suggestion_handler_,
+                SetAssistiveWindowProperties(_, shown_properties, _));
+
+    AssistiveWindowProperties hidden_properties =
+        CreateHiddenUndoWindowProperties();
+    EXPECT_CALL(mock_suggestion_handler_,
+                SetAssistiveWindowProperties(_, hidden_properties, _));
+  }
+
+  manager_.OnSurroundingTextChanged(u"the ", 2, 2);
+  mock_ime_input_context_handler_.SetAutocorrectRange(gfx::Range(),
+                                                      base::DoNothing());
+  manager_.OnSurroundingTextChanged(u"thee ", 3, 3);
+
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kRejectedTypingNoSelection, 1);
+  histogram_tester_.ExpectBucketCount(
+      test_case.histogram_name,
+      AutocorrectRejectionBreakdown::kSuggestionRejected, 1);
+  histogram_tester_.ExpectTotalCount(test_case.histogram_name, 2);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AutocorrectManagerTest,
+    RejectMetric,
+    testing::ValuesIn<RejectCase>({
+        {"VkEnabled",
+         /*vk_visible=*/true,
+         /*histogram_name=*/kAutocorrectV2VkRejectionHistName},
+        {"VkDisabled",
+         /*vk_visible=*/false,
+         /*histogram_name=*/kAutocorrectV2PkRejectionHistName},
+    }),
+    [](const testing::TestParamInfo<RejectCase> info) {
+      return info.param.test_name;
+    });
+
 struct PkUserPrefCase {
   std::string test_name;
   std::string engine_id;
diff --git a/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc b/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
index 178c3f2..4f38f6a 100644
--- a/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
+++ b/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
@@ -424,7 +424,7 @@
   std::string error;
   scoped_refptr<extensions::Extension> lock_profile_app =
       extensions::Extension::Create(lock_profile_app_path, app->location(),
-                                    app->manifest()->value()->GetDict(),
+                                    app->manifest()->value()->Clone(),
                                     app->creation_flags(), app->id(), &error);
 
   // While extension creation can fail in general, in this case the lock screen
@@ -567,7 +567,7 @@
   std::string error;
   scoped_refptr<extensions::Extension> lock_profile_app =
       extensions::Extension::Create(app->path(), app->location(),
-                                    app->manifest()->value()->GetDict(),
+                                    app->manifest()->value()->Clone(),
                                     app->creation_flags(), app->id(), &error);
 
   extensions::ExtensionService* extension_service =
diff --git a/chrome/browser/ash/login/error_screen_browsertest.cc b/chrome/browser/ash/login/error_screen_browsertest.cc
index 3623c629..f66688bc 100644
--- a/chrome/browser/ash/login/error_screen_browsertest.cc
+++ b/chrome/browser/ash/login/error_screen_browsertest.cc
@@ -294,7 +294,7 @@
 
     network_helper_->service_test()->AddService(
         kWifiServiceName, "wifi_guid", kWifiNetworkName, shill::kTypeWifi,
-        shill::kStateOffline, /*visible=*/true);
+        shill::kStateIdle, /*visible=*/true);
 
     MixinBasedInProcessBrowserTest::SetUpOnMainThread();
   }
diff --git a/chrome/browser/ash/login/oobe_quick_start/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
index 46c5987..946e41c 100644
--- a/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
+++ b/chrome/browser/ash/login/oobe_quick_start/BUILD.gn
@@ -9,6 +9,7 @@
 source_set("oobe_quick_start") {
   deps = [
     "connectivity",
+    "logging",
     "//base",
     "//chrome/common:channel_info",
     "//chromeos/ash/components/attestation:attestation",
@@ -39,6 +40,7 @@
     ":oobe_quick_start",
     "connectivity:test_support",
     "connectivity:unit_tests",
+    "logging:unit_tests",
     "//base",
     "//base/test:test_support",
     "//chromeos/ash/components/attestation:test_support",
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/logging/BUILD.gn
new file mode 100644
index 0000000..313d393
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/BUILD.gn
@@ -0,0 +1,26 @@
+# 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("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash)
+
+source_set("logging") {
+  sources = [
+    "logging.cc",
+    "logging.h",
+  ]
+  deps = [ "//base" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "logging_unittest.cc" ]
+
+  deps = [
+    ":logging",
+    "//base/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/OWNERS b/chrome/browser/ash/login/oobe_quick_start/logging/OWNERS
new file mode 100644
index 0000000..8221a66
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/OWNERS
@@ -0,0 +1 @@
+file://chrome/browser/ash/login/oobe_quick_start/connectivity/OWNERS
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/README.md b/chrome/browser/ash/login/oobe_quick_start/logging/README.md
new file mode 100644
index 0000000..bf1a486
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/README.md
@@ -0,0 +1,7 @@
+This directory implements several logging macros to be used with Quick Start.
+
+Use `QS_LOG(severity)` for general-purpose logging, and will emit logs to the
+standard logging system. VERBOSE messages logged in this manner can be emitted
+to the logs by using the `--quick-start-verbose-logging` command-line flag.
+
+See go/cros-quickstart-logging for more info.
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/logging.cc b/chrome/browser/ash/login/oobe_quick_start/logging/logging.cc
new file mode 100644
index 0000000..7a31e079
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/logging.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/ash/login/oobe_quick_start/logging/logging.h"
+
+#include "base/command_line.h"
+
+namespace ash::quick_start {
+
+namespace {
+
+// Passing "--quick-start-verbose-logging" on the command line will force Quick
+// Start's verbose logs to be emitted to the log file regardless of the current
+// vlog level or vmodules.
+constexpr char kQuickStartVerboseLoggingSwitch[] =
+    "quick-start-verbose-logging";
+
+}  // namespace
+
+ScopedLogMessage::ScopedLogMessage(const char* file,
+                                   int line,
+                                   logging::LogSeverity severity)
+    : file_(file), line_(line), severity_(severity) {}
+
+ScopedLogMessage::~ScopedLogMessage() {
+  if (ShouldEmitToStandardLog()) {
+    // Create a log for the standard logging system.
+    logging::LogMessage log_message(file_, line_, severity_);
+    log_message.stream() << stream_.str();
+  }
+}
+
+bool ScopedLogMessage::ShouldEmitToStandardLog() const {
+  // Logs should be emitted if any of the following is true:
+  // - The severity is INFO or greater
+  // - The Vlog Level for |file_| is at least 1
+  // - The --quick-start-verbose-logging switch is enabled
+  return severity_ > logging::LOG_VERBOSE ||
+         logging::GetVlogLevelHelper(file_, strlen(file_) + 1) > 0 ||
+         base::CommandLine::ForCurrentProcess()->HasSwitch(
+             kQuickStartVerboseLoggingSwitch);
+}
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/logging.h b/chrome/browser/ash/login/oobe_quick_start/logging/logging.h
new file mode 100644
index 0000000..d75cf60
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/logging.h
@@ -0,0 +1,41 @@
+// 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_LOGIN_OOBE_QUICK_START_LOGGING_LOGGING_H_
+#define CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_LOGGING_LOGGING_H_
+
+#include <sstream>
+
+#include "base/logging.h"
+
+namespace ash::quick_start {
+
+// Use the QS_LOG() macro for all logging related to Quick Start.
+#define QS_LOG(severity) \
+  ScopedLogMessage(__FILE__, __LINE__, logging::LOG_##severity).stream()
+
+// An intermediate object used by the QS_LOG macro, wrapping a
+// logging::LogMessage instance. When this object is destroyed, the message will
+// be logged with the standard logging system.
+class ScopedLogMessage {
+ public:
+  ScopedLogMessage(const char* file, int line, logging::LogSeverity severity);
+  ScopedLogMessage(const ScopedLogMessage&) = delete;
+  ScopedLogMessage& operator=(const ScopedLogMessage&) = delete;
+  ~ScopedLogMessage();
+
+  std::ostream& stream() { return stream_; }
+
+ private:
+  bool ShouldEmitToStandardLog() const;
+
+  const char* file_;
+  int line_;
+  logging::LogSeverity severity_;
+  std::ostringstream stream_;
+};
+
+}  // namespace ash::quick_start
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_LOGGING_LOGGING_H_
diff --git a/chrome/browser/ash/login/oobe_quick_start/logging/logging_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/logging/logging_unittest.cc
new file mode 100644
index 0000000..71ee5db
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/logging/logging_unittest.cc
@@ -0,0 +1,85 @@
+// 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 "base/command_line.h"
+#include "base/no_destructor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "chrome/browser/ash/login/oobe_quick_start/logging/logging.h"
+
+namespace ash::quick_start {
+
+namespace {
+
+const char kLog1[] = "Mahogony destined to make a sturdy table";
+const char kLog2[] = "Construction grade cedar";
+const char kLog3[] = "Pine infested by hungry beetles";
+const char kLog4[] = "Unremarkable maple";
+
+// Called for every log message added to the standard logging system. The new
+// log is saved in |standard_logs| and consumed so it does not flood stdout.
+std::vector<std::string>& GetStandardLogs() {
+  static base::NoDestructor<std::vector<std::string>> standard_logs;
+  return *standard_logs;
+}
+
+bool HandleStandardLogMessage(int severity,
+                              const char* file,
+                              int line,
+                              size_t message_start,
+                              const std::string& str) {
+  GetStandardLogs().push_back(str);
+  return true;
+}
+
+}  // namespace
+
+class QuickStartLoggingTest : public testing::Test {
+ public:
+  QuickStartLoggingTest() = default;
+
+  void SetUp() override {
+    GetStandardLogs().clear();
+
+    previous_handler_ = logging::GetLogMessageHandler();
+    logging::SetLogMessageHandler(&HandleStandardLogMessage);
+  }
+
+  void TearDown() override { logging::SetLogMessageHandler(previous_handler_); }
+
+ private:
+  logging::LogMessageHandlerFunction previous_handler_{nullptr};
+};
+
+TEST_F(QuickStartLoggingTest, NonVerboseStandardLogsCreated) {
+  QS_LOG(INFO) << kLog1;
+  QS_LOG(WARNING) << kLog2;
+  QS_LOG(ERROR) << kLog3;
+  QS_LOG(VERBOSE) << kLog4;
+
+  ASSERT_EQ(3u, GetStandardLogs().size());
+  EXPECT_NE(std::string::npos, GetStandardLogs()[0].find(kLog1));
+  EXPECT_NE(std::string::npos, GetStandardLogs()[1].find(kLog2));
+  EXPECT_NE(std::string::npos, GetStandardLogs()[2].find(kLog3));
+}
+
+TEST_F(QuickStartLoggingTest, VerboseStandardLogsCreatedWithFlagEnabled) {
+  base::CommandLine::ForCurrentProcess()->InitFromArgv(
+      {"", "--quick-start-verbose-logging"});
+
+  QS_LOG(INFO) << kLog1;
+  QS_LOG(WARNING) << kLog2;
+  QS_LOG(ERROR) << kLog3;
+  QS_LOG(VERBOSE) << kLog4;
+
+  ASSERT_EQ(4u, GetStandardLogs().size());
+  EXPECT_NE(std::string::npos, GetStandardLogs()[0].find(kLog1));
+  EXPECT_NE(std::string::npos, GetStandardLogs()[1].find(kLog2));
+  EXPECT_NE(std::string::npos, GetStandardLogs()[2].find(kLog3));
+  EXPECT_NE(std::string::npos, GetStandardLogs()[3].find(kLog4));
+}
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc b/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
index 1234b2c..b6e98f10b 100644
--- a/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
+++ b/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
@@ -496,7 +496,7 @@
   network_state_test_helper_->service_test()->AddService(
       /*service_path=*/kWifiServicePath, /*guid=*/kWifiServicePath,
       /*name=*/kWifiServicePath, /*type=*/shill::kTypeWifi,
-      /*state=*/shill::kStateOffline, /*visible=*/true);
+      /*state=*/shill::kStateIdle, /*visible=*/true);
 
   reauth_dialog_helper->WaitForNetworkDialogAndSetHandlers();
 
@@ -525,7 +525,7 @@
   network_state_test_helper_->service_test()->AddService(
       /*service_path=*/kWifiServicePath, /*guid=*/kWifiServicePath,
       /*name=*/kWifiServicePath, /*type=*/shill::kTypeWifi,
-      /*state=*/shill::kStateOffline, /*visible=*/true);
+      /*state=*/shill::kStateIdle, /*visible=*/true);
 
   reauth_dialog_helper->WaitForNetworkDialogAndSetHandlers();
 
@@ -561,7 +561,7 @@
   network_state_test_helper_->service_test()->AddService(
       /*service_path=*/kWifiServicePath, /*guid=*/kWifiServicePath,
       /*name=*/kWifiServicePath, /*type=*/shill::kTypeWifi,
-      /*state=*/shill::kStateOffline, /*visible=*/true);
+      /*state=*/shill::kStateIdle, /*visible=*/true);
 
   reauth_dialog_helper->WaitForNetworkDialogAndSetHandlers();
 
@@ -612,7 +612,7 @@
   network_test_helper.service_test()->AddService(
       /*service_path=*/kWifiServicePath, /*guid=*/kWifiServicePath,
       /*name=*/kWifiServicePath, /*type=*/shill::kTypeWifi,
-      /*state=*/shill::kStateOffline, /*visible=*/true);
+      /*state=*/shill::kStateIdle, /*visible=*/true);
 
   reauth_dialog_helper->WaitForNetworkDialogAndSetHandlers();
 
diff --git a/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc b/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
index 1b4ea87f..c5bac742 100644
--- a/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
+++ b/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
@@ -214,7 +214,7 @@
   network_state_test_helper_->service_test()->AddService(
       /*service_path=*/kWifiServicePath, /*guid=*/kWifiServicePath,
       /*name=*/kWifiServicePath, /*type=*/shill::kTypeWifi,
-      /*state=*/shill::kStateOffline, /*visible=*/true);
+      /*state=*/shill::kStateIdle, /*visible=*/true);
   base::RunLoop().RunUntilIdle();
 
   // Wait for ErrorScreen to appear.
diff --git a/chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h b/chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h
index a29cc1c..dab55da 100644
--- a/chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h
+++ b/chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h
@@ -9,6 +9,7 @@
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 class ChromeOSSystemProfileProviderTest;
+class ChromeMetricsServiceClientTestIgnoredForAppMetrics;
 class ChromeMetricsServiceClientTest;
 class Profile;
 
@@ -32,6 +33,7 @@
  private:
   friend struct base::DefaultSingletonTraits<MultiDeviceSetupClientFactory>;
   friend class ::ChromeOSSystemProfileProviderTest;
+  friend class ::ChromeMetricsServiceClientTestIgnoredForAppMetrics;
   friend class ::ChromeMetricsServiceClientTest;
 
   MultiDeviceSetupClientFactory();
diff --git a/chrome/browser/ash/net/network_diagnostics/captive_portal_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/captive_portal_routine_unittest.cc
index 61ac7f6..7467a9e4 100644
--- a/chrome/browser/ash/net/network_diagnostics/captive_portal_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/captive_portal_routine_unittest.cc
@@ -64,7 +64,7 @@
 // Test whether no active networks is reported correctly.
 TEST_F(CaptivePortalRoutineTest, TestNoActiveNetworks) {
   base::RunLoop run_loop;
-  SetUpWiFi(shill::kStateOffline);
+  SetUpWiFi(shill::kStateIdle);
   std::vector<mojom::CaptivePortalProblem> expected_problems = {
       mojom::CaptivePortalProblem::kNoActiveNetworks};
   captive_portal_routine()->RunRoutine(
diff --git a/chrome/browser/ash/net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc
index 0a07606..f459948 100644
--- a/chrome/browser/ash/net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc
@@ -141,7 +141,7 @@
 
 TEST_F(GatewayCanBePingedRoutineTest, TestNoActiveNetworks) {
   SetUpRoutine(kFakeValidICMPOutput);
-  SetUpWiFi(shill::kStateOffline);
+  SetUpWiFi(shill::kStateIdle);
   std::vector<mojom::GatewayCanBePingedProblem> expected_problems = {
       mojom::GatewayCanBePingedProblem::kUnreachableGateway};
   base::RunLoop run_loop;
diff --git a/chrome/browser/ash/net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc
index 66652c74..8c26db5 100644
--- a/chrome/browser/ash/net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc
@@ -107,7 +107,7 @@
 }
 
 TEST_F(HasSecureWiFiConnectionRoutineTest, TestWiFiNotConnected) {
-  SetUpWiFi(shill::kStateOffline, kSecureSecurity);
+  SetUpWiFi(shill::kStateIdle, kSecureSecurity);
   std::vector<mojom::HasSecureWiFiConnectionProblem> expected_problems = {};
   has_secure_wifi_connection_routine()->RunRoutine(base::BindOnce(
       &HasSecureWiFiConnectionRoutineTest::CompareResult, weak_ptr(),
diff --git a/chrome/browser/ash/net/network_diagnostics/lan_connectivity_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/lan_connectivity_routine_unittest.cc
index 2de900ae..b27be47 100644
--- a/chrome/browser/ash/net/network_diagnostics/lan_connectivity_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/lan_connectivity_routine_unittest.cc
@@ -95,7 +95,7 @@
 }
 
 TEST_F(LanConnectivityRoutineTest, TestDisconnectedLan) {
-  SetUpWiFi(shill::kStateOffline);
+  SetUpWiFi(shill::kStateIdle);
   lan_connectivity_routine()->RunRoutine(
       base::BindOnce(&LanConnectivityRoutineTest::CompareVerdict, weak_ptr(),
                      mojom::RoutineVerdict::kProblem));
diff --git a/chrome/browser/ash/net/network_diagnostics/signal_strength_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/signal_strength_routine_unittest.cc
index a23fef6..e93a429d 100644
--- a/chrome/browser/ash/net/network_diagnostics/signal_strength_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/signal_strength_routine_unittest.cc
@@ -104,7 +104,7 @@
 }
 
 TEST_F(SignalStrengthRoutineTest, TestNoWiFiConnection) {
-  SetUpWiFi(shill::kStateOffline, kGoodWiFiSignal);
+  SetUpWiFi(shill::kStateIdle, kGoodWiFiSignal);
   std::vector<mojom::SignalStrengthProblem> expected_problems = {};
   signal_strength_routine()->RunRoutine(
       base::BindOnce(&SignalStrengthRoutineTest::CompareResult, weak_ptr(),
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
index 2517b93f..022d893 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
@@ -523,6 +523,10 @@
       NOTIMPLEMENTED();
       break;
     }
+    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kEmmcLifetime: {
+      diagnostics_service->RunEmmcLifetimeRoutine(std::move(response_callback));
+      break;
+    }
   }
 }
 
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
index e1e7e24f..acec314 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
@@ -1573,4 +1573,28 @@
              })));
 }
 
+// Note that the eMMC lifetime routine has no parameters, so we only need to
+// test that it can be run successfully.
+TEST_F(DeviceCommandRunRoutineJobTest, RunEmmcLifetimeRoutineSuccess) {
+  auto run_routine_response =
+      ash::cros_healthd::mojom::RunRoutineResponse::New(kId, kStatus);
+  ash::cros_healthd::FakeCrosHealthd::Get()->SetRunRoutineResponseForTesting(
+      run_routine_response);
+  base::Value params_dict(base::Value::Type::DICTIONARY);
+  EXPECT_TRUE(RunJob(
+      ash::cros_healthd::mojom::DiagnosticRoutineEnum::kEmmcLifetime,
+      std::move(params_dict),
+      base::BindLambdaForTesting([](RemoteCommandJob* job) {
+        EXPECT_EQ(
+            ash::cros_healthd::FakeCrosHealthd::Get()
+                ->GetLastRunRoutine()
+                .value(),
+            ash::cros_healthd::mojom::DiagnosticRoutineEnum::kEmmcLifetime);
+        EXPECT_EQ(job->status(), RemoteCommandJob::SUCCEEDED);
+        std::unique_ptr<std::string> payload = job->GetResultPayload();
+        EXPECT_TRUE(payload);
+        EXPECT_EQ(CreateSuccessPayload(kId, kStatus), *payload);
+      })));
+}
+
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/reporting/arc_app_install_event_log_collector_unittest.cc b/chrome/browser/ash/policy/reporting/arc_app_install_event_log_collector_unittest.cc
index 1e4f2e9..bac5f0c 100644
--- a/chrome/browser/ash/policy/reporting/arc_app_install_event_log_collector_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/arc_app_install_event_log_collector_unittest.cc
@@ -152,10 +152,10 @@
         std::make_unique<ash::NetworkHandlerTestHelper>();
     network_handler_test_helper_->service_test()->AddService(
         kEthernetServicePath, "eth1_guid", "eth1", shill::kTypeEthernet,
-        shill::kStateOffline, true /* visible */);
+        shill::kStateIdle, true /* visible */);
     network_handler_test_helper_->service_test()->AddService(
         kWifiServicePath, "wifi1_guid", "wifi1", shill::kTypeEthernet,
-        shill::kStateOffline, true /* visible */);
+        shill::kStateIdle, true /* visible */);
     base::RunLoop().RunUntilIdle();
   }
 
@@ -339,10 +339,10 @@
   SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOnline);
   EXPECT_EQ(1, delegate()->add_for_all_count());
 
-  SetNetworkState(collector.get(), kEthernetServicePath, shill::kStateOffline);
+  SetNetworkState(collector.get(), kEthernetServicePath, shill::kStateIdle);
   EXPECT_EQ(1, delegate()->add_for_all_count());
 
-  SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOffline);
+  SetNetworkState(collector.get(), kWifiServicePath, shill::kStateIdle);
   EXPECT_EQ(2, delegate()->add_for_all_count());
   EXPECT_EQ(em::AppInstallReportLogEvent::CONNECTIVITY_CHANGE,
             delegate()->last_event().event_type());
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector.cc b/chrome/browser/ash/policy/status_collector/device_status_collector.cc
index e002810..43f2781c 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector.cc
@@ -2390,7 +2390,6 @@
       {shill::kStateNoConnectivity, em::NetworkState::PORTAL},
       {shill::kStateRedirectFound, em::NetworkState::PORTAL},
       {shill::kStatePortalSuspected, em::NetworkState::PORTAL},
-      {shill::kStateOffline, em::NetworkState::OFFLINE},
       {shill::kStateOnline, em::NetworkState::ONLINE},
       {shill::kStateDisconnect, em::NetworkState::DISCONNECT},
       {shill::kStateFailure, em::NetworkState::FAILURE},
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
index 58f6946..ee06dd5b 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
@@ -3834,8 +3834,6 @@
 // by convention shill will not report a signal strength of 0 for a visible
 // network, so we use 1 below.
 static const FakeNetworkState kFakeNetworks[] = {
-    {"offline", "/device/wifi", shill::kTypeWifi, 35, -72, shill::kStateOffline,
-     em::NetworkState::OFFLINE, "", "", true},
     {"ethernet", "/device/ethernet", shill::kTypeEthernet, 0, 0,
      shill::kStateOnline, em::NetworkState::ONLINE, "192.168.0.1", "8.8.8.8",
      true},
@@ -3864,8 +3862,8 @@
                                                       shill::kTypeWifi,
                                                       35,
                                                       -85,
-                                                      shill::kStateOffline,
-                                                      em::NetworkState::OFFLINE,
+                                                      shill::kStateIdle,
+                                                      em::NetworkState::IDLE,
                                                       "",
                                                       ""};
 
diff --git a/chrome/browser/ash/privacy_hub/privacy_hub_util.cc b/chrome/browser/ash/privacy_hub/privacy_hub_util.cc
index 143d2cff..8b8ece7 100644
--- a/chrome/browser/ash/privacy_hub/privacy_hub_util.cc
+++ b/chrome/browser/ash/privacy_hub/privacy_hub_util.cc
@@ -8,6 +8,8 @@
 #include "ash/system/privacy_hub/camera_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/microphone_privacy_switch_controller.h"
 #include "ash/system/privacy_hub/privacy_hub_controller.h"
+#include "base/supports_user_data.h"
+#include "chrome/browser/ash/camera_presence_notifier.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "media/capture/video/chromeos/mojom/cros_camera_service.mojom.h"
 
@@ -49,4 +51,25 @@
   return CrasAudioHandler::Get()->HasActiveInputDeviceForSimpleUsage();
 }
 
+void SetUpCameraCountObserver() {
+  DCHECK(Shell::Get());
+  if (PrivacyHubController* privacy_hub_controller =
+          Shell::Get()->privacy_hub_controller()) {
+    CameraPrivacySwitchController& camera_controller =
+        privacy_hub_controller->camera_controller();
+    base::RepeatingCallback<void(int)> update_camera_count_in_privacy_hub =
+        base::BindRepeating(
+            [](CameraPrivacySwitchController* controller, int camera_count) {
+              controller->OnCameraCountChanged(camera_count);
+            },
+            &camera_controller);
+    auto notifier = std::make_unique<CameraPresenceNotifier>(
+        std::move(update_camera_count_in_privacy_hub));
+    notifier->Start();
+
+    static const char kUserDataKey = '\0';
+    camera_controller.SetUserData(&kUserDataKey, std::move(notifier));
+  }
+}
+
 }  // namespace ash::privacy_hub_util
diff --git a/chrome/browser/ash/privacy_hub/privacy_hub_util.h b/chrome/browser/ash/privacy_hub/privacy_hub_util.h
index e8b7e8c..1d0c725 100644
--- a/chrome/browser/ash/privacy_hub/privacy_hub_util.h
+++ b/chrome/browser/ash/privacy_hub/privacy_hub_util.h
@@ -23,6 +23,9 @@
 // Checks whether there are active input devices for simple usage.
 bool HasActiveInputDeviceForSimpleUsage();
 
+// Needs to be called for the Privacy Hub to be aware of the camera count.
+void SetUpCameraCountObserver();
+
 }  // namespace privacy_hub_util
 }  // namespace ash
 
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
index 27d0a22c..9a91382 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
@@ -193,7 +193,7 @@
           data.url, web_app::WebAppManagement::Type::kSystem);
       const web_app::AppId app_id = web_app->app_id();
       {
-        web_app::ScopedRegistryUpdate update(&provider().sync_bridge());
+        web_app::ScopedRegistryUpdate update(&provider().sync_bridge_unsafe());
         update->CreateApp(std::move(web_app));
       }
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 656327e..66690a4 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -817,25 +817,22 @@
         ->RemoveEmbargoAndResetCounts(filter);
   }
 
-  // Different types of DIPS events are cleared for DATA_TYPE_HISTORY,
-  // DATA_TYPE_COOKIES and DATA_TYPE_SITE_USAGE_DATA.
+  // Different types of DIPS events are cleared for DATA_TYPE_HISTORY and
+  // DATA_TYPE_COOKIES.
   DIPSEventRemovalType dips_mask = DIPSEventRemovalType::kNone;
   if ((remove_mask & content::BrowsingDataRemover::DATA_TYPE_COOKIES) &&
-      (origin_type_mask &
-       content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB)) {
+      !filter_builder->IsCrossSiteClearSiteDataForCookies()) {
     dips_mask |= DIPSEventRemovalType::kStorage;
   }
-  if ((remove_mask & constants::DATA_TYPE_SITE_USAGE_DATA) ||
-      ((remove_mask & constants::DATA_TYPE_HISTORY) && may_delete_history)) {
+  if (remove_mask & constants::DATA_TYPE_HISTORY) {
     dips_mask |= DIPSEventRemovalType::kHistory;
   }
 
   if (dips_mask != DIPSEventRemovalType::kNone) {
     auto* dips_service = DIPSServiceFactory::GetForBrowserContext(profile_);
     if (dips_service) {
-      // TODO(crbug.com/1342228): Currently the filter is not supported and
-      // calls with a non-null filter are ignored.
-      dips_service->RemoveEvents(delete_begin_, delete_end_, nullable_filter,
+      dips_service->RemoveEvents(delete_begin_, delete_end_,
+                                 filter_builder->BuildNetworkServiceFilter(),
                                  dips_mask);
     }
   }
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 2922422..a2e57e1 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -1547,12 +1547,14 @@
   auto web_app_id = web_app::test::InstallDummyWebApp(GetProfile(), "Web App",
                                                       GURL("http://some.url"));
   auto last_launch_time = base::Time() + base::Seconds(10);
-  provider->sync_bridge().SetAppLastLaunchTime(web_app_id, last_launch_time);
+  provider->sync_bridge_unsafe().SetAppLastLaunchTime(web_app_id,
+                                                      last_launch_time);
   EXPECT_EQ(
       provider->registrar_unsafe().GetAppById(web_app_id)->last_launch_time(),
       last_launch_time);
   auto last_badging_time = base::Time() + base::Seconds(20);
-  provider->sync_bridge().SetAppLastBadgingTime(web_app_id, last_badging_time);
+  provider->sync_bridge_unsafe().SetAppLastBadgingTime(web_app_id,
+                                                       last_badging_time);
   EXPECT_EQ(
       provider->registrar_unsafe().GetAppById(web_app_id)->last_badging_time(),
       last_badging_time);
diff --git a/chrome/browser/chrome_browser_main_mac.mm b/chrome/browser/chrome_browser_main_mac.mm
index d12abb3..77aaa6d 100644
--- a/chrome/browser/chrome_browser_main_mac.mm
+++ b/chrome/browser/chrome_browser_main_mac.mm
@@ -23,7 +23,6 @@
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/buildflags.h"
 #import "chrome/browser/chrome_browser_application_mac.h"
-#include "chrome/browser/chrome_for_testing/buildflags.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/mac/install_from_dmg.h"
 #import "chrome/browser/mac/keystone_glue.h"
@@ -80,7 +79,7 @@
   // point (needed to load the nib).
   CHECK(ui::ResourceBundle::HasSharedInstance());
 
-#if !BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING)
+#if BUILDFLAG(ENABLE_UPDATER)
   if (base::FeatureList::IsEnabled(features::kUseChromiumUpdater)) {
     EnsureUpdater(base::DoNothing(), base::DoNothing());
   } else {
@@ -110,7 +109,7 @@
       exit(0);
     }
   }
-#endif  // !BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING)
+#endif  // BUILDFLAG(ENABLE_UPDATER)
 
   // Create the app delegate. This object is intentionally leaked as a global
   // singleton. It is accessed through -[NSApp delegate].
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 83c0728..44ee095 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -347,6 +347,7 @@
 #include "base/win/win_util.h"
 #include "base/win/windows_version.h"
 #include "chrome/browser/chrome_browser_main_win.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.h"
 #include "chrome/browser/lifetime/application_lifetime_desktop.h"
 #include "chrome/install_static/install_util.h"
 #include "chrome/services/util_win/public/mojom/util_win.mojom.h"
@@ -5058,6 +5059,17 @@
                          MaybeCreateNavigationThrottle(handle),
                      &throttles);
   }
+
+#if BUILDFLAG(IS_WIN)
+  // Don't perform platform authentication in incognito and guest profiles.
+  if (profile && !profile->IsOffTheRecord()) {
+    MaybeAddThrottle(
+        enterprise_auth::PlatformAuthNavigationThrottle::MaybeCreateThrottleFor(
+            handle),
+        &throttles);
+  }
+#endif  // BUILDFLAG(IS_WIN)
+
   return throttles;
 }
 
diff --git a/chrome/browser/dips/dips_database.cc b/chrome/browser/dips/dips_database.cc
index 049f0d4f..0f3169c 100644
--- a/chrome/browser/dips/dips_database.cc
+++ b/chrome/browser/dips/dips_database.cc
@@ -13,6 +13,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "chrome/browser/dips/dips_utils.h"
@@ -434,6 +435,26 @@
   return true;
 }
 
+bool DIPSDatabase::RemoveEventsBySite(bool preserve,
+                                      const std::vector<std::string>& sites,
+                                      const DIPSEventRemovalType type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!CheckDBInit())
+    return false;
+
+  sql::Transaction transaction(db_.get());
+  if (!transaction.Begin())
+    return false;
+
+  GarbageCollect();
+
+  if (!ClearTimestampsBySite(preserve, sites, type))
+    return false;
+
+  transaction.Commit();
+  return true;
+}
+
 bool DIPSDatabase::ClearTimestamps(const base::Time& delete_begin,
                                    const base::Time& delete_end,
                                    const DIPSEventRemovalType type) {
@@ -573,22 +594,7 @@
       return false;
   }
 
-  static constexpr char kCleanUpSql[] =  // clang-format off
-      "DELETE FROM bounces "
-          "WHERE first_site_storage_time=0 AND "
-                "last_site_storage_time=0 AND "
-                "first_user_interaction_time=0 AND "
-                "last_user_interaction_time=0 AND "
-                "first_stateful_bounce_time=0 AND "
-                "last_stateful_bounce_time=0 AND "
-                "first_stateless_bounce_time=0 AND "
-                "last_stateless_bounce_time=0";
-  // clang-format on
-  DCHECK(db_->IsSQLValid(kCleanUpSql));
-
-  sql::Statement s_clean(db_->GetCachedStatement(SQL_FROM_HERE, kCleanUpSql));
-
-  return s_clean.Run();
+  return RemoveEmptyRows();
 }
 
 bool DIPSDatabase::AdjustFirstTimestamps(const base::Time& delete_begin,
@@ -743,6 +749,60 @@
   return true;
 }
 
+bool DIPSDatabase::ClearTimestampsBySite(bool preserve,
+                                         const std::vector<std::string>& sites,
+                                         const DIPSEventRemovalType type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (sites.empty())
+    return false;
+
+  std::string placeholders =
+      base::JoinString(std::vector<std::string>(sites.size(), "?"), ",");
+
+  if ((type & DIPSEventRemovalType::kStorage) ==
+      DIPSEventRemovalType::kStorage) {
+    sql::Statement s_clear_storage(db_->GetUniqueStatement(  // clang-format off
+        base::StrCat({"UPDATE bounces SET first_site_storage_time=0,"
+                                         "last_site_storage_time=0,"
+                                         "first_stateful_bounce_time=0,"
+                                         "last_stateful_bounce_time=0 "
+                          "WHERE site ", (preserve ? "NOT " : ""),
+                              "IN(", placeholders, ")" })  // clang-format on
+            .c_str()));
+
+    for (size_t i = 0; i < sites.size(); i++) {
+      s_clear_storage.BindString(i, sites[i]);
+    }
+
+    if (!s_clear_storage.Run())
+      return false;
+  }
+
+  return RemoveEmptyRows();
+}
+
+bool DIPSDatabase::RemoveEmptyRows() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  static constexpr char kCleanUpSql[] =  // clang-format off
+      "DELETE FROM bounces "
+          "WHERE first_site_storage_time=0 AND "
+                "last_site_storage_time=0 AND "
+                "first_user_interaction_time=0 AND "
+                "last_user_interaction_time=0 AND "
+                "first_stateful_bounce_time=0 AND "
+                "last_stateful_bounce_time=0 AND "
+                "first_stateless_bounce_time=0 AND "
+                "last_stateless_bounce_time=0";
+  // clang-format on
+  DCHECK(db_->IsSQLValid(kCleanUpSql));
+
+  sql::Statement s_clean(db_->GetCachedStatement(SQL_FROM_HERE, kCleanUpSql));
+
+  return s_clean.Run();
+}
+
 size_t DIPSDatabase::GetEntryCount() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
diff --git a/chrome/browser/dips/dips_database.h b/chrome/browser/dips/dips_database.h
index 6d2b714..8e404299 100644
--- a/chrome/browser/dips/dips_database.h
+++ b/chrome/browser/dips/dips_database.h
@@ -86,6 +86,12 @@
                           const base::Time& delete_end,
                           const DIPSEventRemovalType type);
 
+  // Because |sites| is taken from a ClearDataFilter, this only removes
+  // storage and stateful bounce timestamps at the moment.
+  bool RemoveEventsBySite(bool preserve,
+                          const std::vector<std::string>& sites,
+                          const DIPSEventRemovalType type);
+
   // Returns the number of entries present in the database.
   size_t GetEntryCount() const;
 
@@ -135,6 +141,11 @@
                             const base::Time& delete_end,
                             const DIPSEventRemovalType type);
 
+  bool ClearTimestampsBySite(bool preserve,
+                             const std::vector<std::string>& sites,
+                             const DIPSEventRemovalType type);
+  bool RemoveEmptyRows();
+
   void ComputeDatabaseMetrics() const;
 
  private:
diff --git a/chrome/browser/dips/dips_service.cc b/chrome/browser/dips/dips_service.cc
index 2f84d02..eca651b 100644
--- a/chrome/browser/dips/dips_service.cc
+++ b/chrome/browser/dips/dips_service.cc
@@ -157,10 +157,10 @@
 
 void DIPSService::RemoveEvents(const base::Time& delete_begin,
                                const base::Time& delete_end,
-                               const UrlPredicate& predicate,
+                               network::mojom::ClearDataFilterPtr filter,
                                DIPSEventRemovalType type) {
   storage_.AsyncCall(&DIPSStorage::RemoveEvents)
-      .WithArgs(delete_begin, delete_end, predicate, type);
+      .WithArgs(delete_begin, delete_end, std::move(filter), type);
 }
 
 void DIPSService::InitializeStorageWithEngagedSites() {
diff --git a/chrome/browser/dips/dips_service.h b/chrome/browser/dips/dips_service.h
index 728d9187..09eb31b4 100644
--- a/chrome/browser/dips/dips_service.h
+++ b/chrome/browser/dips/dips_service.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/dips/dips_storage.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 #include "third_party/blink/public/mojom/site_engagement/site_engagement.mojom-forward.h"
 
 class Profile;
@@ -48,7 +49,7 @@
 
   void RemoveEvents(const base::Time& delete_begin,
                     const base::Time& delete_end,
-                    const UrlPredicate& predicate,
+                    network::mojom::ClearDataFilterPtr filter,
                     const DIPSEventRemovalType type);
 
   void HandleRedirect(const DIPSRedirectInfo& redirect,
diff --git a/chrome/browser/dips/dips_storage.cc b/chrome/browser/dips/dips_storage.cc
index dd25af3..b8c638c 100644
--- a/chrome/browser/dips/dips_storage.cc
+++ b/chrome/browser/dips/dips_storage.cc
@@ -13,6 +13,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/dips/dips_utils.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 #include "sql/init_status.h"
 #include "url/gurl.h"
 
@@ -101,7 +102,7 @@
 
 void DIPSStorage::RemoveEvents(base::Time delete_begin,
                                base::Time delete_end,
-                               const UrlPredicate& predicate,
+                               network::mojom::ClearDataFilterPtr filter,
                                const DIPSEventRemovalType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(db_);
@@ -110,11 +111,27 @@
   if (delete_end.is_null())
     delete_end = base::Time::Max();
 
-  // Currently, only time-based deletions are supported.
-  if (!predicate.is_null())
-    return;
+  if (filter.is_null()) {
+    db_->RemoveEventsByTime(delete_begin, delete_end, type);
+  } else if (type == DIPSEventRemovalType::kStorage &&
+             filter->origins.empty()) {
+    // Site-filtered deletion is only supported for cookie-related
+    // DIPS events, since only cookie deletion allows domains but not hosts.
+    //
+    // TODO(jdh): Assess the use of cookie deletions with both a time range and
+    // a list of domains to determine whether supporting time ranges here is
+    // necessary.
+    // Time ranges aren't currently supported for site-filtered
+    // deletion of DIPS Events.
+    if (delete_begin != base::Time() || delete_end != base::Time::Max())
+      return;
 
-  db_->RemoveEventsByTime(delete_begin, delete_end, type);
+    bool preserve =
+        (filter->type == network::mojom::ClearDataFilter::Type::KEEP_MATCHES);
+    std::vector<std::string> sites = std::move(filter->domains);
+
+    db_->RemoveEventsBySite(preserve, sites, type);
+  }
 }
 
 // DIPSTabHelper Function Impls ------------------------------------------------
diff --git a/chrome/browser/dips/dips_storage.h b/chrome/browser/dips/dips_storage.h
index 78c82045..19eca31 100644
--- a/chrome/browser/dips/dips_storage.h
+++ b/chrome/browser/dips/dips_storage.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/dips/dips_database.h"
 #include "chrome/browser/dips/dips_state.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 
 class GURL;
 
@@ -29,7 +30,7 @@
 
   void RemoveEvents(base::Time delete_begin,
                     base::Time delete_end,
-                    const UrlPredicate& predicate,
+                    network::mojom::ClearDataFilterPtr filter,
                     const DIPSEventRemovalType type);
 
   // DIPS Helper Method Impls --------------------------------------------------
diff --git a/chrome/browser/dips/dips_storage_unittest.cc b/chrome/browser/dips/dips_storage_unittest.cc
index b54737b3..c15da25 100644
--- a/chrome/browser/dips/dips_storage_unittest.cc
+++ b/chrome/browser/dips/dips_storage_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/test/task_environment.h"
 #include "base/threading/sequence_bound.h"
 #include "chrome/browser/dips/dips_utils.h"
+#include "content/public/browser/browsing_data_filter_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
@@ -155,8 +156,7 @@
   storage_.WriteForTesting(
       url2, {{absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kAll);
 
   DIPSState state1 = storage_.Read(url1);
@@ -185,8 +185,7 @@
   storage_.WriteForTesting(
       url2, {{absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kAll);
 
   DIPSState state1 = storage_.Read(url1);
@@ -212,8 +211,7 @@
   storage_.WriteForTesting(
       url1, {{base::Time::FromDoubleT(1), base::Time::FromDoubleT(3)},
              {base::Time::FromDoubleT(5), base::Time::FromDoubleT(8)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kAll);
 
   DIPSState state = storage_.Read(url1);
@@ -239,8 +237,7 @@
   storage_.WriteForTesting(
       url2, {{absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kStorage);
 
   DIPSState state1 = storage_.Read(url1);
@@ -272,8 +269,7 @@
   storage_.WriteForTesting(
       url2, {{absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kHistory);
 
   DIPSState state1 = storage_.Read(url1);
@@ -306,8 +302,7 @@
              {absl::nullopt, absl::nullopt},
              {absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kStorage);
 
   DIPSState state1 = storage_.Read(url1);
@@ -343,8 +338,7 @@
              {absl::nullopt, absl::nullopt},
              {absl::nullopt, absl::nullopt},
              {base::Time::FromDoubleT(3), base::Time::FromDoubleT(5)}});
-  storage_.RemoveEvents(delete_begin, delete_end,
-                        base::RepeatingCallback<bool(const GURL&)>(),
+  storage_.RemoveEvents(delete_begin, delete_end, nullptr,
                         DIPSEventRemovalType::kHistory);
 
   DIPSState state1 = storage_.Read(url1);
@@ -361,6 +355,106 @@
   EXPECT_FALSE(state2.was_loaded());  // removed
 }
 
+TEST_F(DIPSStorageTest, RemoveBySite) {
+  GURL url1("https://example1.com");
+  GURL url2("https://example2.com");
+  GURL url3("https://example3.com");
+  GURL url4("https://example4.com");
+
+  storage_.WriteForTesting(
+      url1, {{base::Time::FromDoubleT(1), base::Time::FromDoubleT(1)},
+             {base::Time::FromDoubleT(2), base::Time::FromDoubleT(2)},
+             {base::Time::FromDoubleT(3), base::Time::FromDoubleT(3)},
+             {base::Time::FromDoubleT(4), base::Time::FromDoubleT(4)}});
+  storage_.WriteForTesting(
+      url2, {{base::Time::FromDoubleT(1), base::Time::FromDoubleT(1)},
+             {base::Time::FromDoubleT(2), base::Time::FromDoubleT(2)},
+             {base::Time::FromDoubleT(3), base::Time::FromDoubleT(3)},
+             {base::Time::FromDoubleT(4), base::Time::FromDoubleT(4)}});
+  storage_.WriteForTesting(
+      url3, {{base::Time::FromDoubleT(1), base::Time::FromDoubleT(2)},
+             {absl::nullopt, absl::nullopt},
+             {base::Time::FromDoubleT(3), base::Time::FromDoubleT(4)},
+             {absl::nullopt, absl::nullopt}});
+  storage_.WriteForTesting(
+      url4, {{absl::nullopt, absl::nullopt},
+             {base::Time::FromDoubleT(2), base::Time::FromDoubleT(2)},
+             {base::Time::FromDoubleT(3), base::Time::FromDoubleT(3)},
+             {base::Time::FromDoubleT(4), base::Time::FromDoubleT(4)}});
+
+  std::unique_ptr<content::BrowsingDataFilterBuilder> builder =
+      content::BrowsingDataFilterBuilder::Create(
+          content::BrowsingDataFilterBuilder::Mode::kDelete);
+  builder->AddRegisterableDomain(GetSiteForDIPS(url1));
+  builder->AddRegisterableDomain(GetSiteForDIPS(url3));
+  storage_.RemoveEvents(base::Time(), base::Time::Max(),
+                        builder->BuildNetworkServiceFilter(),
+                        DIPSEventRemovalType::kStorage);
+
+  DIPSState state1 = storage_.Read(url1);
+  EXPECT_FALSE(state1.site_storage_times().first.has_value());  // removed
+  EXPECT_EQ(state1.user_interaction_times().first,
+            absl::make_optional(base::Time::FromDoubleT(2)));      // no change
+  EXPECT_FALSE(state1.stateful_bounce_times().first.has_value());  // removed
+  EXPECT_EQ(state1.stateless_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(4)));  // no change
+
+  DIPSState state2 = storage_.Read(url2);
+  EXPECT_EQ(state2.site_storage_times().first,
+            absl::make_optional(base::Time::FromDoubleT(1)));  // no change
+  EXPECT_EQ(state2.user_interaction_times().first,
+            absl::make_optional(base::Time::FromDoubleT(2)));  // no change
+  EXPECT_EQ(state2.stateful_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(3)));  // no change
+  EXPECT_EQ(state2.stateless_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(4)));  // no change
+
+  DIPSState state3 = storage_.Read(url3);
+  EXPECT_FALSE(state3.was_loaded());  // removed
+
+  DIPSState state4 = storage_.Read(url2);
+  EXPECT_FALSE(state1.site_storage_times().first.has_value());  // no change
+  EXPECT_EQ(state4.user_interaction_times().first,
+            absl::make_optional(base::Time::FromDoubleT(2)));  // no change
+  EXPECT_EQ(state4.stateful_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(3)));  // no change
+  EXPECT_EQ(state4.stateless_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(4)));  // no change
+}
+
+TEST_F(DIPSStorageTest, RemoveBySiteIgnoresDeletionWithTimeRange) {
+  GURL url1("https://example1.com");
+  base::Time delete_begin = base::Time::FromDoubleT(2);
+  base::Time delete_end = base::Time::FromDoubleT(6);
+
+  storage_.WriteForTesting(
+      url1, {{base::Time::FromDoubleT(1), base::Time::FromDoubleT(1)},
+             {base::Time::FromDoubleT(2), base::Time::FromDoubleT(2)},
+             {base::Time::FromDoubleT(3), base::Time::FromDoubleT(3)},
+             {base::Time::FromDoubleT(4), base::Time::FromDoubleT(4)}});
+
+  std::unique_ptr<content::BrowsingDataFilterBuilder> builder =
+      content::BrowsingDataFilterBuilder::Create(
+          content::BrowsingDataFilterBuilder::Mode::kDelete);
+  builder->AddRegisterableDomain(GetSiteForDIPS(url1));
+  storage_.RemoveEvents(delete_begin, delete_end,
+                        builder->BuildNetworkServiceFilter(),
+                        DIPSEventRemovalType::kStorage);
+
+  // Removing events by site (i.e. by using a non-null filter) with a time-range
+  // (other than base::Time() to base::Time::Max()), is currently unsupported.
+  // So url1's DIPS Storage entry should be unaffected.
+  DIPSState state1 = storage_.Read(url1);
+  EXPECT_EQ(state1.site_storage_times().first,
+            absl::make_optional(base::Time::FromDoubleT(1)));  // no change
+  EXPECT_EQ(state1.user_interaction_times().first,
+            absl::make_optional(base::Time::FromDoubleT(2)));  // no change
+  EXPECT_EQ(state1.stateful_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(3)));  // no change
+  EXPECT_EQ(state1.stateless_bounce_times().first,
+            absl::make_optional(base::Time::FromDoubleT(4)));  // no change
+}
+
 class DIPSStoragePrepopulateTest : public testing::Test {
  public:
   DIPSStoragePrepopulateTest()
diff --git a/chrome/browser/download/OWNERS b/chrome/browser/download/OWNERS
index 4d6d5640..0206bb2d5 100644
--- a/chrome/browser/download/OWNERS
+++ b/chrome/browser/download/OWNERS
@@ -1,4 +1,4 @@
 file://components/download/OWNERS
 
 per-file download_request_limiter*=dominickn@chromium.org
-per-file mixed_content_download_blocking*=jdeblasio@chromium.org
+per-file insecure_download_blocking*=jdeblasio@chromium.org
diff --git a/chrome/browser/download/android/BUILD.gn b/chrome/browser/download/android/BUILD.gn
index ab95043d..9b77546 100644
--- a/chrome/browser/download/android/BUILD.gn
+++ b/chrome/browser/download/android/BUILD.gn
@@ -35,9 +35,9 @@
     "java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java",
     "java/src/org/chromium/chrome/browser/download/DownloadStartupUtils.java",
     "java/src/org/chromium/chrome/browser/download/DownloadStatus.java",
+    "java/src/org/chromium/chrome/browser/download/InsecureDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/MediaStoreHelper.java",
     "java/src/org/chromium/chrome/browser/download/MimeUtils.java",
-    "java/src/org/chromium/chrome/browser/download/MixedContentDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/StringUtils.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DangerousDownloadDialog.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDialogUtils.java",
@@ -46,7 +46,7 @@
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogCoordinator.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogProperties.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogViewBinder.java",
-    "java/src/org/chromium/chrome/browser/download/dialogs/MixedContentDownloadDialog.java",
+    "java/src/org/chromium/chrome/browser/download/dialogs/InsecureDownloadDialog.java",
     "java/src/org/chromium/chrome/browser/download/home/DownloadManagerCoordinator.java",
     "java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java",
     "java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfigHelper.java",
@@ -136,8 +136,8 @@
     "java/src/org/chromium/chrome/browser/download/DownloadInfo.java",
     "java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java",
     "java/src/org/chromium/chrome/browser/download/DownloadStartupUtils.java",
+    "java/src/org/chromium/chrome/browser/download/InsecureDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/MimeUtils.java",
-    "java/src/org/chromium/chrome/browser/download/MixedContentDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/StringUtils.java",
     "java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
   ]
diff --git a/chrome/browser/download/android/mixed_content_download_dialog_bridge.cc b/chrome/browser/download/android/insecure_download_dialog_bridge.cc
similarity index 61%
rename from chrome/browser/download/android/mixed_content_download_dialog_bridge.cc
rename to chrome/browser/download/android/insecure_download_dialog_bridge.cc
index 62fdd7b..acdf831c 100644
--- a/chrome/browser/download/android/mixed_content_download_dialog_bridge.cc
+++ b/chrome/browser/download/android/insecure_download_dialog_bridge.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/download/android/mixed_content_download_dialog_bridge.h"
+#include "chrome/browser/download/android/insecure_download_dialog_bridge.h"
 
 #include <string>
 
@@ -14,7 +14,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/android/android_theme_resources.h"
 #include "chrome/browser/download/android/download_dialog_utils.h"
-#include "chrome/browser/download/android/jni_headers/MixedContentDownloadDialogBridge_jni.h"
+#include "chrome/browser/download/android/jni_headers/InsecureDownloadDialogBridge_jni.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/browser/web_contents.h"
@@ -22,26 +22,25 @@
 #include "ui/base/l10n/l10n_util.h"
 
 using base::android::JavaParamRef;
-using MixedContentStatus = download::DownloadItem::MixedContentStatus;
+using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus;
 
 // static
-MixedContentDownloadDialogBridge*
-MixedContentDownloadDialogBridge::GetInstance() {
-  return base::Singleton<MixedContentDownloadDialogBridge>::get();
+InsecureDownloadDialogBridge* InsecureDownloadDialogBridge::GetInstance() {
+  return base::Singleton<InsecureDownloadDialogBridge>::get();
 }
 
-MixedContentDownloadDialogBridge::MixedContentDownloadDialogBridge() {
+InsecureDownloadDialogBridge::InsecureDownloadDialogBridge() {
   JNIEnv* env = base::android::AttachCurrentThread();
-  java_object_.Reset(Java_MixedContentDownloadDialogBridge_create(
+  java_object_.Reset(Java_InsecureDownloadDialogBridge_create(
       env, reinterpret_cast<intptr_t>(this)));
 }
 
-MixedContentDownloadDialogBridge::~MixedContentDownloadDialogBridge() {
-  Java_MixedContentDownloadDialogBridge_destroy(
+InsecureDownloadDialogBridge::~InsecureDownloadDialogBridge() {
+  Java_InsecureDownloadDialogBridge_destroy(
       base::android::AttachCurrentThread(), java_object_);
 }
 
-void MixedContentDownloadDialogBridge::CreateDialog(
+void InsecureDownloadDialogBridge::CreateDialog(
     download::DownloadItem* download,
     const base::FilePath& base_name,
     ui::WindowAndroid* window_android,
@@ -53,23 +52,23 @@
   }
   JNIEnv* env = base::android::AttachCurrentThread();
   intptr_t callback_id = reinterpret_cast<intptr_t>(
-      new MixedContentDialogCallback(std::move(callback)));
+      new InsecureDownloadDialogCallback(std::move(callback)));
   validator_.AddJavaCallback(callback_id);
 
-  Java_MixedContentDownloadDialogBridge_showDialog(
+  Java_InsecureDownloadDialogBridge_showDialog(
       env, java_object_, window_android->GetJavaObject(),
       base::android::ConvertUTF16ToJavaString(
           env, base::UTF8ToUTF16(base_name.value())),
       download->GetTotalBytes(), callback_id);
 }
 
-void MixedContentDownloadDialogBridge::OnConfirmed(JNIEnv* env,
-                                                   jlong callback_id,
-                                                   jboolean accepted) {
+void InsecureDownloadDialogBridge::OnConfirmed(JNIEnv* env,
+                                               jlong callback_id,
+                                               jboolean accepted) {
   if (!validator_.ValidateAndClearJavaCallback(callback_id))
     return;
   // Convert java long long int to c++ pointer, take ownership.
-  std::unique_ptr<MixedContentDialogCallback> cb(
-      reinterpret_cast<MixedContentDialogCallback*>(callback_id));
+  std::unique_ptr<InsecureDownloadDialogCallback> cb(
+      reinterpret_cast<InsecureDownloadDialogCallback*>(callback_id));
   std::move(*cb).Run(accepted);
 }
diff --git a/chrome/browser/download/android/insecure_download_dialog_bridge.h b/chrome/browser/download/android/insecure_download_dialog_bridge.h
new file mode 100644
index 0000000..99a29a56
--- /dev/null
+++ b/chrome/browser/download/android/insecure_download_dialog_bridge.h
@@ -0,0 +1,54 @@
+// Copyright 2021 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_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_DIALOG_BRIDGE_H_
+#define CHROME_BROWSER_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_DIALOG_BRIDGE_H_
+
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/callback_forward.h"
+#include "chrome/browser/download/android/download_callback_validator.h"
+#include "components/download/public/common/download_item.h"
+#include "ui/gfx/native_widget_types.h"
+
+// Class for showing dialogs to asks whether user wants to download an insecure
+// URL.
+class InsecureDownloadDialogBridge : public download::DownloadItem::Observer {
+ public:
+  using InsecureDownloadDialogCallback =
+      base::OnceCallback<void(bool /* accept */)>;
+
+  static InsecureDownloadDialogBridge* GetInstance();
+
+  InsecureDownloadDialogBridge();
+  InsecureDownloadDialogBridge(const InsecureDownloadDialogBridge&) = delete;
+  InsecureDownloadDialogBridge& operator=(const InsecureDownloadDialogBridge&) =
+      delete;
+
+  ~InsecureDownloadDialogBridge() override;
+
+  // Called to create and show a dialog for an insecure download.
+  void CreateDialog(download::DownloadItem* download,
+                    const base::FilePath& base_name,
+                    ui::WindowAndroid* window_android,
+                    InsecureDownloadDialogCallback callback);
+
+  // Called from Java via JNI.
+  void OnConfirmed(JNIEnv* env, jlong callback_id, jboolean accepted);
+
+ private:
+  // Download items that are requesting the dialog. Could get deleted while
+  // the dialog is showing.
+  std::vector<download::DownloadItem*> download_items_;
+
+  // Validator for all JNI callbacks.
+  DownloadCallbackValidator validator_;
+
+  // The corresponding java object.
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+};
+
+#endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_DIALOG_BRIDGE_H_
diff --git a/chrome/browser/download/android/insecure_download_infobar_delegate.cc b/chrome/browser/download/android/insecure_download_infobar_delegate.cc
new file mode 100644
index 0000000..f134bd6
--- /dev/null
+++ b/chrome/browser/download/android/insecure_download_infobar_delegate.cc
@@ -0,0 +1,110 @@
+// 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 "chrome/browser/download/android/insecure_download_infobar_delegate.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/download/public/common/download_item.h"
+#include "components/infobars/android/confirm_infobar.h"
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/infobar.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ui_strings.h"
+
+using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus;
+
+// static
+void InsecureDownloadInfoBarDelegate::Create(
+    infobars::ContentInfoBarManager* infobar_manager,
+    const base::FilePath& basename,
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status,
+    ResultCallback callback) {
+  infobar_manager->AddInfoBar(std::make_unique<infobars::ConfirmInfoBar>(
+      base::WrapUnique(new InsecureDownloadInfoBarDelegate(
+          basename, insecure_download_status, std::move(callback)))));
+}
+
+InsecureDownloadInfoBarDelegate::InsecureDownloadInfoBarDelegate(
+    const base::FilePath& basename,
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status,
+    ResultCallback callback)
+    : insecure_download_status_(insecure_download_status),
+      callback_(std::move(callback)) {
+  message_text_ =
+      l10n_util::GetStringFUTF16(IDS_PROMPT_CONFIRM_INSECURE_DOWNLOAD,
+                                 base::UTF8ToUTF16(basename.value()));
+}
+
+InsecureDownloadInfoBarDelegate::~InsecureDownloadInfoBarDelegate() = default;
+
+infobars::InfoBarDelegate::InfoBarIdentifier
+InsecureDownloadInfoBarDelegate::GetIdentifier() const {
+  return INSECURE_DOWNLOAD_INFOBAR_DELEGATE_ANDROID;
+}
+
+int InsecureDownloadInfoBarDelegate::GetIconId() const {
+  return IDR_ANDROID_INFOBAR_WARNING;
+}
+
+bool InsecureDownloadInfoBarDelegate::ShouldExpire(
+    const NavigationDetails& details) const {
+  return false;
+}
+
+void InsecureDownloadInfoBarDelegate::InfoBarDismissed() {
+  PostReply(false);
+}
+
+std::u16string InsecureDownloadInfoBarDelegate::GetMessageText() const {
+  return message_text_;
+}
+
+std::u16string InsecureDownloadInfoBarDelegate::GetButtonLabel(
+    InfoBarButton button) const {
+  if (insecure_download_status_ == InsecureDownloadStatus::WARN) {
+    return l10n_util::GetStringUTF16(
+        button == BUTTON_OK ? IDS_CONFIRM_DOWNLOAD : IDS_DISCARD_DOWNLOAD);
+  }
+
+  DCHECK_EQ(insecure_download_status_, InsecureDownloadStatus::BLOCK);
+  // Default button is Discard when blocking.
+  return l10n_util::GetStringUTF16(button == BUTTON_OK ? IDS_DISCARD_DOWNLOAD
+                                                       : IDS_CONFIRM_DOWNLOAD);
+}
+
+bool InsecureDownloadInfoBarDelegate::Accept() {
+  if (insecure_download_status_ == InsecureDownloadStatus::WARN) {
+    PostReply(true);
+    return true;
+  }
+
+  DCHECK_EQ(insecure_download_status_, InsecureDownloadStatus::BLOCK);
+  // Default button is Discard when blocking.
+  PostReply(false);
+  return true;
+}
+
+bool InsecureDownloadInfoBarDelegate::Cancel() {
+  if (insecure_download_status_ == InsecureDownloadStatus::WARN) {
+    PostReply(false);
+    return true;
+  }
+
+  CHECK_EQ(insecure_download_status_, InsecureDownloadStatus::BLOCK);
+  // Cancel button is Keep when blocking.
+  PostReply(true);
+  return true;
+}
+
+void InsecureDownloadInfoBarDelegate::PostReply(bool should_download) {
+  DCHECK(callback_);
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), should_download));
+}
diff --git a/chrome/browser/download/android/insecure_download_infobar_delegate.h b/chrome/browser/download/android/insecure_download_infobar_delegate.h
new file mode 100644
index 0000000..ed7cbfe
--- /dev/null
+++ b/chrome/browser/download/android/insecure_download_infobar_delegate.h
@@ -0,0 +1,61 @@
+// 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 CHROME_BROWSER_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_INFOBAR_DELEGATE_H_
+#define CHROME_BROWSER_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_INFOBAR_DELEGATE_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "components/download/public/common/download_item.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+
+namespace infobars {
+class ContentInfoBarManager;
+}
+
+// An infobar that asks if user wants to download an insecurely delivered file.
+// Note that this infobar does not expire if the user subsequently navigates,
+// since such navigations won't automatically cancel the underlying download.
+class InsecureDownloadInfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+  using ResultCallback = base::OnceCallback<void(bool should_download)>;
+
+  static void Create(
+      infobars::ContentInfoBarManager* infobar_manager,
+      const base::FilePath& basename,
+      download::DownloadItem::InsecureDownloadStatus insecure_download_status,
+      ResultCallback callback);
+
+  InsecureDownloadInfoBarDelegate(const InsecureDownloadInfoBarDelegate&) =
+      delete;
+  InsecureDownloadInfoBarDelegate& operator=(
+      const InsecureDownloadInfoBarDelegate&) = delete;
+
+  ~InsecureDownloadInfoBarDelegate() override;
+
+ private:
+  explicit InsecureDownloadInfoBarDelegate(
+      const base::FilePath& basename,
+      download::DownloadItem::InsecureDownloadStatus insecure_download_status,
+      ResultCallback callback);
+
+  // ConfirmInfoBarDelegate:
+  infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
+  int GetIconId() const override;
+  bool ShouldExpire(const NavigationDetails& details) const override;
+  void InfoBarDismissed() override;
+  std::u16string GetMessageText() const override;
+  std::u16string GetButtonLabel(InfoBarButton button) const override;
+  bool Accept() override;
+  bool Cancel() override;
+
+  // Calls callback_ with the appropriate result.
+  void PostReply(bool should_download);
+
+  std::u16string message_text_;
+  download::DownloadItem::InsecureDownloadStatus insecure_download_status_;
+  ResultCallback callback_;
+};
+
+#endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_INSECURE_DOWNLOAD_INFOBAR_DELEGATE_H_
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/InsecureDownloadDialogBridge.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/InsecureDownloadDialogBridge.java
new file mode 100644
index 0000000..ebcef08
--- /dev/null
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/InsecureDownloadDialogBridge.java
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import android.app.Activity;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.download.dialogs.InsecureDownloadDialog;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
+
+/**
+ * Glues insecure download dialogs UI code and handles the communication to download native
+ * backend.
+ */
+public class InsecureDownloadDialogBridge {
+    private long mNativeInsecureDownloadDialogBridge;
+
+    /**
+     * Constructor, taking a pointer to the native instance.
+     * @param nativeInsecureDownloadDialogBridge Pointer to the native object.
+     */
+    public InsecureDownloadDialogBridge(long nativeInsecureDownloadDialogBridge) {
+        mNativeInsecureDownloadDialogBridge = nativeInsecureDownloadDialogBridge;
+    }
+
+    @CalledByNative
+    private static InsecureDownloadDialogBridge create(long nativeDialog) {
+        return new InsecureDownloadDialogBridge(nativeDialog);
+    }
+
+    /**
+     * Called to show a warning dialog for insecure download.
+     * @param windowAndroid Window to show the dialog.
+     * @param fileName Name of the download file.
+     * @param totalBytes Total bytes of the file.
+     * @param callbackId Native callback Id to invoke.
+     */
+    @CalledByNative
+    private void showDialog(
+            WindowAndroid windowAndroid, String fileName, long totalBytes, long callbackId) {
+        Activity activity = windowAndroid.getActivity().get();
+        if (activity == null) {
+            onConfirmed(callbackId, false);
+            return;
+        }
+
+        new InsecureDownloadDialog().show(activity,
+                ((ModalDialogManagerHolder) activity).getModalDialogManager(), fileName, totalBytes,
+                (accepted) -> { onConfirmed(callbackId, accepted); });
+    }
+
+    @CalledByNative
+    private void destroy() {
+        mNativeInsecureDownloadDialogBridge = 0;
+    }
+
+    private void onConfirmed(long callbackId, boolean accepted) {
+        InsecureDownloadDialogBridgeJni.get().onConfirmed(
+                mNativeInsecureDownloadDialogBridge, callbackId, accepted);
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onConfirmed(
+                long nativeInsecureDownloadDialogBridge, long callbackId, boolean accepted);
+    }
+}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/MixedContentDownloadDialogBridge.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/MixedContentDownloadDialogBridge.java
deleted file mode 100644
index 3de42da..0000000
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/MixedContentDownloadDialogBridge.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download;
-
-import android.app.Activity;
-
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.download.dialogs.MixedContentDownloadDialog;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
-
-/**
- * Glues mixed-content download dialogs UI code and handles the communication to download native
- * backend.
- */
-public class MixedContentDownloadDialogBridge {
-    private long mNativeMixedContentDownloadDialogBridge;
-
-    /**
-     * Constructor, taking a pointer to the native instance.
-     * @param nativeMixedContentDownloadDialogBridge Pointer to the native object.
-     */
-    public MixedContentDownloadDialogBridge(long nativeMixedContentDownloadDialogBridge) {
-        mNativeMixedContentDownloadDialogBridge = nativeMixedContentDownloadDialogBridge;
-    }
-
-    @CalledByNative
-    private static MixedContentDownloadDialogBridge create(long nativeDialog) {
-        return new MixedContentDownloadDialogBridge(nativeDialog);
-    }
-
-    /**
-     * Called to show a warning dialog for mixed-content download.
-     * @param windowAndroid Window to show the dialog.
-     * @param fileName Name of the download file.
-     * @param totalBytes Total bytes of the file.
-     * @param callbackId Native callback Id to invoke.
-     */
-    @CalledByNative
-    private void showDialog(
-            WindowAndroid windowAndroid, String fileName, long totalBytes, long callbackId) {
-        Activity activity = windowAndroid.getActivity().get();
-        if (activity == null) {
-            onConfirmed(callbackId, false);
-            return;
-        }
-
-        new MixedContentDownloadDialog().show(activity,
-                ((ModalDialogManagerHolder) activity).getModalDialogManager(), fileName, totalBytes,
-                (accepted) -> { onConfirmed(callbackId, accepted); });
-    }
-
-    @CalledByNative
-    private void destroy() {
-        mNativeMixedContentDownloadDialogBridge = 0;
-    }
-
-    private void onConfirmed(long callbackId, boolean accepted) {
-        MixedContentDownloadDialogBridgeJni.get().onConfirmed(
-                mNativeMixedContentDownloadDialogBridge, callbackId, accepted);
-    }
-
-    @NativeMethods
-    interface Natives {
-        void onConfirmed(
-                long nativeMixedContentDownloadDialogBridge, long callbackId, boolean accepted);
-    }
-}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/MixedContentDownloadDialog.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/InsecureDownloadDialog.java
similarity index 73%
rename from chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/MixedContentDownloadDialog.java
rename to chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/InsecureDownloadDialog.java
index c61c882..eddc3d41 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/MixedContentDownloadDialog.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/InsecureDownloadDialog.java
@@ -18,18 +18,19 @@
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
- * Dialog for confirming that the user wants to download a mixed-content file, using the default
- * model dialog from ModalDialogManager.
+ * Dialog for confirming that the user wants to download an insecurely-delivered file, using the
+ * default model dialog from ModalDialogManager.
  */
-public class MixedContentDownloadDialog {
+public class InsecureDownloadDialog {
     /**
-     * Events related to the mixed-content download dialog, used for UMA reporting.
+     * Events related to the insecure download dialog, used for UMA reporting.
      * These values are persisted to logs. Entries should not be renumbered and
      * numeric values should never be reused.
      */
-    @IntDef({MixedContentDownloadDialogEvent.SHOW, MixedContentDownloadDialogEvent.CONFIRM,
-            MixedContentDownloadDialogEvent.CANCEL, MixedContentDownloadDialogEvent.DISMISS})
-    private @interface MixedContentDownloadDialogEvent {
+    @IntDef({InsecureDownloadDialogEvent.SHOW, InsecureDownloadDialogEvent.CONFIRM,
+            InsecureDownloadDialogEvent.CANCEL, InsecureDownloadDialogEvent.DISMISS,
+            InsecureDownloadDialogEvent.COUNT})
+    private @interface InsecureDownloadDialogEvent {
         int SHOW = 0;
         int CONFIRM = 1;
         int CANCEL = 2;
@@ -39,7 +40,7 @@
     }
 
     /**
-     * Called to show a warning dialog for mixed-content download.
+     * Called to show a warning dialog for insecure download.
      * @param context Context for showing the dialog.
      * @param modalDialogManager Manager for managing the modal dialog.
      * @param fileName Name of the download file.
@@ -69,9 +70,9 @@
                                                                          .POSITIVE_BUTTON_CLICKED
                                                                : DialogDismissalCause
                                                                          .NEGATIVE_BUTTON_CLICKED);
-                                        recordMixedContentDownloadDialogEvent(acceptDownload
-                                                        ? MixedContentDownloadDialogEvent.CONFIRM
-                                                        : MixedContentDownloadDialogEvent.CANCEL);
+                                        recordInsecureDownloadDialogEvent(acceptDownload
+                                                        ? InsecureDownloadDialogEvent.CONFIRM
+                                                        : InsecureDownloadDialogEvent.CANCEL);
                                     }
 
                                     @Override
@@ -83,36 +84,35 @@
                                                         != DialogDismissalCause
                                                                    .NEGATIVE_BUTTON_CLICKED) {
                                             if (callback != null) callback.onResult(false);
-                                            recordMixedContentDownloadDialogEvent(
-                                                    MixedContentDownloadDialogEvent.DISMISS);
+                                            recordInsecureDownloadDialogEvent(
+                                                    InsecureDownloadDialogEvent.DISMISS);
                                         }
                                     }
                                 })
                         .with(ModalDialogProperties.TITLE,
                                 context.getResources().getString(
-                                        R.string.mixed_content_download_dialog_title))
+                                        R.string.insecure_download_dialog_title))
                         .with(ModalDialogProperties.MESSAGE_PARAGRAPH_1, message)
                         .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                                 context.getResources().getString(
-                                        R.string.mixed_content_download_dialog_confirm_text))
+                                        R.string.insecure_download_dialog_confirm_text))
                         .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
                                 context.getResources().getString(
-                                        R.string.mixed_content_download_dialog_discard_text))
+                                        R.string.insecure_download_dialog_discard_text))
                         .with(ModalDialogProperties.BUTTON_STYLES,
                                 ModalDialogProperties.ButtonStyles.PRIMARY_OUTLINE_NEGATIVE_OUTLINE)
                         .build();
 
         modalDialogManager.showDialog(propertyModel, ModalDialogManager.ModalDialogType.TAB);
-        recordMixedContentDownloadDialogEvent(MixedContentDownloadDialogEvent.SHOW);
+        recordInsecureDownloadDialogEvent(InsecureDownloadDialogEvent.SHOW);
     }
 
     /**
-     * Collects mixed content download dialog UI event metrics.
+     * Collects insecure download dialog UI event metrics.
      * @param event The UI event to collect.
      */
-    private static void recordMixedContentDownloadDialogEvent(
-            @MixedContentDownloadDialogEvent int event) {
+    private static void recordInsecureDownloadDialogEvent(@InsecureDownloadDialogEvent int event) {
         RecordHistogram.recordEnumeratedHistogram(
-                "Download.MixedContentDialog.Events", event, MixedContentDownloadDialogEvent.COUNT);
+                "Download.MixedContentDialog.Events", event, InsecureDownloadDialogEvent.COUNT);
     }
 }
diff --git a/chrome/browser/download/android/mixed_content_download_dialog_bridge.h b/chrome/browser/download/android/mixed_content_download_dialog_bridge.h
deleted file mode 100644
index 296b357..0000000
--- a/chrome/browser/download/android/mixed_content_download_dialog_bridge.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 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_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_DIALOG_BRIDGE_H_
-#define CHROME_BROWSER_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_DIALOG_BRIDGE_H_
-
-#include <vector>
-
-#include "base/android/jni_android.h"
-#include "base/android/scoped_java_ref.h"
-#include "base/callback_forward.h"
-#include "chrome/browser/download/android/download_callback_validator.h"
-#include "components/download/public/common/download_item.h"
-#include "ui/gfx/native_widget_types.h"
-
-// Class for showing dialogs to asks whether user wants to download a mixed
-// content URL.
-class MixedContentDownloadDialogBridge
-    : public download::DownloadItem::Observer {
- public:
-  using MixedContentDialogCallback =
-      base::OnceCallback<void(bool /* accept */)>;
-
-  static MixedContentDownloadDialogBridge* GetInstance();
-
-  MixedContentDownloadDialogBridge();
-  MixedContentDownloadDialogBridge(const MixedContentDownloadDialogBridge&) =
-      delete;
-  MixedContentDownloadDialogBridge& operator=(
-      const MixedContentDownloadDialogBridge&) = delete;
-
-  ~MixedContentDownloadDialogBridge() override;
-
-  // Called to create and show a dialog for a mixed-content download.
-  void CreateDialog(download::DownloadItem* download,
-                    const base::FilePath& base_name,
-                    ui::WindowAndroid* window_android,
-                    MixedContentDialogCallback callback);
-
-  // Called from Java via JNI.
-  void OnConfirmed(JNIEnv* env, jlong callback_id, jboolean accepted);
-
- private:
-  // Download items that are requesting the dialog. Could get deleted while
-  // the dialog is showing.
-  std::vector<download::DownloadItem*> download_items_;
-
-  // Validator for all JNI callbacks.
-  DownloadCallbackValidator validator_;
-
-  // The corresponding java object.
-  base::android::ScopedJavaGlobalRef<jobject> java_object_;
-};
-
-#endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_DIALOG_BRIDGE_H_
diff --git a/chrome/browser/download/android/mixed_content_download_infobar_delegate.cc b/chrome/browser/download/android/mixed_content_download_infobar_delegate.cc
deleted file mode 100644
index 449d48a0..0000000
--- a/chrome/browser/download/android/mixed_content_download_infobar_delegate.cc
+++ /dev/null
@@ -1,110 +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 "chrome/browser/download/android/mixed_content_download_infobar_delegate.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/memory/ptr_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/android/android_theme_resources.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/download/public/common/download_item.h"
-#include "components/infobars/android/confirm_infobar.h"
-#include "components/infobars/content/content_infobar_manager.h"
-#include "components/infobars/core/infobar.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/strings/grit/ui_strings.h"
-
-using MixedContentStatus = download::DownloadItem::MixedContentStatus;
-
-// static
-void MixedContentDownloadInfoBarDelegate::Create(
-    infobars::ContentInfoBarManager* infobar_manager,
-    const base::FilePath& basename,
-    download::DownloadItem::MixedContentStatus mixed_content_status,
-    ResultCallback callback) {
-  infobar_manager->AddInfoBar(std::make_unique<infobars::ConfirmInfoBar>(
-      base::WrapUnique(new MixedContentDownloadInfoBarDelegate(
-          basename, mixed_content_status, std::move(callback)))));
-}
-
-MixedContentDownloadInfoBarDelegate::MixedContentDownloadInfoBarDelegate(
-    const base::FilePath& basename,
-    download::DownloadItem::MixedContentStatus mixed_content_status,
-    ResultCallback callback)
-    : mixed_content_status_(mixed_content_status),
-      callback_(std::move(callback)) {
-  message_text_ =
-      l10n_util::GetStringFUTF16(IDS_PROMPT_CONFIRM_MIXED_CONTENT_DOWNLOAD,
-                                 base::UTF8ToUTF16(basename.value()));
-}
-
-MixedContentDownloadInfoBarDelegate::~MixedContentDownloadInfoBarDelegate() {}
-
-infobars::InfoBarDelegate::InfoBarIdentifier
-MixedContentDownloadInfoBarDelegate::GetIdentifier() const {
-  return MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_ANDROID;
-}
-
-int MixedContentDownloadInfoBarDelegate::GetIconId() const {
-  return IDR_ANDROID_INFOBAR_WARNING;
-}
-
-bool MixedContentDownloadInfoBarDelegate::ShouldExpire(
-    const NavigationDetails& details) const {
-  return false;
-}
-
-void MixedContentDownloadInfoBarDelegate::InfoBarDismissed() {
-  PostReply(false);
-}
-
-std::u16string MixedContentDownloadInfoBarDelegate::GetMessageText() const {
-  return message_text_;
-}
-
-std::u16string MixedContentDownloadInfoBarDelegate::GetButtonLabel(
-    InfoBarButton button) const {
-  if (mixed_content_status_ == MixedContentStatus::WARN) {
-    return l10n_util::GetStringUTF16(
-        button == BUTTON_OK ? IDS_CONFIRM_DOWNLOAD : IDS_DISCARD_DOWNLOAD);
-  }
-
-  DCHECK_EQ(mixed_content_status_, MixedContentStatus::BLOCK);
-  // Default button is Discard when blocking.
-  return l10n_util::GetStringUTF16(button == BUTTON_OK ? IDS_DISCARD_DOWNLOAD
-                                                       : IDS_CONFIRM_DOWNLOAD);
-}
-
-bool MixedContentDownloadInfoBarDelegate::Accept() {
-  if (mixed_content_status_ == MixedContentStatus::WARN) {
-    PostReply(true);
-    return true;
-  }
-
-  DCHECK_EQ(mixed_content_status_, MixedContentStatus::BLOCK);
-  // Default button is Discard when blocking.
-  PostReply(false);
-  return true;
-}
-
-bool MixedContentDownloadInfoBarDelegate::Cancel() {
-  if (mixed_content_status_ == MixedContentStatus::WARN) {
-    PostReply(false);
-    return true;
-  }
-
-  DCHECK_EQ(mixed_content_status_, MixedContentStatus::BLOCK);
-  // Cancel button is Keep when blocking.
-  PostReply(true);
-  return true;
-}
-
-void MixedContentDownloadInfoBarDelegate::PostReply(bool should_download) {
-  DCHECK(callback_);
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback_), should_download));
-}
diff --git a/chrome/browser/download/android/mixed_content_download_infobar_delegate.h b/chrome/browser/download/android/mixed_content_download_infobar_delegate.h
deleted file mode 100644
index 44373a6..0000000
--- a/chrome/browser/download/android/mixed_content_download_infobar_delegate.h
+++ /dev/null
@@ -1,62 +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 CHROME_BROWSER_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_H_
-#define CHROME_BROWSER_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_H_
-
-#include "base/callback.h"
-#include "base/files/file_path.h"
-#include "components/download/public/common/download_item.h"
-#include "components/infobars/core/confirm_infobar_delegate.h"
-
-namespace infobars {
-class ContentInfoBarManager;
-}
-
-// An infobar that asks if user wants to download an insecurely delivered file
-// initiated from a secure context.  Note that this infobar does not expire if
-// the user subsequently navigates, since such navigations won't automatically
-// cancel the underlying download.
-class MixedContentDownloadInfoBarDelegate : public ConfirmInfoBarDelegate {
- public:
-  using ResultCallback = base::OnceCallback<void(bool should_download)>;
-
-  static void Create(
-      infobars::ContentInfoBarManager* infobar_manager,
-      const base::FilePath& basename,
-      download::DownloadItem::MixedContentStatus mixed_content_status,
-      ResultCallback callback);
-
-  MixedContentDownloadInfoBarDelegate(
-      const MixedContentDownloadInfoBarDelegate&) = delete;
-  MixedContentDownloadInfoBarDelegate& operator=(
-      const MixedContentDownloadInfoBarDelegate&) = delete;
-
-  ~MixedContentDownloadInfoBarDelegate() override;
-
- private:
-  explicit MixedContentDownloadInfoBarDelegate(
-      const base::FilePath& basename,
-      download::DownloadItem::MixedContentStatus mixed_content_status,
-      ResultCallback callback);
-
-  // ConfirmInfoBarDelegate:
-  infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
-  int GetIconId() const override;
-  bool ShouldExpire(const NavigationDetails& details) const override;
-  void InfoBarDismissed() override;
-  std::u16string GetMessageText() const override;
-  std::u16string GetButtonLabel(InfoBarButton button) const override;
-  bool Accept() override;
-  bool Cancel() override;
-
-  // Calls callback_ with the appropriate result.
-  void PostReply(bool should_download);
-
-  std::u16string message_text_;
-  download::DownloadItem::MixedContentStatus mixed_content_status_;
-  ResultCallback callback_;
-};
-
-#endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_H_
diff --git a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
index 5ec14da..3bfbb2a 100644
--- a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
@@ -173,9 +173,9 @@
             ReturnRefOfCopy(base::FilePath(FILE_PATH_LITERAL("foo"))));
     EXPECT_CALL(item(index), GetLastReason())
         .WillRepeatedly(Return(download::DOWNLOAD_INTERRUPT_REASON_NONE));
-    EXPECT_CALL(item(index), GetMixedContentStatus())
+    EXPECT_CALL(item(index), GetInsecureDownloadStatus())
         .WillRepeatedly(
-            Return(download::DownloadItem::MixedContentStatus::SAFE));
+            Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
     int received_bytes =
         state == download::DownloadItem::IN_PROGRESS ? 50 : 100;
     EXPECT_CALL(item(index), GetReceivedBytes())
diff --git a/chrome/browser/download/bubble/download_display_controller_unittest.cc b/chrome/browser/download/bubble/download_display_controller_unittest.cc
index cab9e11..bf5d673 100644
--- a/chrome/browser/download/bubble/download_display_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_display_controller_unittest.cc
@@ -207,9 +207,9 @@
         .WillRepeatedly(ReturnRefOfCopy(target_file_path));
     EXPECT_CALL(item(index), GetLastReason())
         .WillRepeatedly(Return(download::DOWNLOAD_INTERRUPT_REASON_NONE));
-    EXPECT_CALL(item(index), GetMixedContentStatus())
+    EXPECT_CALL(item(index), GetInsecureDownloadStatus())
         .WillRepeatedly(
-            Return(download::DownloadItem::MixedContentStatus::SAFE));
+            Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
     if (state == DownloadState::IN_PROGRESS) {
       in_progress_count_++;
     }
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index 8e98bee5..3495cb1 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -40,7 +40,7 @@
 #include "chrome/browser/download/download_request_limiter.h"
 #include "chrome/browser/download/download_stats.h"
 #include "chrome/browser/download/download_target_determiner.h"
-#include "chrome/browser/download/mixed_content_download_blocking.h"
+#include "chrome/browser/download/insecure_download_blocking.h"
 #include "chrome/browser/download/save_package_file_picker.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
@@ -104,8 +104,8 @@
 #include "chrome/browser/download/android/download_open_source.h"
 #include "chrome/browser/download/android/download_utils.h"
 #include "chrome/browser/download/android/duplicate_download_dialog_bridge_delegate.h"
-#include "chrome/browser/download/android/mixed_content_download_dialog_bridge.h"
-#include "chrome/browser/download/android/mixed_content_download_infobar_delegate.h"
+#include "chrome/browser/download/android/insecure_download_dialog_bridge.h"
+#include "chrome/browser/download/android/insecure_download_infobar_delegate.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "components/infobars/content/content_infobar_manager.h"
 #include "net/http/http_content_disposition.h"
@@ -316,15 +316,15 @@
 
   std::move(callback).Run(
       target_info->target_path, target_info->target_disposition,
-      target_info->danger_type, target_info->mixed_content_status,
+      target_info->danger_type, target_info->insecure_download_status,
       target_info->intermediate_path, target_info->display_name,
       target_info->mime_type, target_info->result);
 }
 
 #if BUILDFLAG(IS_ANDROID)
-// Callback used by Mixed Download infobar on Android. Unlike on Desktop, this
-// infobar's entire life occurs prior to download start.
-void HandleMixedDownloadInfoBarResult(
+// Callback used by Insecure Download infobar on Android. Unlike on Desktop,
+// this infobar's entire life occurs prior to download start.
+void HandleInsecureDownloadInfoBarResult(
     download::DownloadItem* download_item,
     std::unique_ptr<DownloadTargetInfo> target_info,
     content::DownloadTargetCallback callback,
@@ -334,14 +334,14 @@
     std::move(callback).Run(target_info->target_path,
                             target_info->target_disposition,
                             download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                            DownloadItem::MixedContentStatus::SILENT_BLOCK,
+                            DownloadItem::InsecureDownloadStatus::SILENT_BLOCK,
                             target_info->intermediate_path,
                             target_info->display_name, target_info->mime_type,
                             download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED);
     return;
   }
-  target_info->mixed_content_status =
-      download::DownloadItem::MixedContentStatus::VALIDATED;
+  target_info->insecure_download_status =
+      download::DownloadItem::InsecureDownloadStatus::VALIDATED;
 
   // Otherwise, proceed as normal and check for a separate reservation with the
   // same target path. If such a reservation exists, cancel this reservation.
@@ -978,13 +978,13 @@
   return nullptr;
 }
 
-void ChromeDownloadManagerDelegate::GetMixedContentStatus(
+void ChromeDownloadManagerDelegate::GetInsecureDownloadStatus(
     download::DownloadItem* download,
     const base::FilePath& virtual_path,
-    GetMixedContentStatusCallback callback) {
+    GetInsecureDownloadStatusCallback callback) {
   DCHECK(download);
   std::move(callback).Run(
-      GetMixedContentStatusForDownload(profile_, virtual_path, download));
+      GetInsecureDownloadStatusForDownload(profile_, virtual_path, download));
 }
 
 void ChromeDownloadManagerDelegate::NotifyExtensions(
@@ -1534,22 +1534,22 @@
   base::FilePath target_path = target_info->target_path;
 
 #if BUILDFLAG(IS_ANDROID)
-  // Present a mixed content download infobar when needed, and wait to initiate
+  // Present an insecure download infobar when needed, and wait to initiate
   // the download until the user decides what to do.
   // On Desktop, this is handled using the unsafe-download warnings that are
   // shown in parallel with the download. Those warnings don't exist for
   // Android, so for simplicity we prompt before starting the download instead.
-  auto mcs = target_info->mixed_content_status;
+  auto ids = target_info->insecure_download_status;
   if (target_info->result == download::DOWNLOAD_INTERRUPT_REASON_NONE &&
-      (mcs == download::DownloadItem::MixedContentStatus::BLOCK ||
-       mcs == download::DownloadItem::MixedContentStatus::WARN)) {
+      (ids == download::DownloadItem::InsecureDownloadStatus::BLOCK ||
+       ids == download::DownloadItem::InsecureDownloadStatus::WARN)) {
     auto* web_contents = content::DownloadItemUtils::GetWebContents(item);
     gfx::NativeWindow native_window =
         web_contents ? web_contents->GetTopLevelNativeWindow() : nullptr;
     if (native_window && item) {
-      MixedContentDownloadDialogBridge::GetInstance()->CreateDialog(
+      InsecureDownloadDialogBridge::GetInstance()->CreateDialog(
           item, target_path.BaseName(), native_window,
-          base::BindOnce(HandleMixedDownloadInfoBarResult, item,
+          base::BindOnce(HandleInsecureDownloadInfoBarResult, item,
                          std::move(target_info), std::move(callback)));
       return;
     }
diff --git a/chrome/browser/download/chrome_download_manager_delegate.h b/chrome/browser/download/chrome_download_manager_delegate.h
index 618a870..39b411f5 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.h
+++ b/chrome/browser/download/chrome_download_manager_delegate.h
@@ -207,9 +207,10 @@
       DownloadTargetDeterminerDelegate::ConfirmationCallback callback);
 
   // DownloadTargetDeterminerDelegate. Protected for testing.
-  void GetMixedContentStatus(download::DownloadItem* download,
-                             const base::FilePath& virtual_path,
-                             GetMixedContentStatusCallback callback) override;
+  void GetInsecureDownloadStatus(
+      download::DownloadItem* download,
+      const base::FilePath& virtual_path,
+      GetInsecureDownloadStatusCallback callback) override;
   void NotifyExtensions(download::DownloadItem* download,
                         const base::FilePath& suggested_virtual_path,
                         NotifyExtensionsCallback callback) override;
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index ab3059c..7837db9 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -32,7 +32,7 @@
 #include "chrome/browser/download/download_item_model.h"
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/download/download_target_info.h"
-#include "chrome/browser/download/mixed_content_download_blocking.h"
+#include "chrome/browser/download/insecure_download_blocking.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/chrome_features.h"
@@ -122,8 +122,8 @@
       download::DownloadItem::TARGET_DISPOSITION_OVERWRITE;
   download::DownloadDangerType danger_type =
       download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
-  download::DownloadItem::MixedContentStatus mixed_content_status =
-      download::DownloadItem::MixedContentStatus::UNKNOWN;
+  download::DownloadItem::InsecureDownloadStatus insecure_download_status =
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN;
   base::FilePath intermediate_path;
   base::FilePath display_name;
   download::DownloadInterruptReason interrupt_reason =
@@ -315,7 +315,7 @@
   DownloadPrefs* download_prefs();
   PrefService* pref_service();
 
-  // Creates a mock download item as used by HTTP download blocking tests.
+  // Creates a mock download item as used by mixed download blocking tests.
   std::unique_ptr<download::MockDownloadItem>
   PrepareDownloadItemForMixedContent(
       const GURL& download_url,
@@ -330,7 +330,7 @@
       const base::FieldTrialParams& parameters,
       InsecureDownloadExtensions extension,
       download::DownloadInterruptReason interrupt_reason,
-      download::DownloadItem::MixedContentStatus mixed_content_status);
+      download::DownloadItem::InsecureDownloadStatus insecure_download_status);
 
  private:
   base::FilePath test_download_dir_;
@@ -445,7 +445,7 @@
     const base::FilePath& target_path,
     DownloadItem::TargetDisposition target_disposition,
     download::DownloadDangerType danger_type,
-    download::DownloadItem::MixedContentStatus mixed_content_status,
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status,
     const base::FilePath& intermediate_path,
     const base::FilePath& display_name,
     const std::string& mime_type,
@@ -453,7 +453,7 @@
   result->target_path = target_path;
   result->disposition = target_disposition;
   result->danger_type = danger_type;
-  result->mixed_content_status = mixed_content_status;
+  result->insecure_download_status = insecure_download_status;
   result->intermediate_path = intermediate_path;
   result->display_name = display_name;
   result->interrupt_reason = interrupt_reason;
@@ -556,14 +556,14 @@
 
 // Determine download target for |download_item| after enabling active content
 // download blocking with the |parameters| enabled. Verify |extension|,
-// |interrupt_reason| and |mixed_content_status|. Used by
+// |interrupt_reason| and |insecure_download_status|. Used by
 // BlockedAsActiveContent_ tests.
 void ChromeDownloadManagerDelegateTest::VerifyMixedContentExtensionOverride(
     DownloadItem* download_item,
     const base::FieldTrialParams& parameters,
     InsecureDownloadExtensions extension,
     download::DownloadInterruptReason interrupt_reason,
-    download::DownloadItem::MixedContentStatus mixed_content_status) {
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status) {
   DetermineDownloadTargetResult result;
   base::HistogramTester histograms;
   base::test::ScopedFeatureList feature_list;
@@ -574,7 +574,7 @@
   DetermineDownloadTarget(download_item, &result);
 
   EXPECT_EQ(interrupt_reason, result.interrupt_reason);
-  EXPECT_EQ(mixed_content_status, result.mixed_content_status);
+  EXPECT_EQ(insecure_download_status, result.insecure_download_status);
   histograms.ExpectUniqueSample(
       kInsecureDownloadHistogramName,
       InsecureDownloadSecurityStatus::kInitiatorSecureFileInsecure, 1);
@@ -882,8 +882,8 @@
 
   DetermineDownloadTarget(download_item.get(), &result);
 
-  EXPECT_EQ(download::DownloadItem::MixedContentStatus::BLOCK,
-            result.mixed_content_status);
+  EXPECT_EQ(download::DownloadItem::InsecureDownloadStatus::BLOCK,
+            result.insecure_download_status);
   histograms.ExpectUniqueSample(
       kInsecureDownloadHistogramName,
       InsecureDownloadSecurityStatus::kInitiatorInferredSecureFileInsecure, 1);
@@ -967,7 +967,7 @@
        {"SilentBlockExtensionList", "foo"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
 }
 
 // Verify that downloads ending in a blob URL are considered secure.
@@ -996,8 +996,8 @@
 
   DetermineDownloadTarget(download_item.get(), &result);
   EXPECT_EQ(download::DOWNLOAD_INTERRUPT_REASON_NONE, result.interrupt_reason);
-  EXPECT_EQ(download::DownloadItem::MixedContentStatus::SAFE,
-            result.mixed_content_status);
+  EXPECT_EQ(download::DownloadItem::InsecureDownloadStatus::SAFE,
+            result.insecure_download_status);
   histograms.ExpectUniqueSample(
       kInsecureDownloadHistogramName,
       InsecureDownloadSecurityStatus::kInitiatorSecureFileSecure, 1);
@@ -1025,7 +1025,7 @@
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(), {{}}, InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
-      download::DownloadItem::MixedContentStatus::SILENT_BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK);
 
   // An extension can punch through silent blocking if it's allowlisted.
   VerifyMixedContentExtensionOverride(
@@ -1034,7 +1034,7 @@
        {"TreatSilentBlockListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
 
   // And if that happens it can still be subject to other treatment.
   VerifyMixedContentExtensionOverride(
@@ -1045,7 +1045,7 @@
        {"TreatBlockListAsAllowlist", "false"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::BLOCK);
 
   // It's also possible to punch through silent blocking by swapping
   // configuration to a blocklist, but that's not expected to be needed again.
@@ -1055,7 +1055,7 @@
        {"TreatSilentBlockListAsAllowlist", "false"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
 }
 
 TEST_F(ChromeDownloadManagerDelegateTest, BlockedAsActiveContent_Warn) {
@@ -1077,7 +1077,7 @@
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(), {{}}, InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
-      download::DownloadItem::MixedContentStatus::SILENT_BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK);
 
   // This is true no matter what you do on the warn extension configuration.
   VerifyMixedContentExtensionOverride(
@@ -1085,13 +1085,13 @@
       {{"WarnExtensionList", "foo"}, {"TreatWarnListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
-      download::DownloadItem::MixedContentStatus::SILENT_BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK);
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(),
       {{"WarnExtensionList", "foo"}, {"TreatWarnListAsAllowlist", "false"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED,
-      download::DownloadItem::MixedContentStatus::SILENT_BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK);
 
   // To get to a warning, you need to disable other forms of blocking.
   // By default, carving out silent blocking will leave the extension as safe.
@@ -1101,7 +1101,7 @@
        {"TreatSilentBlockListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
   // But from there you can individually warn on specific extensions.
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(),
@@ -1111,7 +1111,7 @@
        {"TreatWarnListAsAllowlist", "false"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::WARN);
+      download::DownloadItem::InsecureDownloadStatus::WARN);
   // Or warn on everything.
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(),
@@ -1121,7 +1121,7 @@
        {"TreatWarnListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::WARN);
+      download::DownloadItem::InsecureDownloadStatus::WARN);
 }
 
 TEST_F(ChromeDownloadManagerDelegateTest, BlockedAsActiveContent_Block) {
@@ -1152,14 +1152,14 @@
        {"TreatBlockListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kMSExecutable,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::BLOCK);
   VerifyMixedContentExtensionOverride(
       foo_download_item.get(),
       {{"TreatSilentBlockListAsAllowlist", "false"},
        {"TreatBlockListAsAllowlist", "false"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
 
   // Test extensions selected via parameter are indeed blocked.
   VerifyMixedContentExtensionOverride(
@@ -1168,14 +1168,14 @@
        {"BlockExtensionList", "foo,bar"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::BLOCK);
   VerifyMixedContentExtensionOverride(
       bar_download_item.get(),
       {{"TreatSilentBlockListAsAllowlist", "false"},
        {"BlockExtensionList", "foo,bar"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::BLOCK);
 
   // Test that overriding extensions AND allowlisting work together.
   VerifyMixedContentExtensionOverride(
@@ -1185,7 +1185,7 @@
        {"TreatBlockListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
   VerifyMixedContentExtensionOverride(
       bar_download_item.get(),
       {{"TreatSilentBlockListAsAllowlist", "false"},
@@ -1193,7 +1193,7 @@
        {"TreatBlockListAsAllowlist", "true"}},
       InsecureDownloadExtensions::kUnknown,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::BLOCK);
+      download::DownloadItem::InsecureDownloadStatus::BLOCK);
 }
 
 // MIXEDSCRIPT content setting only applies to Desktop.
@@ -1232,17 +1232,17 @@
   VerifyMixedContentExtensionOverride(
       warned_download_item.get(), {{}}, InsecureDownloadExtensions::kTest,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
   VerifyMixedContentExtensionOverride(
       blocked_download_item.get(), {{}},
       InsecureDownloadExtensions::kMSExecutable,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
   VerifyMixedContentExtensionOverride(
       silent_blocked_download_item.get(), {{}},
       InsecureDownloadExtensions::kTest,
       download::DOWNLOAD_INTERRUPT_REASON_NONE,
-      download::DownloadItem::MixedContentStatus::SAFE);
+      download::DownloadItem::InsecureDownloadStatus::SAFE);
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
diff --git a/chrome/browser/download/download_commands.h b/chrome/browser/download/download_commands.h
index 6389f62..37412bf 100644
--- a/chrome/browser/download/download_commands.h
+++ b/chrome/browser/download/download_commands.h
@@ -30,13 +30,13 @@
     DISCARD,              // Discard the malicious download.
     KEEP,                 // Keep the malicious download.
     LEARN_MORE_SCANNING,  // Show information about download scanning.
-    LEARN_MORE_INTERRUPTED,    // Show information about interrupted downloads.
-    LEARN_MORE_MIXED_CONTENT,  // Show info about mixed content downloads.
-    COPY_TO_CLIPBOARD,         // Copy the contents to the clipboard.
-    DEEP_SCAN,                 // Send file to Safe Browsing for deep scanning.
-    BYPASS_DEEP_SCANNING,      // Bypass the prompt to deep scan.
-    REVIEW,                    // Show enterprise download review dialog.
-    RETRY,                     // Retry the download.
+    LEARN_MORE_INTERRUPTED,  // Show information about interrupted downloads.
+    LEARN_MORE_INSECURE_DOWNLOAD,  // Show info about insecure downloads.
+    COPY_TO_CLIPBOARD,             // Copy the contents to the clipboard.
+    DEEP_SCAN,             // Send file to Safe Browsing for deep scanning.
+    BYPASS_DEEP_SCANNING,  // Bypass the prompt to deep scan.
+    REVIEW,                // Show enterprise download review dialog.
+    RETRY,                 // Retry the download.
     MAX
   };
 
diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc
index 40b1542..132165a 100644
--- a/chrome/browser/download/download_item_model.cc
+++ b/chrome/browser/download/download_item_model.cc
@@ -66,7 +66,7 @@
 #endif
 
 using download::DownloadItem;
-using MixedContentStatus = download::DownloadItem::MixedContentStatus;
+using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus;
 using safe_browsing::DownloadFileType;
 using ReportThreatDetailsResult =
     safe_browsing::PingManager::ReportThreatDetailsResult;
@@ -210,7 +210,7 @@
 bool MaybeSubmitDownloadToFeedbackService(DownloadCommands::Command command,
                                           Profile* profile,
                                           download::DownloadItem* download) {
-  if (!download->IsDangerous() || download->IsMixedContent()) {
+  if (!download->IsDangerous() || download->IsInsecure()) {
     return false;
   }
   if (!safe_browsing::DownloadFeedbackService::IsEnabledForDownload(
@@ -393,8 +393,8 @@
   return false;
 }
 
-bool DownloadItemModel::IsMixedContent() const {
-  return download_->IsMixedContent();
+bool DownloadItemModel::IsInsecure() const {
+  return download_->IsInsecure();
 }
 
 bool DownloadItemModel::ShouldRemoveFromShelfWhenComplete() const {
@@ -546,9 +546,9 @@
   data->danger_level_ = danger_level;
 }
 
-download::DownloadItem::MixedContentStatus
-DownloadItemModel::GetMixedContentStatus() const {
-  return download_->GetMixedContentStatus();
+download::DownloadItem::InsecureDownloadStatus
+DownloadItemModel::GetInsecureDownloadStatus() const {
+  return download_->GetInsecureDownloadStatus();
 }
 
 bool DownloadItemModel::IsBeingRevived() const {
@@ -778,7 +778,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
     case DownloadCommands::REVIEW:
@@ -818,7 +818,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::COPY_TO_CLIPBOARD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
@@ -870,8 +870,8 @@
         MaybeSubmitDownloadToFeedbackService(command, profile(), download_);
       }
 #endif
-      if (IsMixedContent()) {
-        download_->ValidateMixedContentDownload();
+      if (IsInsecure()) {
+        download_->ValidateInsecureDownload();
         break;
       }
       if (GetDangerType() == download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING) {
@@ -918,7 +918,7 @@
     case DownloadCommands::PLATFORM_OPEN:
     case DownloadCommands::CANCEL:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::PAUSE:
     case DownloadCommands::RESUME:
     case DownloadCommands::COPY_TO_CLIPBOARD:
@@ -1068,8 +1068,8 @@
   bool should_notify =
       download_->GetLastReason() ==
           download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED &&
-      download_->GetMixedContentStatus() !=
-          download::DownloadItem::MixedContentStatus::SILENT_BLOCK;
+      download_->GetInsecureDownloadStatus() !=
+          download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK;
 
   // Wait until the target path is determined.
   if (download_->GetTargetFilePath().empty() && !should_notify) {
@@ -1103,14 +1103,14 @@
     return false;
   }
 
-  switch (GetMixedContentStatus()) {
-    case download::DownloadItem::MixedContentStatus::BLOCK:
-    case download::DownloadItem::MixedContentStatus::WARN:
+  switch (GetInsecureDownloadStatus()) {
+    case download::DownloadItem::InsecureDownloadStatus::BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::WARN:
       return true;
-    case download::DownloadItem::MixedContentStatus::UNKNOWN:
-    case download::DownloadItem::MixedContentStatus::SAFE:
-    case download::DownloadItem::MixedContentStatus::VALIDATED:
-    case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+    case download::DownloadItem::InsecureDownloadStatus::SAFE:
+    case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+    case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
 
diff --git a/chrome/browser/download/download_item_model.h b/chrome/browser/download/download_item_model.h
index c6facc5..1a58fe8b 100644
--- a/chrome/browser/download/download_item_model.h
+++ b/chrome/browser/download/download_item_model.h
@@ -56,7 +56,7 @@
   bool IsDangerous() const override;
   bool MightBeMalicious() const override;
   bool IsMalicious() const override;
-  bool IsMixedContent() const override;
+  bool IsInsecure() const override;
   bool ShouldRemoveFromShelfWhenComplete() const override;
   bool ShouldShowDownloadStartedAnimation() const override;
   bool ShouldShowInShelf() const override;
@@ -75,7 +75,7 @@
   safe_browsing::DownloadFileType::DangerLevel GetDangerLevel() const override;
   void SetDangerLevel(
       safe_browsing::DownloadFileType::DangerLevel danger_level) override;
-  download::DownloadItem::MixedContentStatus GetMixedContentStatus()
+  download::DownloadItem::InsecureDownloadStatus GetInsecureDownloadStatus()
       const override;
   void OpenUsingPlatformHandler() override;
   bool IsBeingRevived() const override;
diff --git a/chrome/browser/download/download_item_model_unittest.cc b/chrome/browser/download/download_item_model_unittest.cc
index 351b382..05a3f6d 100644
--- a/chrome/browser/download/download_item_model_unittest.cc
+++ b/chrome/browser/download/download_item_model_unittest.cc
@@ -208,9 +208,9 @@
             Return(DownloadItem::TARGET_DISPOSITION_OVERWRITE));
     ON_CALL(item_, IsPaused()).WillByDefault(Return(false));
     ON_CALL(item_, CanResume()).WillByDefault(Return(false));
-    ON_CALL(item_, GetMixedContentStatus())
+    ON_CALL(item_, GetInsecureDownloadStatus())
         .WillByDefault(
-            Return(download::DownloadItem::MixedContentStatus::SAFE));
+            Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
     ON_CALL(item(), GetDangerType())
         .WillByDefault(Return(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
     content::DownloadItemUtils::AttachInfoForTesting(&(item()), profile_,
@@ -751,18 +751,18 @@
   SetupCompletedDownloadItem(base::Hours(1));
   SetStatusTextBuilder(/*for_bubble=*/true);
 
-  const struct MixedContentStatusTestCase {
-    download::DownloadItem::MixedContentStatus mixed_content_status;
+  const struct InsecureDownloadStatusTestCase {
+    download::DownloadItem::InsecureDownloadStatus mixed_content_status;
     std::string expected_bubble_status_msg;
-  } kMixedContentStatusTestCases[] = {
-      {download::DownloadItem::MixedContentStatus::BLOCK,
+  } kInsecureDownloadStatusTestCases[] = {
+      {download::DownloadItem::InsecureDownloadStatus::BLOCK,
        "Blocked \xE2\x80\xA2 Insecure download"},
-      {download::DownloadItem::MixedContentStatus::WARN,
+      {download::DownloadItem::InsecureDownloadStatus::WARN,
        "Blocked \xE2\x80\xA2 Insecure download"},
   };
-  for (const auto& test_case : kMixedContentStatusTestCases) {
+  for (const auto& test_case : kInsecureDownloadStatusTestCases) {
     SetupDownloadItemDefaults();
-    ON_CALL(item(), GetMixedContentStatus())
+    ON_CALL(item(), GetInsecureDownloadStatus())
         .WillByDefault(Return(test_case.mixed_content_status));
     EXPECT_EQ(base::UTF16ToUTF8(model().GetStatusText()),
               test_case.expected_bubble_status_msg);
diff --git a/chrome/browser/download/download_shelf_context_menu.cc b/chrome/browser/download/download_shelf_context_menu.cc
index de61e908..a97225f 100644
--- a/chrome/browser/download/download_shelf_context_menu.cc
+++ b/chrome/browser/download/download_shelf_context_menu.cc
@@ -24,12 +24,12 @@
 #include "chrome/browser/ui/pdf/adobe_reader_info_win.h"
 #endif
 
-using MixedContentStatus = download::DownloadItem::MixedContentStatus;
+using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus;
 
 bool DownloadShelfContextMenu::WantsContextMenu(
     DownloadUIModel* download_model) {
   return !download_model->IsDangerous() || download_model->MightBeMalicious() ||
-         download_model->IsMixedContent();
+         download_model->IsInsecure();
 }
 
 DownloadShelfContextMenu::~DownloadShelfContextMenu() {
@@ -76,8 +76,8 @@
 
   bool is_download = download_->GetDownloadItem() != nullptr;
 
-  if (download_->IsMixedContent()) {
-    model = GetMixedContentDownloadMenuModel();
+  if (download_->IsInsecure()) {
+    model = GetInsecureDownloadMenuModel();
   } else if (ChromeDownloadManagerDelegate::IsDangerTypeBlocked(
                  download_->GetDangerType())) {
     model = GetInterruptedMenuModel(is_download);
@@ -191,8 +191,8 @@
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
       id = IDS_DOWNLOAD_MENU_LEARN_MORE_INTERRUPTED;
       break;
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
-      id = IDS_DOWNLOAD_MENU_LEARN_MORE_MIXED_CONTENT;
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
+      id = IDS_DOWNLOAD_MENU_LEARN_MORE_INSECURE;
       break;
     case DownloadCommands::COPY_TO_CLIPBOARD:
       // This command is implemented only for the Download notification.
@@ -414,29 +414,27 @@
   return deep_scanning_menu_model_.get();
 }
 
-ui::SimpleMenuModel*
-DownloadShelfContextMenu::GetMixedContentDownloadMenuModel() {
-  if (mixed_content_download_menu_model_)
-    return mixed_content_download_menu_model_.get();
+ui::SimpleMenuModel* DownloadShelfContextMenu::GetInsecureDownloadMenuModel() {
+  if (insecure_download_menu_model_)
+    return insecure_download_menu_model_.get();
 
-  mixed_content_download_menu_model_ =
-      std::make_unique<ui::SimpleMenuModel>(this);
+  insecure_download_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
 
-  if (download_->GetMixedContentStatus() == MixedContentStatus::WARN) {
-    mixed_content_download_menu_model_->AddItem(
+  if (download_->GetInsecureDownloadStatus() == InsecureDownloadStatus::WARN) {
+    insecure_download_menu_model_->AddItem(
         DownloadCommands::DISCARD,
         GetLabelForCommandId(DownloadCommands::DISCARD));
   } else {
-    mixed_content_download_menu_model_->AddItem(
+    insecure_download_menu_model_->AddItem(
         DownloadCommands::KEEP, GetLabelForCommandId(DownloadCommands::KEEP));
   }
 
-  mixed_content_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
-  mixed_content_download_menu_model_->AddItem(
-      DownloadCommands::LEARN_MORE_MIXED_CONTENT,
-      GetLabelForCommandId(DownloadCommands::LEARN_MORE_MIXED_CONTENT));
+  insecure_download_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
+  insecure_download_menu_model_->AddItem(
+      DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD,
+      GetLabelForCommandId(DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD));
 
-  return mixed_content_download_menu_model_.get();
+  return insecure_download_menu_model_.get();
 }
 
 void DownloadShelfContextMenu::AddAutoOpenToMenu(ui::SimpleMenuModel* menu) {
diff --git a/chrome/browser/download/download_shelf_context_menu.h b/chrome/browser/download/download_shelf_context_menu.h
index 4796720..311f0c3 100644
--- a/chrome/browser/download/download_shelf_context_menu.h
+++ b/chrome/browser/download/download_shelf_context_menu.h
@@ -68,7 +68,7 @@
   ui::SimpleMenuModel* GetMaybeMaliciousMenuModel(bool is_download);
   ui::SimpleMenuModel* GetMaliciousMenuModel(bool is_download);
   ui::SimpleMenuModel* GetDeepScanningMenuModel(bool is_download);
-  ui::SimpleMenuModel* GetMixedContentDownloadMenuModel();
+  ui::SimpleMenuModel* GetInsecureDownloadMenuModel();
 
   void AddAutoOpenToMenu(ui::SimpleMenuModel* model);
 
@@ -83,7 +83,7 @@
   std::unique_ptr<ui::SimpleMenuModel> maybe_malicious_download_menu_model_;
   std::unique_ptr<ui::SimpleMenuModel> malicious_download_menu_model_;
   std::unique_ptr<ui::SimpleMenuModel> deep_scanning_menu_model_;
-  std::unique_ptr<ui::SimpleMenuModel> mixed_content_download_menu_model_;
+  std::unique_ptr<ui::SimpleMenuModel> insecure_download_menu_model_;
 
   // Whether or not a histogram has been emitted recording which
   // Download commands were enabled
diff --git a/chrome/browser/download/download_shelf_context_menu_unittest.cc b/chrome/browser/download/download_shelf_context_menu_unittest.cc
index fb642d1e..8d543a81 100644
--- a/chrome/browser/download/download_shelf_context_menu_unittest.cc
+++ b/chrome/browser/download/download_shelf_context_menu_unittest.cc
@@ -37,7 +37,7 @@
       std::make_unique<NiceMock<download::MockDownloadItem>>();
   auto download_ui_model = DownloadItemModel::Wrap(item.get());
   auto download_weak_ptr = download_ui_model->GetWeakPtr();
-  EXPECT_CALL(*item, IsMixedContent()).WillRepeatedly(Return(true));
+  EXPECT_CALL(*item, IsInsecure()).WillRepeatedly(Return(true));
   EXPECT_CALL(*item, IsPaused()).WillRepeatedly(Return(true));
   // 1 out of 2 commands should be executed.
   EXPECT_CALL(*item, OpenDownload()).Times(1);
@@ -64,7 +64,7 @@
       std::make_unique<NiceMock<download::MockDownloadItem>>();
   auto download_ui_model = DownloadItemModel::Wrap(item.get());
   auto download_weak_ptr = download_ui_model->GetWeakPtr();
-  EXPECT_CALL(*item, IsMixedContent()).WillRepeatedly(Return(true));
+  EXPECT_CALL(*item, IsInsecure()).WillRepeatedly(Return(true));
   EXPECT_CALL(*item, IsPaused()).WillRepeatedly(Return(true));
 
   MakeContextMenu(download_ui_model.get());
@@ -77,6 +77,6 @@
       DownloadShelfContextMenuAction::kKeepEnabled, 1);
   histogram_tester.ExpectBucketCount(
       "Download.ShelfContextMenuAction",
-      DownloadShelfContextMenuAction::kLearnMoreMixedContentEnabled, 1);
+      DownloadShelfContextMenuAction::kLearnMoreInsecureDownloadEnabled, 1);
   histogram_tester.ExpectTotalCount("Download.ShelfContextMenuAction", 2);
 }
diff --git a/chrome/browser/download/download_stats.cc b/chrome/browser/download/download_stats.cc
index 330c4862..54b42690 100644
--- a/chrome/browser/download/download_stats.cc
+++ b/chrome/browser/download/download_stats.cc
@@ -172,11 +172,11 @@
       return clicked
                  ? DownloadShelfContextMenuAction::kLearnMoreInterruptedClicked
                  : DownloadShelfContextMenuAction::kLearnMoreInterruptedEnabled;
-    case DownloadCommands::Command::LEARN_MORE_MIXED_CONTENT:
-      return clicked
-                 ? DownloadShelfContextMenuAction::kLearnMoreMixedContentClicked
-                 : DownloadShelfContextMenuAction::
-                       kLearnMoreMixedContentEnabled;
+    case DownloadCommands::Command::LEARN_MORE_INSECURE_DOWNLOAD:
+      return clicked ? DownloadShelfContextMenuAction::
+                           kLearnMoreInsecureDownloadClicked
+                     : DownloadShelfContextMenuAction::
+                           kLearnMoreInsecureDownloadEnabled;
     case DownloadCommands::Command::COPY_TO_CLIPBOARD:
       return clicked ? DownloadShelfContextMenuAction::kCopyToClipboardClicked
                      : DownloadShelfContextMenuAction::kCopyToClipboardEnabled;
diff --git a/chrome/browser/download/download_stats.h b/chrome/browser/download/download_stats.h
index 77feec3..1bceb9b6 100644
--- a/chrome/browser/download/download_stats.h
+++ b/chrome/browser/download/download_stats.h
@@ -106,8 +106,8 @@
   kTargetConfirmationResult = 1,
   // Canceled due to no valid virtual path.
   kNoValidPath = 2,
-  // Canceled due to no mixed content.
-  kMixedContent = 3,
+  // Canceled due to no insecure download.
+  kInsecureDownload = 3,
   // Canceled due to failed path reservacation.
   kFailedPathReservation = 4,
   // Canceled due to empty local path.
@@ -215,8 +215,8 @@
   kLearnMoreScanningClicked = 21,
   kLearnMoreInterruptedEnabled = 22,
   kLearnMoreInterruptedClicked = 23,
-  kLearnMoreMixedContentEnabled = 24,
-  kLearnMoreMixedContentClicked = 25,
+  kLearnMoreInsecureDownloadEnabled = 24,
+  kLearnMoreInsecureDownloadClicked = 25,
   kCopyToClipboardEnabled = 26,
   kCopyToClipboardClicked = 27,
   // kAnnotateEnabled = 28,
diff --git a/chrome/browser/download/download_target_determiner.cc b/chrome/browser/download/download_target_determiner.cc
index e55d006..6e2ece9 100644
--- a/chrome/browser/download/download_target_determiner.cc
+++ b/chrome/browser/download/download_target_determiner.cc
@@ -168,8 +168,8 @@
       case STATE_GENERATE_TARGET_PATH:
         result = DoGenerateTargetPath();
         break;
-      case STATE_SET_MIXED_CONTENT_STATUS:
-        result = DoSetMixedContentStatus();
+      case STATE_SET_INSECURE_DOWNLOAD_STATUS:
+        result = DoSetInsecureDownloadStatus();
         break;
       case STATE_NOTIFY_EXTENSIONS:
         result = DoNotifyExtensions();
@@ -222,7 +222,7 @@
   DCHECK(!should_notify_extensions_);
   bool is_forced_path = !download_->GetForcedFilePath().empty();
 
-  next_state_ = STATE_SET_MIXED_CONTENT_STATUS;
+  next_state_ = STATE_SET_INSECURE_DOWNLOAD_STATUS;
 
   // Transient download should use the existing path.
   if (download_->IsTransient()) {
@@ -357,30 +357,30 @@
 }
 
 DownloadTargetDeterminer::Result
-DownloadTargetDeterminer::DoSetMixedContentStatus() {
+DownloadTargetDeterminer::DoSetInsecureDownloadStatus() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!virtual_path_.empty());
 
   next_state_ = STATE_NOTIFY_EXTENSIONS;
 
-  delegate_->GetMixedContentStatus(
+  delegate_->GetInsecureDownloadStatus(
       download_, virtual_path_,
-      base::BindOnce(&DownloadTargetDeterminer::GetMixedContentStatusDone,
+      base::BindOnce(&DownloadTargetDeterminer::GetInsecureDownloadStatusDone,
                      weak_ptr_factory_.GetWeakPtr()));
   return QUIT_DOLOOP;
 }
 
-void DownloadTargetDeterminer::GetMixedContentStatusDone(
-    download::DownloadItem::MixedContentStatus status) {
+void DownloadTargetDeterminer::GetInsecureDownloadStatusDone(
+    download::DownloadItem::InsecureDownloadStatus status) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // Delegate should not call back here more than once.
   DCHECK_EQ(STATE_NOTIFY_EXTENSIONS, next_state_);
 
-  mixed_content_status_ = status;
+  insecure_download_status_ = status;
 
-  if (status == download::DownloadItem::MixedContentStatus::SILENT_BLOCK) {
-    RecordDownloadCancelReason(DownloadCancelReason::kMixedContent);
+  if (status == download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK) {
+    RecordDownloadCancelReason(DownloadCancelReason::kInsecureDownload);
     ScheduleCallbackAndDeleteSelf(
         download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED);
     return;
@@ -1121,7 +1121,7 @@
   target_info->intermediate_path = intermediate_path_;
   target_info->mime_type = mime_type_;
   target_info->is_filetype_handled_safely = is_filetype_handled_safely_;
-  target_info->mixed_content_status = mixed_content_status_;
+  target_info->insecure_download_status = insecure_download_status_;
 #if BUILDFLAG(IS_ANDROID)
   // If |virtual_path_| is content URI, there is no need to prompt the user.
   if (local_path_.IsContentUri() && !virtual_path_.IsContentUri()) {
diff --git a/chrome/browser/download/download_target_determiner.h b/chrome/browser/download/download_target_determiner.h
index a24a4654..5850d8f9 100644
--- a/chrome/browser/download/download_target_determiner.h
+++ b/chrome/browser/download/download_target_determiner.h
@@ -116,7 +116,7 @@
   // handler returns COMPLETE.
   enum State {
     STATE_GENERATE_TARGET_PATH,
-    STATE_SET_MIXED_CONTENT_STATUS,
+    STATE_SET_INSECURE_DOWNLOAD_STATUS,
     STATE_NOTIFY_EXTENSIONS,
     STATE_RESERVE_VIRTUAL_PATH,
     STATE_PROMPT_USER_FOR_DOWNLOAD_PATH,
@@ -178,21 +178,21 @@
   // the download item.
   // Next state:
   // - STATE_NONE : If the download is not in progress, returns COMPLETE.
-  // - STATE_SET_MIXED_CONTENT_STATUS : All other downloads.
+  // - STATE_SET_INSECURE_DOWNLOAD_STATUS : All other downloads.
   Result DoGenerateTargetPath();
 
-  // Determines the mixed content status of the download, so as to block it
+  // Determines the insecure download status of the download, so as to block it
   // prior to prompting the user for the file path.  This function relies on the
   // delegate for the actual determination.
   //
   // Next state:
   // - STATE_NOTIFY_EXTENSIONS
-  Result DoSetMixedContentStatus();
+  Result DoSetInsecureDownloadStatus();
 
-  // Callback invoked by delegate after mixed content status is determined.
+  // Callback invoked by delegate after insecure download status is determined.
   // Cancels the download if status indicates blocking is necessary.
-  void GetMixedContentStatusDone(
-      download::DownloadItem::MixedContentStatus status);
+  void GetInsecureDownloadStatusDone(
+      download::DownloadItem::InsecureDownloadStatus status);
 
   // Notifies downloads extensions. If any extension wishes to override the
   // download filename, it will respond to the OnDeterminingFilename()
@@ -378,8 +378,8 @@
   base::FilePath local_path_;
   base::FilePath intermediate_path_;
   std::string mime_type_;
-  bool is_filetype_handled_safely_;
-  download::DownloadItem::MixedContentStatus mixed_content_status_;
+  bool is_filetype_handled_safely_ = false;
+  download::DownloadItem::InsecureDownloadStatus insecure_download_status_;
 #if BUILDFLAG(IS_ANDROID)
   bool is_checking_dialog_confirmed_path_;
 #endif
diff --git a/chrome/browser/download/download_target_determiner_delegate.h b/chrome/browser/download/download_target_determiner_delegate.h
index b948b68a..14add84 100644
--- a/chrome/browser/download/download_target_determiner_delegate.h
+++ b/chrome/browser/download/download_target_determiner_delegate.h
@@ -24,10 +24,10 @@
 // DownloadTargetDeterminer and is expected to outlive it.
 class DownloadTargetDeterminerDelegate {
  public:
-  // Callback to be invoked after GetMixedContentStatus() completes. The
+  // Callback to be invoked after GetInsecureDownloadStatus() completes. The
   // |should_block| bool represents whether the download should be aborted.
-  using GetMixedContentStatusCallback = base::OnceCallback<void(
-      download::DownloadItem::MixedContentStatus status)>;
+  using GetInsecureDownloadStatusCallback = base::OnceCallback<void(
+      download::DownloadItem::InsecureDownloadStatus status)>;
 
   // Callback to be invoked after NotifyExtensions() completes. The
   // |new_virtual_path| should be set to a new path if an extension wishes to
@@ -72,12 +72,12 @@
   // determined, it should be set to the empty string.
   typedef base::OnceCallback<void(const std::string&)> GetFileMimeTypeCallback;
 
-  // Returns whether the download should be warned/blocked based on its mixed
-  // content status, and if so, what kind of warning/blocking should be used.
-  virtual void GetMixedContentStatus(
+  // Returns whether the download should be warned/blocked based on its insecure
+  // download status, and if so, what kind of warning/blocking should be used.
+  virtual void GetInsecureDownloadStatus(
       download::DownloadItem* download,
       const base::FilePath& virtual_path,
-      GetMixedContentStatusCallback callback) = 0;
+      GetInsecureDownloadStatusCallback callback) = 0;
 
   // Notifies extensions of the impending filename determination. |virtual_path|
   // is the current suggested virtual path. The |callback| should be invoked to
diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc
index 7154f20a..976fdce5 100644
--- a/chrome/browser/download/download_target_determiner_unittest.cc
+++ b/chrome/browser/download/download_target_determiner_unittest.cc
@@ -189,15 +189,16 @@
 class MockDownloadTargetDeterminerDelegate
     : public DownloadTargetDeterminerDelegate {
  public:
-  void GetMixedContentStatus(download::DownloadItem* item,
-                             const base::FilePath& path,
-                             GetMixedContentStatusCallback cb) override {
-    GetMixedContentStatus_(item, path, cb);
+  void GetInsecureDownloadStatus(
+      download::DownloadItem* item,
+      const base::FilePath& path,
+      GetInsecureDownloadStatusCallback cb) override {
+    GetInsecureDownloadStatus_(item, path, cb);
   }
-  MOCK_METHOD3(GetMixedContentStatus_,
+  MOCK_METHOD3(GetInsecureDownloadStatus_,
                void(download::DownloadItem*,
                     const base::FilePath&,
-                    GetMixedContentStatusCallback&));
+                    GetInsecureDownloadStatusCallback&));
   void CheckDownloadUrl(download::DownloadItem* item,
                         const base::FilePath& path,
                         CheckDownloadUrlCallback cb) override {
@@ -266,9 +267,9 @@
                void(const base::FilePath&, GetFileMimeTypeCallback&));
 
   void SetupDefaults() {
-    ON_CALL(*this, GetMixedContentStatus_(_, _, _))
+    ON_CALL(*this, GetInsecureDownloadStatus_(_, _, _))
         .WillByDefault(WithArg<2>(ScheduleCallback(
-            download::DownloadItem::MixedContentStatus::UNKNOWN)));
+            download::DownloadItem::InsecureDownloadStatus::UNKNOWN)));
     ON_CALL(*this, CheckDownloadUrl_(_, _, _))
         .WillByDefault(WithArg<2>(
             ScheduleCallback(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)));
@@ -1627,7 +1628,7 @@
                              std::size(kManagedPathTestCases));
 }
 
-// Test basic blocking functionality via GetMixedContentStatus.
+// Test basic blocking functionality via GetInsecureDownloadStatus.
 TEST_F(DownloadTargetDeterminerTest, BlockDownloads) {
   const DownloadTestCase kBlockDownloadsTestCases[] = {
       {AUTOMATIC, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
@@ -1636,9 +1637,9 @@
        DownloadItem::TARGET_DISPOSITION_OVERWRITE, EXPECT_EMPTY},
   };
 
-  ON_CALL(*delegate(), GetMixedContentStatus_(_, _, _))
+  ON_CALL(*delegate(), GetInsecureDownloadStatus_(_, _, _))
       .WillByDefault(WithArg<2>(ScheduleCallback(
-          download::DownloadItem::MixedContentStatus::SILENT_BLOCK)));
+          download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK)));
   RunTestCasesWithActiveItem(kBlockDownloadsTestCases,
                              std::size(kBlockDownloadsTestCases));
 }
diff --git a/chrome/browser/download/download_target_info.cc b/chrome/browser/download/download_target_info.cc
index ad088f4..ea98fcc 100644
--- a/chrome/browser/download/download_target_info.cc
+++ b/chrome/browser/download/download_target_info.cc
@@ -11,8 +11,6 @@
       danger_type(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
       danger_level(safe_browsing::DownloadFileType::NOT_DANGEROUS),
       is_filetype_handled_safely(false),
-      result(download::DOWNLOAD_INTERRUPT_REASON_NONE),
-      mixed_content_status(
-          download::DownloadItem::MixedContentStatus::UNKNOWN) {}
+      result(download::DOWNLOAD_INTERRUPT_REASON_NONE) {}
 
 DownloadTargetInfo::~DownloadTargetInfo() {}
diff --git a/chrome/browser/download/download_target_info.h b/chrome/browser/download/download_target_info.h
index 41e568f..70a7969 100644
--- a/chrome/browser/download/download_target_info.h
+++ b/chrome/browser/download/download_target_info.h
@@ -79,8 +79,9 @@
   // Result of the download target determination.
   download::DownloadInterruptReason result;
 
-  // What sort of blocking should be used if the download is of mixed content.
-  download::DownloadItem::MixedContentStatus mixed_content_status;
+  // What sort of blocking should be used if the download is insecure.
+  download::DownloadItem::InsecureDownloadStatus insecure_download_status = 
+          download::DownloadItem::InsecureDownloadStatus::UNKNOWN;
 
   // Display name of the file.
   base::FilePath display_name;
diff --git a/chrome/browser/download/download_ui_controller.cc b/chrome/browser/download/download_ui_controller.cc
index 76ce14c..247a200 100644
--- a/chrome/browser/download/download_ui_controller.cc
+++ b/chrome/browser/download/download_ui_controller.cc
@@ -278,8 +278,8 @@
   bool should_notify =
       item->GetLastReason() ==
           download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED &&
-      item->GetMixedContentStatus() !=
-          download::DownloadItem::MixedContentStatus::SILENT_BLOCK;
+      item->GetInsecureDownloadStatus() !=
+          download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK;
 
   // Wait until the target path is determined or the download is canceled.
   if (item->GetTargetFilePath().empty() &&
diff --git a/chrome/browser/download/download_ui_controller_unittest.cc b/chrome/browser/download/download_ui_controller_unittest.cc
index a856974b..793ba4b 100644
--- a/chrome/browser/download/download_ui_controller_unittest.cc
+++ b/chrome/browser/download/download_ui_controller_unittest.cc
@@ -320,16 +320,17 @@
       .WillRepeatedly(Return(download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED));
 
   // If the download is a silently blocked mixed content download, don't notify.
-  EXPECT_CALL(*item, GetMixedContentStatus())
+  EXPECT_CALL(*item, GetInsecureDownloadStatus())
       .WillRepeatedly(
-          Return(download::DownloadItem::MixedContentStatus::SILENT_BLOCK));
+          Return(download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK));
   ASSERT_TRUE(manager_observer());
   manager_observer()->OnDownloadCreated(manager(), item.get());
   EXPECT_FALSE(notified_item());
 
   // Notify even though the destination hasn't been determined yet.
-  EXPECT_CALL(*item, GetMixedContentStatus())
-      .WillRepeatedly(Return(download::DownloadItem::MixedContentStatus::SAFE));
+  EXPECT_CALL(*item, GetInsecureDownloadStatus())
+      .WillRepeatedly(
+          Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
   item->NotifyObserversDownloadUpdated();
   EXPECT_EQ(static_cast<download::DownloadItem*>(item.get()), notified_item());
 }
diff --git a/chrome/browser/download/download_ui_model.cc b/chrome/browser/download/download_ui_model.cc
index 429fbed6..123b67a 100644
--- a/chrome/browser/download/download_ui_model.cc
+++ b/chrome/browser/download/download_ui_model.cc
@@ -334,17 +334,17 @@
       break;
   }
 
-  switch (GetMixedContentStatus()) {
-    case download::DownloadItem::MixedContentStatus::BLOCK:
-      return l10n_util::GetStringFUTF16(
-          IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED, filename, offset);
-    case download::DownloadItem::MixedContentStatus::WARN:
-      return l10n_util::GetStringFUTF16(
-          IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_WARNING, filename, offset);
-    case download::DownloadItem::MixedContentStatus::UNKNOWN:
-    case download::DownloadItem::MixedContentStatus::SAFE:
-    case download::DownloadItem::MixedContentStatus::VALIDATED:
-    case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+  switch (GetInsecureDownloadStatus()) {
+    case download::DownloadItem::InsecureDownloadStatus::BLOCK:
+      return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED,
+                                        filename, offset);
+    case download::DownloadItem::InsecureDownloadStatus::WARN:
+      return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_INSECURE_WARNING,
+                                        filename, offset);
+    case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+    case download::DownloadItem::InsecureDownloadStatus::SAFE:
+    case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+    case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
 
@@ -407,7 +407,7 @@
   return false;
 }
 
-bool DownloadUIModel::IsMixedContent() const {
+bool DownloadUIModel::IsInsecure() const {
   return false;
 }
 
@@ -468,9 +468,9 @@
 void DownloadUIModel::SetDangerLevel(
     DownloadFileType::DangerLevel danger_level) {}
 
-download::DownloadItem::MixedContentStatus
-DownloadUIModel::GetMixedContentStatus() const {
-  return download::DownloadItem::MixedContentStatus::UNKNOWN;
+download::DownloadItem::InsecureDownloadStatus
+DownloadUIModel::GetInsecureDownloadStatus() const {
+  return download::DownloadItem::InsecureDownloadStatus::UNKNOWN;
 }
 
 void DownloadUIModel::OpenUsingPlatformHandler() {}
@@ -623,7 +623,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
     case DownloadCommands::REVIEW:
@@ -653,7 +653,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::COPY_TO_CLIPBOARD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
@@ -692,9 +692,9 @@
           content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
           ui::PAGE_TRANSITION_LINK, false));
       break;
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
       download_commands->GetBrowser()->OpenURL(content::OpenURLParams(
-          GURL(chrome::kMixedContentDownloadBlockingLearnMoreUrl),
+          GURL(chrome::kInsecureDownloadBlockingLearnMoreUrl),
           content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
           ui::PAGE_TRANSITION_LINK, false));
       break;
@@ -908,9 +908,9 @@
 DownloadUIModel::BubbleUIInfo
 DownloadUIModel::GetBubbleUIInfoForInProgressOrComplete(
     bool is_download_bubble_v2) const {
-  switch (GetMixedContentStatus()) {
-    case download::DownloadItem::MixedContentStatus::BLOCK:
-    case download::DownloadItem::MixedContentStatus::WARN:
+  switch (GetInsecureDownloadStatus()) {
+    case download::DownloadItem::InsecureDownloadStatus::BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::WARN:
       return DownloadUIModel::BubbleUIInfo(
                  l10n_util::GetStringUTF16(
                      IDS_DOWNLOAD_BUBBLE_WARNING_SUBPAGE_SUMMARY_INSECURE))
@@ -923,10 +923,10 @@
           .AddSubpageButton(
               l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_CONTINUE),
               DownloadCommands::Command::KEEP, /*is_prominent=*/false);
-    case download::DownloadItem::MixedContentStatus::UNKNOWN:
-    case download::DownloadItem::MixedContentStatus::SAFE:
-    case download::DownloadItem::MixedContentStatus::VALIDATED:
-    case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+    case download::DownloadItem::InsecureDownloadStatus::SAFE:
+    case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+    case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
 
@@ -1355,15 +1355,15 @@
         l10n_util::GetStringUTF16(detail_message_id));
   };
 
-  switch (model_->GetMixedContentStatus()) {
-    case download::DownloadItem::MixedContentStatus::BLOCK:
-    case download::DownloadItem::MixedContentStatus::WARN:
+  switch (model_->GetInsecureDownloadStatus()) {
+    case download::DownloadItem::InsecureDownloadStatus::BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::WARN:
       // "Blocked • Insecure download"
       return get_blocked_warning(IDS_DOWNLOAD_BUBBLE_WARNING_STATUS_INSECURE);
-    case download::DownloadItem::MixedContentStatus::UNKNOWN:
-    case download::DownloadItem::MixedContentStatus::SAFE:
-    case download::DownloadItem::MixedContentStatus::VALIDATED:
-    case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+    case download::DownloadItem::InsecureDownloadStatus::SAFE:
+    case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+    case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
 
diff --git a/chrome/browser/download/download_ui_model.h b/chrome/browser/download/download_ui_model.h
index 6b3c9219..69103e5 100644
--- a/chrome/browser/download/download_ui_model.h
+++ b/chrome/browser/download/download_ui_model.h
@@ -273,9 +273,9 @@
   // Implies IsDangerous() and MightBeMalicious().
   virtual bool IsMalicious() const;
 
-  // Is this download a mixed content download, but not something more severe?
+  // Is this download an insecure download, but not something more severe?
   // Implies IsDangerous() and !IsMalicious().
-  virtual bool IsMixedContent() const;
+  virtual bool IsInsecure() const;
 
   // Returns |true| if this download is expected to complete successfully and
   // thereafter be removed from the shelf.  Downloads that are opened
@@ -357,8 +357,8 @@
 
   // Return the mixed content status determined during download target
   // determination.
-  virtual download::DownloadItem::MixedContentStatus GetMixedContentStatus()
-      const;
+  virtual download::DownloadItem::InsecureDownloadStatus
+  GetInsecureDownloadStatus() const;
 
   // Open the download using the platform handler for the download. The behavior
   // of this method will be different from DownloadItem::OpenDownload() if
diff --git a/chrome/browser/download/mixed_content_download_blocking.cc b/chrome/browser/download/insecure_download_blocking.cc
similarity index 93%
rename from chrome/browser/download/mixed_content_download_blocking.cc
rename to chrome/browser/download/insecure_download_blocking.cc
index 2f1a2c09..5159ffcd 100644
--- a/chrome/browser/download/mixed_content_download_blocking.cc
+++ b/chrome/browser/download/insecure_download_blocking.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/download/mixed_content_download_blocking.h"
+#include "chrome/browser/download/insecure_download_blocking.h"
 
 #include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
@@ -27,7 +27,7 @@
 #include "url/origin.h"
 
 using download::DownloadSource;
-using MixedContentStatus = download::DownloadItem::MixedContentStatus;
+using InsecureDownloadStatus = download::DownloadItem::InsecureDownloadStatus;
 
 namespace {
 
@@ -183,9 +183,9 @@
   return InsecureDownloadSecurityStatus::kInitiatorInsecureFileInsecure;
 }
 
-struct MixedContentDownloadData {
-  MixedContentDownloadData(const base::FilePath& path,
-                           const download::DownloadItem* item)
+struct InsecureDownloadData {
+  InsecureDownloadData(const base::FilePath& path,
+                       const download::DownloadItem* item)
       : item_(item) {
     // Configure initiator.
     bool initiator_inferred = false;
@@ -302,8 +302,7 @@
 
 // Just print a descriptive message to the console about the blocked download.
 // |is_blocked| indicates whether this download will be blocked now.
-void PrintConsoleMessage(const MixedContentDownloadData& data,
-                         bool is_blocked) {
+void PrintConsoleMessage(const InsecureDownloadData& data, bool is_blocked) {
   content::WebContents* web_contents =
       content::DownloadItemUtils::GetWebContents(data.item_);
   if (!web_contents) {
@@ -360,25 +359,29 @@
 
 }  // namespace
 
-MixedContentStatus GetMixedContentStatusForDownload(
+InsecureDownloadStatus GetInsecureDownloadStatusForDownload(
     Profile* profile,
     const base::FilePath& path,
     const download::DownloadItem* item) {
-  MixedContentDownloadData data(path, item);
+  InsecureDownloadData data(path, item);
+
+  // For now, the only downloads that are marked as insecure are "mixed"
+  // downloads (i.e. insecure downloads that are initiated from a secure
+  // context). We expect this to change in the future.
 
   if (!data.is_mixed_content_) {
-    return MixedContentStatus::SAFE;
+    return InsecureDownloadStatus::SAFE;
   }
 
   // As of M81, print a console message even if no other blocking is enabled.
   if (!base::FeatureList::IsEnabled(features::kTreatUnsafeDownloadsAsActive)) {
     PrintConsoleMessage(data, false);
-    return MixedContentStatus::SAFE;
+    return InsecureDownloadStatus::SAFE;
   }
 
   if (IsDownloadPermittedByContentSettings(profile, data.initiator_)) {
     PrintConsoleMessage(data, false);
-    return MixedContentStatus::SAFE;
+    return InsecureDownloadStatus::SAFE;
   }
 
   if (ContainsExtension(kSilentBlockExtensionList.Get(), data.extension_) !=
@@ -390,25 +393,25 @@
     auto download_source = data.item_->GetDownloadSource();
     if (download_source == DownloadSource::CONTEXT_MENU ||
         download_source == DownloadSource::WEB_CONTENTS_API) {
-      return MixedContentStatus::BLOCK;
+      return InsecureDownloadStatus::BLOCK;
     }
 
-    return MixedContentStatus::SILENT_BLOCK;
+    return InsecureDownloadStatus::SILENT_BLOCK;
   }
 
   if (ContainsExtension(kBlockExtensionList.Get(), data.extension_) !=
       kTreatBlockListAsAllowlist.Get()) {
     PrintConsoleMessage(data, true);
-    return MixedContentStatus::BLOCK;
+    return InsecureDownloadStatus::BLOCK;
   }
 
   if (ContainsExtension(kWarnExtensionList.Get(), data.extension_) !=
       kTreatWarnListAsAllowlist.Get()) {
     PrintConsoleMessage(data, true);
-    return MixedContentStatus::WARN;
+    return InsecureDownloadStatus::WARN;
   }
 
   // The download is still mixed content, but we're not blocking it yet.
   PrintConsoleMessage(data, false);
-  return MixedContentStatus::SAFE;
+  return InsecureDownloadStatus::SAFE;
 }
diff --git a/chrome/browser/download/mixed_content_download_blocking.h b/chrome/browser/download/insecure_download_blocking.h
similarity index 94%
rename from chrome/browser/download/mixed_content_download_blocking.h
rename to chrome/browser/download/insecure_download_blocking.h
index a58b11ae..40f4c06 100644
--- a/chrome/browser/download/mixed_content_download_blocking.h
+++ b/chrome/browser/download/insecure_download_blocking.h
@@ -1,8 +1,8 @@
 // Copyright 2019 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_DOWNLOAD_MIXED_CONTENT_DOWNLOAD_BLOCKING_H_
-#define CHROME_BROWSER_DOWNLOAD_MIXED_CONTENT_DOWNLOAD_BLOCKING_H_
+#ifndef CHROME_BROWSER_DOWNLOAD_INSECURE_DOWNLOAD_BLOCKING_H_
+#define CHROME_BROWSER_DOWNLOAD_INSECURE_DOWNLOAD_BLOCKING_H_
 
 #include <string>
 
@@ -205,11 +205,11 @@
       .append(download);
 }
 
-// Returns the correct mixed content download blocking behavior for the given
+// Returns the correct insecure download blocking behavior for the given
 // |item| saved to |path|.  Controlled by kTreatUnsafeDownloadsAsActive.
-download::DownloadItem::MixedContentStatus GetMixedContentStatusForDownload(
-    Profile* profile,
-    const base::FilePath& path,
-    const download::DownloadItem* item);
+download::DownloadItem::InsecureDownloadStatus
+GetInsecureDownloadStatusForDownload(Profile* profile,
+                                     const base::FilePath& path,
+                                     const download::DownloadItem* item);
 
-#endif  // CHROME_BROWSER_DOWNLOAD_MIXED_CONTENT_DOWNLOAD_BLOCKING_H_
+#endif  // CHROME_BROWSER_DOWNLOAD_INSECURE_DOWNLOAD_BLOCKING_H_
diff --git a/chrome/browser/download/notification/download_item_notification.cc b/chrome/browser/download/notification/download_item_notification.cc
index 72415b6..8c31563 100644
--- a/chrome/browser/download/notification/download_item_notification.cc
+++ b/chrome/browser/download/notification/download_item_notification.cc
@@ -167,7 +167,7 @@
       base::RecordAction(
           UserMetricsAction("DownloadNotification.Button_LearnScanning"));
       break;
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
       base::RecordAction(
           UserMetricsAction("DownloadNotification.Button_LearnMixedContent"));
       break;
@@ -238,9 +238,9 @@
   // Only notifications for in-progress downloads are eligible for suppression.
   if (item->GetState() != download::DownloadItem::IN_PROGRESS)
     return false;
-  // Notifications associated with dangerous or mixed content downloads are not
+  // Notifications associated with dangerous or insecure downloads are not
   // eligible for suppression as they likely contain important information.
-  if (item->IsDangerous() || item->IsMixedContent())
+  if (item->IsDangerous() || item->IsInsecure())
     return false;
   // Otherwise notifications are assumed to be informational only and are
   // therefore eligible for suppression.
@@ -353,7 +353,7 @@
     return;
   }
 
-  if (item_ && item_->IsMixedContent() && !item_->IsDone()) {
+  if (item_ && item_->IsInsecure() && !item_->IsDone()) {
     item_->Cancel(by_user);
     return;
   }
@@ -426,7 +426,7 @@
   }
 
   // Handle a click on the notification's body.
-  if (item_->IsMixedContent()) {
+  if (item_->IsInsecure()) {
     chrome::ShowDownloads(GetBrowser());
     return;
   }
@@ -487,7 +487,7 @@
   // dangerous, make sure it pops up again.
   bool pop_up =
       ((item_->IsDangerous() && !previous_dangerous_state_) ||
-       (item_->IsMixedContent() && !previous_mixed_content_state_) ||
+       (item_->IsInsecure() && !previous_insecure_state_) ||
        (download_state == download::DownloadItem::COMPLETE &&
         previous_download_state_ != download::DownloadItem::COMPLETE) ||
        (download_state == download::DownloadItem::INTERRUPTED &&
@@ -497,7 +497,7 @@
   show_next_ = false;
   previous_download_state_ = item_->GetState();
   previous_dangerous_state_ = item_->IsDangerous();
-  previous_mixed_content_state_ = item_->IsMixedContent();
+  previous_insecure_state_ = item_->IsInsecure();
 }
 
 void DownloadItemNotification::UpdateNotificationData(bool display,
@@ -553,19 +553,19 @@
     } else {
       notification_->set_priority(message_center::DEFAULT_PRIORITY);
     }
-  } else if (item_->IsMixedContent()) {
+  } else if (item_->IsInsecure()) {
     notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
-    switch (item_->GetMixedContentStatus()) {
-      case download::DownloadItem::MixedContentStatus::BLOCK:
+    switch (item_->GetInsecureDownloadStatus()) {
+      case download::DownloadItem::InsecureDownloadStatus::BLOCK:
         notification_->set_priority(message_center::HIGH_PRIORITY);
         break;
-      case download::DownloadItem::MixedContentStatus::WARN:
+      case download::DownloadItem::InsecureDownloadStatus::WARN:
         notification_->set_priority(message_center::DEFAULT_PRIORITY);
         break;
-      case download::DownloadItem::MixedContentStatus::UNKNOWN:
-      case download::DownloadItem::MixedContentStatus::SAFE:
-      case download::DownloadItem::MixedContentStatus::VALIDATED:
-      case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+      case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+      case download::DownloadItem::InsecureDownloadStatus::SAFE:
+      case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+      case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
         NOTREACHED();
         break;
     }
@@ -658,16 +658,16 @@
                ? ash::kSystemNotificationColorCriticalWarning
                : ash::kSystemNotificationColorWarning;
   }
-  if (item_->IsMixedContent()) {
-    switch (item_->GetMixedContentStatus()) {
-      case download::DownloadItem::MixedContentStatus::BLOCK:
+  if (item_->IsInsecure()) {
+    switch (item_->GetInsecureDownloadStatus()) {
+      case download::DownloadItem::InsecureDownloadStatus::BLOCK:
         return ash::kSystemNotificationColorCriticalWarning;
-      case download::DownloadItem::MixedContentStatus::WARN:
+      case download::DownloadItem::InsecureDownloadStatus::WARN:
         return ash::kSystemNotificationColorWarning;
-      case download::DownloadItem::MixedContentStatus::UNKNOWN:
-      case download::DownloadItem::MixedContentStatus::SAFE:
-      case download::DownloadItem::MixedContentStatus::VALIDATED:
-      case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+      case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+      case download::DownloadItem::InsecureDownloadStatus::SAFE:
+      case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+      case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
         NOTREACHED();
         break;
     }
@@ -772,22 +772,22 @@
     return actions;
   }
 
-  if (item_->IsMixedContent()) {
-    switch (item_->GetMixedContentStatus()) {
-      case download::DownloadItem::MixedContentStatus::BLOCK:
+  if (item_->IsInsecure()) {
+    switch (item_->GetInsecureDownloadStatus()) {
+      case download::DownloadItem::InsecureDownloadStatus::BLOCK:
         actions->push_back(DownloadCommands::DISCARD);
         break;
-      case download::DownloadItem::MixedContentStatus::WARN:
+      case download::DownloadItem::InsecureDownloadStatus::WARN:
         actions->push_back(DownloadCommands::KEEP);
         break;
-      case download::DownloadItem::MixedContentStatus::UNKNOWN:
-      case download::DownloadItem::MixedContentStatus::SAFE:
-      case download::DownloadItem::MixedContentStatus::VALIDATED:
-      case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+      case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+      case download::DownloadItem::InsecureDownloadStatus::SAFE:
+      case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+      case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
         NOTREACHED();
         break;
     }
-    actions->push_back(DownloadCommands::LEARN_MORE_MIXED_CONTENT);
+    actions->push_back(DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD);
     return actions;
   }
 
@@ -867,8 +867,9 @@
     }
   }
 
-  if (item_->IsMixedContent()) {
-    return l10n_util::GetStringUTF16(IDS_PROMPT_BLOCKED_MIXED_DOWNLOAD_TITLE);
+  if (item_->IsInsecure()) {
+    return l10n_util::GetStringUTF16(
+        IDS_PROMPT_BLOCKED_INSECURE_DOWNLOAD_TITLE);
   }
 
   std::u16string file_name =
@@ -943,7 +944,7 @@
     case DownloadCommands::COPY_TO_CLIPBOARD:
       id = IDS_DOWNLOAD_NOTIFICATION_COPY_TO_CLIPBOARD;
       break;
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
       id = IDS_LEARN_MORE;
       break;
     case DownloadCommands::DEEP_SCAN:
@@ -978,13 +979,13 @@
 }
 
 std::u16string DownloadItemNotification::GetWarningStatusString() const {
-  // Should only be called if IsDangerous() or IsMixedContent().
-  DCHECK(item_->IsDangerous() || item_->IsMixedContent());
+  // Should only be called if IsDangerous() or IsInsecure().
+  DCHECK(item_->IsDangerous() || item_->IsInsecure());
   std::u16string elided_filename =
       item_->GetFileNameToReportUser().LossyDisplayName();
-  // If mixed content, that warning is shown first.
-  if (item_->IsMixedContent()) {
-    return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED,
+  // If insecure, that warning is shown first.
+  if (item_->IsInsecure()) {
+    return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED,
                                       elided_filename);
   }
   switch (item_->GetDangerType()) {
@@ -1099,7 +1100,7 @@
 }
 
 std::u16string DownloadItemNotification::GetSubStatusString() const {
-  if (item_->IsMixedContent() || item_->IsDangerous())
+  if (item_->IsInsecure() || item_->IsDangerous())
     return GetWarningStatusString();
 
   if (item_->GetDangerType() ==
@@ -1160,7 +1161,7 @@
 }
 
 std::u16string DownloadItemNotification::GetStatusString() const {
-  if (item_->IsDangerous() || item_->IsMixedContent())
+  if (item_->IsDangerous() || item_->IsInsecure())
     return std::u16string();
 
   if (IsScanning()) {
diff --git a/chrome/browser/download/notification/download_item_notification.h b/chrome/browser/download/notification/download_item_notification.h
index 6d90310..cde991b 100644
--- a/chrome/browser/download/notification/download_item_notification.h
+++ b/chrome/browser/download/notification/download_item_notification.h
@@ -150,7 +150,7 @@
   download::DownloadItem::DownloadState previous_download_state_ =
       download::DownloadItem::MAX_DOWNLOAD_STATE;  // As uninitialized state
   bool previous_dangerous_state_ = false;
-  bool previous_mixed_content_state_ = false;
+  bool previous_insecure_state_ = false;
   std::unique_ptr<message_center::Notification> notification_;
 
   DownloadUIModel::DownloadUIModelPtr item_;
diff --git a/chrome/browser/download/notification/download_item_notification_unittest.cc b/chrome/browser/download/notification/download_item_notification_unittest.cc
index 173b075..241b92dc 100644
--- a/chrome/browser/download/notification/download_item_notification_unittest.cc
+++ b/chrome/browser/download/notification/download_item_notification_unittest.cc
@@ -550,9 +550,9 @@
 
 // Verifies that download in-progress notifications are displayed even if the
 // holding space in-progress downloads notification suppression feature is
-// enabled if the underlying download is mixed content.
+// enabled if the underlying download is insecure.
 TEST_P(DownloadItemNotificationParameterizedTest,
-       ShowInProgressNotificationsIfMixedContent) {
+       ShowInProgressNotificationsIfInsecure) {
   // Creates a download in-progress notification.
   CreateDownloadItemNotification();
 
@@ -563,20 +563,22 @@
                 : 1u,
             NotificationCount());
 
-  // The download becoming mixed-content should cause the notification to be
+  // The download becoming insecure should cause the notification to be
   // displayed even if it was previously suppressed.
-  ON_CALL(*download_item_, GetMixedContentStatus)
-      .WillByDefault(Return(download::DownloadItem::MixedContentStatus::WARN));
-  ON_CALL(*download_item_, IsMixedContent).WillByDefault(Return(true));
+  ON_CALL(*download_item_, GetInsecureDownloadStatus)
+      .WillByDefault(
+          Return(download::DownloadItem::InsecureDownloadStatus::WARN));
+  ON_CALL(*download_item_, IsInsecure).WillByDefault(Return(true));
   download_item_->NotifyObserversDownloadUpdated();
   EXPECT_EQ(1u, NotificationCount());
 
-  // The download becoming non-mixed content should cause the notification to be
+  // The download becoming secure should cause the notification to be
   // suppressed if an only if holding space in-progress downloads notification
   // suppression is enabled.
-  ON_CALL(*download_item_, GetMixedContentStatus)
-      .WillByDefault(Return(download::DownloadItem::MixedContentStatus::SAFE));
-  ON_CALL(*download_item_, IsMixedContent).WillByDefault(Return(false));
+  ON_CALL(*download_item_, GetInsecureDownloadStatus)
+      .WillByDefault(
+          Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
+  ON_CALL(*download_item_, IsInsecure).WillByDefault(Return(false));
   download_item_->NotifyObserversDownloadUpdated();
   EXPECT_EQ(IsHoldingSpaceInProgressDownloadsNotificationSuppressionEnabled()
                 ? 0u
diff --git a/chrome/browser/download/notification/download_notification_browsertest.cc b/chrome/browser/download/notification/download_notification_browsertest.cc
index ae5ab6aa..81ac5b5b 100644
--- a/chrome/browser/download/notification/download_notification_browsertest.cc
+++ b/chrome/browser/download/notification/download_notification_browsertest.cc
@@ -460,11 +460,11 @@
     ASSERT_TRUE(download_item_);
 
     // Confirms that a notification is created when the `download_item_` is not
-    // in-progress, dangerous, mixed content, or holding space in-progress
+    // in-progress, dangerous, insecure, or holding space in-progress
     // downloads notification suppression is disabled. Otherwise notification is
     // suppressed.
     if (download_item_->GetState() != download::DownloadItem::IN_PROGRESS ||
-        download_item_->IsDangerous() || download_item_->IsMixedContent() ||
+        download_item_->IsDangerous() || download_item_->IsInsecure() ||
         !IsHoldingSpaceInProgressDownloadsNotificationSuppressionEnabled()) {
       WaitForDownloadNotification(browser);
       CacheNotification(browser);
diff --git a/chrome/browser/download/offline_item_model.cc b/chrome/browser/download/offline_item_model.cc
index 34533829..366f115 100644
--- a/chrome/browser/download/offline_item_model.cc
+++ b/chrome/browser/download/offline_item_model.cc
@@ -313,7 +313,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
     case DownloadCommands::REVIEW:
@@ -345,7 +345,7 @@
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
     case DownloadCommands::LEARN_MORE_INTERRUPTED:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
     case DownloadCommands::COPY_TO_CLIPBOARD:
     case DownloadCommands::DEEP_SCAN:
     case DownloadCommands::BYPASS_DEEP_SCANNING:
@@ -367,7 +367,7 @@
     case DownloadCommands::ALWAYS_OPEN_TYPE:
     case DownloadCommands::KEEP:
     case DownloadCommands::LEARN_MORE_SCANNING:
-    case DownloadCommands::LEARN_MORE_MIXED_CONTENT:
+    case DownloadCommands::LEARN_MORE_INSECURE_DOWNLOAD:
       NOTIMPLEMENTED();
       return;
     case DownloadCommands::PLATFORM_OPEN:
diff --git a/chrome/browser/enterprise/platform_auth/BUILD.gn b/chrome/browser/enterprise/platform_auth/BUILD.gn
index 7129882..e0a373352 100644
--- a/chrome/browser/enterprise/platform_auth/BUILD.gn
+++ b/chrome/browser/enterprise/platform_auth/BUILD.gn
@@ -9,3 +9,22 @@
 
   public_deps = [ "//base" ]
 }
+
+source_set("test_utils") {
+  testonly = true
+  sources = [
+    "mock_platform_auth_provider.cc",
+    "scoped_set_provider_for_testing.cc",
+  ]
+
+  public = [
+    "mock_platform_auth_provider.h",
+    "scoped_set_provider_for_testing.h",
+  ]
+
+  public_deps = [
+    "//chrome/browser",
+    "//net",
+    "//testing/gmock",
+  ]
+}
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc
new file mode 100644
index 0000000..1d093c34
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc
@@ -0,0 +1,424 @@
+// 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/cloud_ap_provider_win.h"
+
+#include <stdint.h>
+#include <windows.h>  // Must precede lmjoin.h.
+
+#include <lmcons.h>
+#include <lmjoin.h>
+#include <objbase.h>
+#include <proofofpossessioncookieinfo.h>
+#include <windows.security.authentication.web.core.h>
+#include <wrl/client.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_list.h"
+#include "base/check.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/native_library.h"
+#include "base/scoped_native_library.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/win/com_init_util.h"
+#include "base/win/core_winrt_util.h"
+#include "base/win/post_async_results.h"
+#include "base/win/scoped_hstring.h"
+#include "chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.h"
+#include "net/cookies/cookie_util.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+
+using ABI::Windows::Foundation::IAsyncOperation;
+using ABI::Windows::Security::Authentication::Web::Core::
+    IWebAuthenticationCoreManagerStatics;
+using ABI::Windows::Security::Credentials::IWebAccountProvider;
+using ABI::Windows::Security::Credentials::WebAccountProvider;
+using Microsoft::WRL::ComPtr;
+
+namespace enterprise_auth {
+
+namespace {
+
+using OnSupportLevelCallback =
+    base::OnceCallback<void(CloudApProviderWin::SupportLevel)>;
+
+// A helper to manage the lifetime of various objects while checking to see if
+// there is at least one WebAccount for the default provider.
+class WebAccountSupportFinder
+    : public base::RefCountedThreadSafe<WebAccountSupportFinder> {
+ public:
+  REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
+
+  // `on_support_level` is posted to `result_runner` upon destruction with the
+  // results of the operation. Reports `SupportLevel::kEnabled` if an account is
+  // found, `SupportLevel::kDisabled` if no account is found, or
+  // `SupportLevel::kUnsupported` in case of any error.
+  WebAccountSupportFinder(scoped_refptr<base::TaskRunner> result_runner,
+                          OnSupportLevelCallback on_support_level)
+      : result_runner_(std::move(result_runner)),
+        on_support_level_(std::move(on_support_level)) {
+    DETACH_FROM_SEQUENCE(sequence_checker_);
+  }
+
+  WebAccountSupportFinder(const WebAccountSupportFinder&) = delete;
+  WebAccountSupportFinder& operator=(const WebAccountSupportFinder&) = delete;
+
+  // Starts the operation.
+  void Find() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    base::win::AssertComApartmentType(base::win::ComApartmentType::MTA);
+
+    if (!base::win::ResolveCoreWinRTDelayload())
+      return;  // Unsupported.
+    if (!base::win::ScopedHString::ResolveCoreWinRTStringDelayload())
+      return;  // Unsupported.
+
+    // Get the `WebAuthenticationCoreManager`.
+    ComPtr<IWebAuthenticationCoreManagerStatics> auth_manager;
+    HRESULT hresult = base::win::GetActivationFactory<
+        IWebAuthenticationCoreManagerStatics,
+        RuntimeClass_Windows_Security_Authentication_Web_Core_WebAuthenticationCoreManager>(
+        &auth_manager);
+    if (FAILED(hresult))
+      return;  // Unsupported.
+
+    ComPtr<IAsyncOperation<WebAccountProvider*>> find_provider_op;
+
+    // "https://login.windows.local" -- account provider for the OS. Don't
+    // specify an authority when using it.
+    // https://docs.microsoft.com/en-us/uwp/api/windows.security.authentication.web.core.webauthenticationcoremanager.findaccountproviderasync?view=winrt-19041
+    hresult = auth_manager->FindAccountProviderAsync(
+        base::win::ScopedHString::Create(L"https://login.windows.local").get(),
+        &find_provider_op);
+    if (FAILED(hresult))
+      return;  // Unsupported.
+
+    hresult = base::win::PostAsyncHandlers(
+        find_provider_op.Get(),
+        base::BindOnce(&WebAccountSupportFinder::OnAccountProvider,
+                       base::WrapRefCounted(this)));
+    if (FAILED(hresult)) {
+      DLOG(ERROR)
+          << __func__
+          << ": Failed to post result task for provider fetch; HRESULT = "
+          << std::hex << hresult;
+    }
+  }
+
+ private:
+  friend class base::RefCountedThreadSafe<WebAccountSupportFinder>;
+
+  // Posts `on_support_level_` with `support_level_` to `result_runner_`.
+  ~WebAccountSupportFinder() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    result_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(on_support_level_), support_level_));
+  }
+
+  // Handles the result of a successful call to `FindAccountProviderAsync()`.
+  void OnAccountProvider(ComPtr<IWebAccountProvider> account_provider) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // Regardless of whether a provider is found, the machine supports account
+    // providers.
+    support_level_ = account_provider
+                         ? CloudApProviderWin::SupportLevel::kEnabled
+                         : CloudApProviderWin::SupportLevel::kDisabled;
+  }
+
+  scoped_refptr<base::TaskRunner> result_runner_;
+  OnSupportLevelCallback on_support_level_;
+  CloudApProviderWin::SupportLevel support_level_ =
+      CloudApProviderWin::SupportLevel::kUnsupported;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+CloudApProviderWin::SupportLevel* support_level_for_testing_ = nullptr;
+
+// Returns the platform's ProofOfPossessionCookieInfoManager, or null if
+// unsupported. `hresult_out`, if provided, is populated with the result of
+// object creation.
+ComPtr<IProofOfPossessionCookieInfoManager> MakeCookieInfoManager(
+    HRESULT* hresult_out = nullptr) {
+  // CLSID_ProofOfPossessionCookieInfoManager from
+  // ProofOfPossessionCookieInfo.h.
+  static constexpr CLSID kClsidProofOfPossessionCookieInfoManager = {
+      0xA9927F85,
+      0xA304,
+      0x4390,
+      {0x8B, 0x23, 0xA7, 0x5F, 0x1C, 0x66, 0x86, 0x00}};
+
+  // There is no need for SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY here
+  // since this task is posted at USER_VISIBLE priority.
+  DCHECK_NE(base::PlatformThread::GetCurrentThreadType(),
+            base::ThreadType::kBackground);
+  base::win::AssertComInitialized();
+
+  ComPtr<IProofOfPossessionCookieInfoManager> manager;
+
+  HRESULT hresult = ::CoCreateInstance(
+      kClsidProofOfPossessionCookieInfoManager,
+      /*pUnkOuter=*/nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&manager));
+  if (hresult_out)
+    *hresult_out = hresult;
+  return SUCCEEDED(hresult) ? manager : nullptr;
+}
+
+// Returns the proof-of-possession cookies and headers for the interactive
+// user to authenticate to the IdP/STS at `url`.
+net::HttpRequestHeaders GetAuthData(const GURL& url) {
+  base::win::AssertComInitialized();
+  DCHECK(url.is_valid());
+
+  net::HttpRequestHeaders auth_headers;
+  DWORD cookie_info_count = 0;
+  base::ElapsedTimer elapsed_timer;
+
+  HRESULT hresult = S_OK;
+  auto manager = MakeCookieInfoManager(&hresult);
+  if (manager) {
+    ProofOfPossessionCookieInfo* cookie_info = nullptr;
+    hresult =
+        manager->GetCookieInfoForUri(base::ASCIIToWide(url.spec()).c_str(),
+                                     &cookie_info_count, &cookie_info);
+    if (SUCCEEDED(hresult)) {
+      DCHECK(!cookie_info_count || cookie_info);
+      // Add the new auth cookies to the existing set of cookies.
+      // TODO(crbug.com/1246839): Check if cookies whose name begins with
+      // `x-ms-` should be treated as headers.
+      net::cookie_util::ParsedRequestCookies parsed_cookies;
+      for (DWORD i = 0; i < cookie_info_count; ++i) {
+        const ProofOfPossessionCookieInfo& cookie = cookie_info[i];
+        parsed_cookies.emplace_back(base::WideToASCII(cookie.name),
+                                    base::WideToASCII(cookie.data));
+      }
+      if (parsed_cookies.size() > 0) {
+        auth_headers.SetHeader(
+            net::HttpRequestHeaders::kCookie,
+            net::cookie_util::SerializeRequestCookieLine(parsed_cookies));
+      }
+
+      if (cookie_info)
+        FreeProofOfPossessionCookieInfoArray(cookie_info, cookie_info_count);
+    }
+  }
+  const auto delta = elapsed_timer.Elapsed();
+
+  if (SUCCEEDED(hresult)) {
+    base::UmaHistogramTimes("Enterprise.PlatformAuth.GetAuthData.SuccessTime",
+                            delta);
+    base::UmaHistogramExactLinear("Enterprise.PlatformAuth.GetAuthData.Count",
+                                  cookie_info_count,
+                                  10);  // Expect < 10 cookies.
+  } else {
+    base::UmaHistogramTimes("Enterprise.PlatformAuth.GetAuthData.FailureTime",
+                            delta);
+    base::UmaHistogramSparse(
+        "Enterprise.PlatformAuth.GetAuthData.FailureHresult", int{hresult});
+  }
+
+  return auth_headers;
+}
+
+// Returns the support level based on Azure AD join status.
+CloudApProviderWin::SupportLevel GetAadJoinSupportLevel() {
+  // There is no need for `SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY` here
+  // since this task is posted at `USER_VISIBLE` priority.
+  DCHECK_NE(base::PlatformThread::GetCurrentThreadType(),
+            base::ThreadType::kBackground);
+
+  // If Azure AD join info retrieval fails, this feature is not supported.
+  PDSREG_JOIN_INFO join_info = nullptr;
+  if (FAILED(::NetGetAadJoinInformation(/*pcszTenantId=*/nullptr, &join_info)))
+    return CloudApProviderWin::SupportLevel::kUnsupported;
+
+  // Azure AD join info was retrieved successfully, so the feature is supported.
+  // This will free the retrieved Azure AD join info after going out of scope.
+  std::unique_ptr<DSREG_JOIN_INFO, decltype(&NetFreeAadJoinInformation)>
+      scoped_join_info(join_info, ::NetFreeAadJoinInformation);
+
+  return (!join_info || join_info->joinType == DSREG_UNKNOWN_JOIN)
+             ? CloudApProviderWin::SupportLevel::kDisabled
+             : CloudApProviderWin::SupportLevel::kEnabled;
+}
+
+// Handles the results of checking for a WebAccount from the default provider.
+void OnFindWebAccount(OnSupportLevelCallback on_support_level,
+                      CloudApProviderWin::SupportLevel support_level) {
+  // Full support if there's at least one WebAccount for the default provider.
+  if (support_level == CloudApProviderWin::SupportLevel::kEnabled) {
+    std::move(on_support_level).Run(CloudApProviderWin::SupportLevel::kEnabled);
+    return;
+  }
+
+  // Otherwise, support is based on whether or not the device is AAD-joined one
+  // way (device joined) or another (workplace joined).
+  std::move(on_support_level).Run(GetAadJoinSupportLevel());
+}
+
+// Evaluates the level of support for Cloud AP SSO, running `on_support_level`
+// on the caller's sequence (synchronously or asynchronously) with the result.
+void GetSupportLevel(OnSupportLevelCallback on_support_level) {
+  if (support_level_for_testing_) {
+    std::move(on_support_level).Run(*support_level_for_testing_);
+    return;
+  }
+
+  // Check if the machine has the ProofOfPossessionCookieInfoManager COM class.
+  if (!MakeCookieInfoManager()) {
+    std::move(on_support_level)
+        .Run(CloudApProviderWin::SupportLevel::kUnsupported);
+    return;
+  }
+
+  // Check if there's at least one WebAccount for the default provider.
+  base::MakeRefCounted<WebAccountSupportFinder>(
+      base::SequencedTaskRunnerHandle::Get(),
+      base::BindOnce(&OnFindWebAccount, std::move(on_support_level)))
+      ->Find();
+}
+
+// Reads the IdP origins from the Windows registry.
+std::vector<url::Origin> ReadOrigins() {
+  static constexpr wchar_t kLoginUri[] = L"LoginUri";
+  std::vector<url::Origin> result;
+
+  // Windows registry locations (provided by Microsoft) which are expected to
+  // contain Microsoft IdP origins.
+  AppendRegistryOrigins(HKEY_LOCAL_MACHINE,
+                        L"SOFTWARE\\Microsoft\\IdentityStore\\LoadParameters\\"
+                        L"{B16898C6-A148-4967-9171-64D755DA8520}",
+                        kLoginUri, result);
+  AppendRegistryOrigins(
+      HKEY_LOCAL_MACHINE,
+      L"SOFTWARE\\Microsoft\\IdentityStore\\Providers\\"
+      L"{B16898C6-A148-4967-9171-64D755DA8520}\\LoadParameters",
+      kLoginUri, result);
+  AppendRegistryOrigins(
+      HKEY_CURRENT_USER,
+      L"Software\\Microsoft\\Windows\\CurrentVersion\\AAD\\Package", kLoginUri,
+      result);
+  AppendRegistryOrigins(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\IdentityCRL",
+                        L"LoginUrl", result);
+
+  if (result.empty()) {
+    // Certain legacy versions of Windows may not have origins in the registry.
+    // Use the two well-known origins if none other are found.
+    result.push_back(url::Origin::Create(GURL("https://login.live.com")));
+    result.push_back(
+        url::Origin::Create(GURL("https://login.microsoftonline.com")));
+  }
+
+  return result;
+}
+
+// Handles the results of a call to `GetSupportLevel()`.
+void OnSupportLevel(scoped_refptr<base::TaskRunner> result_runner,
+                    CloudApProviderWin::FetchOriginsCallback on_origins,
+                    CloudApProviderWin::SupportLevel support_level) {
+  std::unique_ptr<std::vector<url::Origin>> results;
+
+  switch (support_level) {
+    case CloudApProviderWin::SupportLevel::kUnsupported:
+      // There is no hope in trying again.
+      break;
+    case CloudApProviderWin::SupportLevel::kDisabled:
+      // Not joined at the moment, but could change in the future.
+      results = std::make_unique<std::vector<url::Origin>>();
+      break;
+    case CloudApProviderWin::SupportLevel::kEnabled:
+      results = std::make_unique<std::vector<url::Origin>>(ReadOrigins());
+      break;
+  }
+
+  result_runner->PostTask(
+      FROM_HERE, base::BindOnce(std::move(on_origins), std::move(results)));
+}
+
+// Fetches the collection of IdP/STS origins in the ThreadPool. Runs
+// `on_origins` on `result_runner` with the origins or nullptr if Cloud AP SSO
+// is not supported.
+void FetchOriginsInPool(scoped_refptr<base::TaskRunner> result_runner,
+                        CloudApProviderWin::FetchOriginsCallback on_origins) {
+  GetSupportLevel(base::BindOnce(&OnSupportLevel, std::move(result_runner),
+                                 std::move(on_origins)));
+}
+
+}  // namespace
+
+CloudApProviderWin::CloudApProviderWin() = default;
+
+CloudApProviderWin::~CloudApProviderWin() = default;
+
+void CloudApProviderWin::FetchOrigins(FetchOriginsCallback on_fetch_complete) {
+  // The strategy is as follows:
+  // 1. See if the ProofOfPossessionCookieInfoManager can be instantiated. If
+  //    not, the platform doesn't support AAD SSO.
+  // 2. See if the user has a WebAccount from the default provider. If they do,
+  //    the platform supports AAD SSO and it is enabled.
+  // 3. See if either the device is joined to an AAD domain or if an AAD work
+  //    account has been added. In either case, the device supports AAD SSO and
+  //    it is enabled.
+  // 4. If checking the join status failed, the platform doesn't support AAD
+  //    SSO; otherwise, the platform supports AAD SSO but it is disabled.
+  // The callback is run with:
+  // - nullptr if AAD SSO is not supported.
+  // - an empty collection of origins if AAD SSO is supported but disabled.
+  // - two or more URLs if AAD SSO is supported and enabled.
+  base::ThreadPool::CreateSequencedTaskRunner(
+      {base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, base::MayBlock()})
+      ->PostTask(FROM_HERE,
+                 base::BindOnce(&FetchOriginsInPool,
+                                base::SequencedTaskRunnerHandle::Get(),
+                                std::move(on_fetch_complete)));
+}
+
+void CloudApProviderWin::GetData(
+    const GURL& url,
+    PlatformAuthProviderManager::GetDataCallback callback) {
+  get_data_subscription_ = on_get_data_callback_list_.Add(std::move(callback));
+  if (!base::ThreadPool::CreateCOMSTATaskRunner(
+           {base::TaskPriority::USER_BLOCKING,
+            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, base::MayBlock()})
+           ->PostTaskAndReplyWithResult(
+               FROM_HERE, base::BindOnce(&GetAuthData, url),
+               base::BindOnce(&CloudApProviderWin::OnGetDataCallback,
+                              base::Unretained(this)))) {
+    OnGetDataCallback(net::HttpRequestHeaders());
+  }
+}
+
+void CloudApProviderWin::OnGetDataCallback(
+    net::HttpRequestHeaders auth_headers) {
+  on_get_data_callback_list_.Notify(std::move(auth_headers));
+}
+
+// static
+void CloudApProviderWin::SetSupportLevelForTesting(
+    absl::optional<SupportLevel> level) {
+  delete std::exchange(support_level_for_testing_, nullptr);
+  if (!level)
+    return;
+  support_level_for_testing_ = new SupportLevel;
+  *support_level_for_testing_ = level.value();
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.h b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.h
new file mode 100644
index 0000000..5a0c005f
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.h
@@ -0,0 +1,67 @@
+// 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_CLOUD_AP_PROVIDER_WIN_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_CLOUD_AP_PROVIDER_WIN_H_
+
+#include "base/callback_list.h"
+#include "base/gtest_prod_util.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class GURL;
+
+namespace net {
+class HttpRequestHeaders;
+}  // namespace net
+
+namespace enterprise_auth {
+
+class CloudApProviderWin : public PlatformAuthProvider {
+ public:
+  enum class SupportLevel {
+    // Device does not support Cloud AP SSO.
+    kUnsupported,
+    // User has no accounts capable of SSO at the moment.
+    kDisabled,
+    // User has one or more accounts capable of SSO.
+    kEnabled,
+  };
+
+  CloudApProviderWin();
+  CloudApProviderWin(const CloudApProviderWin&) = delete;
+  CloudApProviderWin& operator=(const CloudApProviderWin&) = delete;
+  ~CloudApProviderWin() override;
+
+  // enterprise_auth::PlatformAuthProvider:
+  void FetchOrigins(FetchOriginsCallback on_fetch_complete) override;
+  void GetData(const GURL& url,
+               PlatformAuthProviderManager::GetDataCallback callback) override;
+
+ private:
+  friend class CloudApProviderWinTest;
+  FRIEND_TEST_ALL_PREFIXES(CloudApProviderWinTest, Unsupported);
+  FRIEND_TEST_ALL_PREFIXES(CloudApProviderWinTest, NotJoined);
+  FRIEND_TEST_ALL_PREFIXES(CloudApProviderWinTest, Joined);
+
+  // Runs the stored callbacks using the provided auth headers.
+  void OnGetDataCallback(net::HttpRequestHeaders auth_headers);
+
+  // Overrides support detection with `level` if it has a value, or resets the
+  // override if not.
+  static void SetSupportLevelForTesting(absl::optional<SupportLevel> level);
+
+  // List of callbacks to run when auth data is received.
+  using GetDataCallbackList =
+      base::OnceCallbackList<void(net::HttpRequestHeaders)>;
+  GetDataCallbackList on_get_data_callback_list_;
+
+  // Subscription for auth data requests. Guarantees that the corresponding
+  // callbacks are run on destruction.
+  base::CallbackListSubscription get_data_subscription_;
+};
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_CLOUD_AP_PROVIDER_WIN_H_
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win_unittest.cc b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win_unittest.cc
new file mode 100644
index 0000000..62a33e3
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win_unittest.cc
@@ -0,0 +1,149 @@
+// 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/cloud_ap_provider_win.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+using ::testing::_;
+
+namespace enterprise_auth {
+
+class CloudApProviderWinTest : public ::testing::Test {
+ protected:
+  ~CloudApProviderWinTest() override {
+    // Clear an override of the join type made by any test.
+    CloudApProviderWin::SetSupportLevelForTesting(absl::nullopt);
+  }
+
+  void SetUp() override {
+    ASSERT_NO_FATAL_FAILURE(
+        registry_override_.OverrideRegistry(HKEY_LOCAL_MACHINE));
+    ASSERT_NO_FATAL_FAILURE(
+        registry_override_.OverrideRegistry(HKEY_CURRENT_USER));
+
+    base::win::RegKey key;
+    ASSERT_EQ(key.Create(HKEY_LOCAL_MACHINE, kIdentityStorePath,
+                         KEY_WOW64_64KEY | KEY_SET_VALUE),
+              ERROR_SUCCESS);
+    ASSERT_EQ(key.WriteValue(kLoginUriName, L"https://host1"), ERROR_SUCCESS);
+
+    ASSERT_EQ(key.Create(HKEY_CURRENT_USER, kPackagePath,
+                         KEY_WOW64_64KEY | KEY_SET_VALUE),
+              ERROR_SUCCESS);
+    ASSERT_EQ(key.WriteValue(kLoginUriName, L"https://host2"), ERROR_SUCCESS);
+  }
+
+  static const wchar_t kIdentityStorePath[];
+  static const wchar_t kPackagePath[];
+  static const wchar_t kLoginUriName[];
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  registry_util::RegistryOverrideManager registry_override_;
+};
+
+// static
+constexpr wchar_t CloudApProviderWinTest::kIdentityStorePath[] =
+    L"SOFTWARE\\Microsoft\\IdentityStore\\LoadParameters\\"
+    L"{B16898C6-A148-4967-9171-64D755DA8520}";
+
+// static
+constexpr wchar_t CloudApProviderWinTest::kPackagePath[] =
+    L"Software\\Microsoft\\Windows\\CurrentVersion\\AAD\\Package";
+
+// static
+constexpr wchar_t CloudApProviderWinTest::kLoginUriName[] = L"LoginUri";
+
+// Tests that the provider returns null when AAD SSO is not supported.
+TEST_F(CloudApProviderWinTest, Unsupported) {
+  CloudApProviderWin::SetSupportLevelForTesting(
+      CloudApProviderWin::SupportLevel::kUnsupported);
+
+  CloudApProviderWin provider;
+
+  base::RunLoop run_loop;
+  base::MockCallback<CloudApProviderWin::FetchOriginsCallback> mock;
+  EXPECT_CALL(mock, Run(_))
+      .WillOnce([&run_loop](std::unique_ptr<std::vector<url::Origin>> origins) {
+        run_loop.Quit();
+        EXPECT_EQ(origins.get(), nullptr);
+      });
+
+  provider.FetchOrigins(mock.Get());
+  run_loop.Run();
+}
+
+// Tests that the provider returns an empty set of origins when the machine
+// isn't joined to an AAD domain.
+TEST_F(CloudApProviderWinTest, NotJoined) {
+  CloudApProviderWin::SetSupportLevelForTesting(
+      CloudApProviderWin::SupportLevel::kDisabled);
+
+  CloudApProviderWin provider;
+
+  base::RunLoop run_loop;
+  base::MockCallback<CloudApProviderWin::FetchOriginsCallback> mock;
+  EXPECT_CALL(mock, Run(_))
+      .WillOnce([&run_loop](std::unique_ptr<std::vector<url::Origin>> origins) {
+        run_loop.Quit();
+        ASSERT_NE(origins.get(), nullptr);
+        EXPECT_TRUE(origins->empty());
+      });
+
+  provider.FetchOrigins(mock.Get());
+  run_loop.Run();
+}
+
+// Tests that the provider returns the two origins in the registry when the
+// machine is joined to an AAD domain.
+TEST_F(CloudApProviderWinTest, Joined) {
+  CloudApProviderWin::SetSupportLevelForTesting(
+      CloudApProviderWin::SupportLevel::kEnabled);
+
+  CloudApProviderWin provider;
+
+  base::RunLoop run_loop;
+  base::MockCallback<CloudApProviderWin::FetchOriginsCallback> mock;
+  EXPECT_CALL(mock, Run(_))
+      .WillOnce([&run_loop](std::unique_ptr<std::vector<url::Origin>> origins) {
+        run_loop.Quit();
+        ASSERT_NE(origins.get(), nullptr);
+        EXPECT_EQ(*origins, std::vector<url::Origin>(
+                                {url::Origin::Create(GURL("https://host1")),
+                                 url::Origin::Create(GURL("https://host2"))}));
+      });
+
+  provider.FetchOrigins(mock.Get());
+  run_loop.Run();
+}
+
+// Tests that the provider doesn't crash when the actual provider detection is
+// run.
+TEST_F(CloudApProviderWinTest, Platform) {
+  CloudApProviderWin provider;
+
+  base::RunLoop run_loop;
+  base::MockCallback<CloudApProviderWin::FetchOriginsCallback> mock;
+  EXPECT_CALL(mock, Run(_))
+      .WillOnce([&run_loop](std::unique_ptr<std::vector<url::Origin>> origins) {
+        run_loop.Quit();
+      });
+
+  provider.FetchOrigins(mock.Get());
+  run_loop.Run();
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.cc b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.cc
new file mode 100644
index 0000000..c2b19bc
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.cc
@@ -0,0 +1,62 @@
+// 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/cloud_ap_utils_win.h"
+
+#include <winerror.h>
+
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/win/registry.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace enterprise_auth {
+
+void AppendRegistryOrigins(HKEY root,
+                           const wchar_t* key_name,
+                           const wchar_t* value_name,
+                           std::vector<url::Origin>& origins) {
+  base::win::RegKey key;
+  auto result = key.Open(root, key_name, KEY_WOW64_64KEY | KEY_QUERY_VALUE);
+  if (result != ERROR_SUCCESS) {
+    if (result != ERROR_FILE_NOT_FOUND && DLOG_IS_ON(ERROR)) {
+      DPLOG(ERROR) << __func__ << " Failed to open " << key_name;
+    }
+    return;
+  }
+
+  std::vector<std::wstring> multi_string_values;
+  result = key.ReadValues(value_name, &multi_string_values);
+  if (result == ERROR_CANTREAD) {
+    multi_string_values.resize(1);
+    result = key.ReadValue(value_name, &multi_string_values[0]);
+  }
+  if (result != ERROR_SUCCESS) {
+    if (result != ERROR_FILE_NOT_FOUND && DLOG_IS_ON(ERROR)) {
+      DPLOG(ERROR) << __func__ << " Failed to read value " << value_name;
+    }
+    return;
+  }
+
+  for (const auto& value : multi_string_values) {
+    GURL url(base::AsStringPiece16(value));
+    if (url.is_valid() && url.SchemeIs(url::kHttpsScheme) && url.has_host() &&
+        url.EffectiveIntPort() ==
+            url::DefaultPortForScheme(url.scheme_piece().data(),
+                                      url.scheme_piece().length())) {
+      DVLOG(1) << __func__ << " Discovered MS Auth LoginUrl: \"" << url << "\"";
+      origins.push_back(url::Origin::Create(url));
+    } else {
+      DLOG(ERROR) << __func__ << " Ignoring invalid LoginUrl value: \"" << value
+                  << "\"";
+    }
+  }
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.h b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.h
new file mode 100644
index 0000000..dd45282
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.h
@@ -0,0 +1,29 @@
+// 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_CLOUD_AP_UTILS_WIN_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_CLOUD_AP_UTILS_WIN_H_
+
+#include <vector>
+
+#include "base/win/windows_types.h"
+
+namespace url {
+class Origin;
+}
+
+namespace enterprise_auth {
+
+// Reads URLs from the value `value_name` in the key `key_name` in the Windows
+// registry and appends their origins to `origins`. The named value must be of
+// type either REG_SZ or REG_MULTI_SZ. Only https URLs with a host part and no
+// or the default port are retained.
+void AppendRegistryOrigins(HKEY root,
+                           const wchar_t* key_name,
+                           const wchar_t* value_name,
+                           std::vector<url::Origin>& origins);
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_CLOUD_AP_UTILS_WIN_H_
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win_unittest.cc b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win_unittest.cc
new file mode 100644
index 0000000..4f60249
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_utils_win_unittest.cc
@@ -0,0 +1,131 @@
+// 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/cloud_ap_utils_win.h"
+
+#include <windows.h>
+
+#include <vector>
+
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace enterprise_auth {
+
+class AppendRegistryOriginsTest : public ::testing::Test {
+ protected:
+  AppendRegistryOriginsTest() = default;
+
+  void SetUp() override {
+    ASSERT_NO_FATAL_FAILURE(
+        registry_override_.OverrideRegistry(HKEY_CURRENT_USER));
+  }
+
+ private:
+  registry_util::RegistryOverrideManager registry_override_;
+};
+
+// Test that an origin is extracted from an SZ value.
+TEST_F(AppendRegistryOriginsTest, SingleSz) {
+  static constexpr wchar_t kRawValue[] = L"https://one";
+
+  ASSERT_EQ(
+      base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+          .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue), REG_SZ),
+      ERROR_SUCCESS);
+
+  std::vector<url::Origin> result;
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>(
+                        {url::Origin::Create(GURL("https://one"))}));
+}
+
+// Tests that existing values are preserved when extracting a single value.
+TEST_F(AppendRegistryOriginsTest, SingleSzAppend) {
+  static constexpr wchar_t kRawValue[] = L"https://two";
+
+  ASSERT_EQ(
+      base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+          .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue), REG_SZ),
+      ERROR_SUCCESS);
+
+  std::vector<url::Origin> result{url::Origin::Create(GURL("https://one"))};
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>(
+                        {url::Origin::Create(GURL("https://one")),
+                         url::Origin::Create(GURL("https://two"))}));
+}
+
+// Test that several values are extracted from a MUTLI_SZ value.
+TEST_F(AppendRegistryOriginsTest, MultiSz) {
+  static constexpr wchar_t kRawValue[] =
+      L"https://one\0https://two\0https://three\0";
+
+  ASSERT_EQ(base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+                .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue),
+                            REG_MULTI_SZ),
+            ERROR_SUCCESS);
+
+  std::vector<url::Origin> result;
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>(
+                        {url::Origin::Create(GURL("https://one")),
+                         url::Origin::Create(GURL("https://two")),
+                         url::Origin::Create(GURL("https://three"))}));
+}
+
+// Test that existing values are preserved when extracting multiple values.
+TEST_F(AppendRegistryOriginsTest, MultiSzAppend) {
+  static constexpr wchar_t kRawValue[] =
+      L"https://one\0https://two\0https://three\0";
+
+  ASSERT_EQ(base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+                .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue),
+                            REG_MULTI_SZ),
+            ERROR_SUCCESS);
+
+  std::vector<url::Origin> result{url::Origin::Create(GURL("https://zero"))};
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>(
+                        {url::Origin::Create(GURL("https://zero")),
+                         url::Origin::Create(GURL("https://one")),
+                         url::Origin::Create(GURL("https://two")),
+                         url::Origin::Create(GURL("https://three"))}));
+}
+
+// Test that a bogus value is ignored in an SZ value.
+TEST_F(AppendRegistryOriginsTest, SingleBogus) {
+  static constexpr wchar_t kRawValue[] = L"musiciscool";
+
+  ASSERT_EQ(base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+                .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue),
+                            REG_MULTI_SZ),
+            ERROR_SUCCESS);
+
+  std::vector<url::Origin> result;
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>());
+}
+
+// Test that bogus values are ignored in a MULTI_SZ value.
+TEST_F(AppendRegistryOriginsTest, MultiSzWithBogus) {
+  static constexpr wchar_t kRawValue[] =
+      L"https://one\0musiciscool\0https://two\0";
+
+  ASSERT_EQ(base::win::RegKey(HKEY_CURRENT_USER, L"TestKey", KEY_SET_VALUE)
+                .WriteValue(L"TestValue", &kRawValue[0], sizeof(kRawValue),
+                            REG_MULTI_SZ),
+            ERROR_SUCCESS);
+
+  std::vector<url::Origin> result;
+  AppendRegistryOrigins(HKEY_CURRENT_USER, L"TestKey", L"TestValue", result);
+  EXPECT_EQ(result, std::vector<url::Origin>(
+                        {url::Origin::Create(GURL("https://one")),
+                         url::Origin::Create(GURL("https://two"))}));
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.cc b/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.cc
new file mode 100644
index 0000000..deb5697
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.cc
@@ -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.
+
+#include "chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h"
+
+namespace enterprise_auth {
+
+MockPlatformAuthProvider::MockPlatformAuthProvider() = default;
+
+MockPlatformAuthProvider::~MockPlatformAuthProvider() {
+  Die();
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h b/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h
new file mode 100644
index 0000000..fabaebe
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h
@@ -0,0 +1,27 @@
+// 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_MOCK_PLATFORM_AUTH_PROVIDER_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_MOCK_PLATFORM_AUTH_PROVIDER_H_
+
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace enterprise_auth {
+
+class MockPlatformAuthProvider : public PlatformAuthProvider {
+ public:
+  MockPlatformAuthProvider();
+  ~MockPlatformAuthProvider() override;
+  MOCK_METHOD(void, FetchOrigins, (FetchOriginsCallback), (override));
+  MOCK_METHOD(void,
+              GetData,
+              (const GURL&, PlatformAuthProviderManager::GetDataCallback),
+              (override));
+  MOCK_METHOD(void, Die, ());
+};
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_MOCK_PLATFORM_AUTH_PROVIDER_H_
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.cc b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.cc
new file mode 100644
index 0000000..7d3e6f4
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.cc
@@ -0,0 +1,100 @@
+// 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_navigation_throttle.h"
+
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "net/cookies/cookie_util.h"
+#include "net/http/http_request_headers.h"
+
+namespace enterprise_auth {
+// static
+std::unique_ptr<PlatformAuthNavigationThrottle>
+PlatformAuthNavigationThrottle::MaybeCreateThrottleFor(
+    content::NavigationHandle* navigation_handle) {
+  // The manager is enabled when both the feature and policy are enabled.
+  // If the manager is not enabled, there is no point in creating a throttle
+  // since no auth data can be fetched.
+  if (!PlatformAuthProviderManager::GetInstance().IsEnabled())
+    return nullptr;
+
+  // To ensure that auth data is attached to both requests and redirects, the
+  // navigation throttle is created for all requests.
+  return std::make_unique<PlatformAuthNavigationThrottle>(navigation_handle);
+}
+
+PlatformAuthNavigationThrottle::PlatformAuthNavigationThrottle(
+    content::NavigationHandle* navigation_handle)
+    : content::NavigationThrottle(navigation_handle) {}
+
+PlatformAuthNavigationThrottle::~PlatformAuthNavigationThrottle() = default;
+
+content::NavigationThrottle::ThrottleCheckResult
+PlatformAuthNavigationThrottle::WillStartRequest() {
+  // The manager is enabled when both the feature and policy are enabled. This
+  // value is set in `ResourceRequest::TrustedParams`, which can only be
+  // modified at the start of a request (not during redirects).
+  navigation_handle()->SetAllowCookiesFromBrowser(
+      PlatformAuthProviderManager::GetInstance().IsEnabled());
+  return FetchHeaders();
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+PlatformAuthNavigationThrottle::WillRedirectRequest() {
+  for (auto header : attached_headers_)
+    navigation_handle()->RemoveRequestHeader(header);
+
+  attached_headers_.clear();
+  return FetchHeaders();
+}
+
+const char* PlatformAuthNavigationThrottle::GetNameForLogging() {
+  return "PlatformAuthNavigationThrottle";
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+PlatformAuthNavigationThrottle::FetchHeaders() {
+  fetch_headers_callback_ran_ = false;
+
+  // `PlatformAuthProviderManager` may be in the middle of an asynchronous state
+  // change, such as becoming disabled or updating its supported IdP origins, in
+  // which case the auth data fetch may still succeed.
+  if (!PlatformAuthProviderManager::GetInstance().IsEnabledFor(
+          navigation_handle()->GetURL()))
+    return content::NavigationThrottle::PROCEED;
+
+  PlatformAuthProviderManager::GetInstance().GetData(
+      navigation_handle()->GetURL(),
+      base::BindOnce(&PlatformAuthNavigationThrottle::FetchHeadersCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
+
+  // If the header fetch callback already ran it likely means that headers could
+  // not be fetched and `PlatformAuthProviderManager::GetData()` returned
+  // synchronously, so no need to defer.
+  if (fetch_headers_callback_ran_)
+    return content::NavigationThrottle::PROCEED;
+
+  is_deferred_ = true;
+  return content::NavigationThrottle::DEFER;
+}
+
+void PlatformAuthNavigationThrottle::FetchHeadersCallback(
+    net::HttpRequestHeaders auth_headers) {
+  net::HttpRequestHeaders::Iterator it(auth_headers);
+  while (it.GetNext()) {
+    attached_headers_.push_back(it.name());
+    navigation_handle()->SetRequestHeader(it.name(), it.value());
+  }
+
+  // Resume the deferred request.
+  if (is_deferred_) {
+    Resume();
+    is_deferred_ = false;
+  }
+  fetch_headers_callback_ran_ = true;
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.h b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.h
new file mode 100644
index 0000000..6492e19
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_NAVIGATION_THROTTLE_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_NAVIGATION_THROTTLE_H_
+
+#include "content/public/browser/navigation_throttle.h"
+#include "net/http/http_request_headers.h"
+
+namespace content {
+class NavigationHandle;
+}  // namespace content
+
+namespace net {
+class HttpRequestHeaders;
+}  // namespace net
+
+namespace enterprise_auth {
+class PlatformAuthNavigationThrottle : public content::NavigationThrottle {
+ public:
+  static std::unique_ptr<PlatformAuthNavigationThrottle> MaybeCreateThrottleFor(
+      content::NavigationHandle* navigation_handle);
+
+  explicit PlatformAuthNavigationThrottle(
+      content::NavigationHandle* navigation_handle);
+  PlatformAuthNavigationThrottle(const PlatformAuthNavigationThrottle&) =
+      delete;
+  PlatformAuthNavigationThrottle& operator=(
+      const PlatformAuthNavigationThrottle&) = delete;
+  ~PlatformAuthNavigationThrottle() override;
+
+  // content::NavigationThrottle implementation:
+  ThrottleCheckResult WillStartRequest() override;
+  ThrottleCheckResult WillRedirectRequest() override;
+  const char* GetNameForLogging() override;
+
+ private:
+  ThrottleCheckResult FetchHeaders();
+  void FetchHeadersCallback(net::HttpRequestHeaders auth_headers);
+
+  // Since `PlatformAuthProviderManager` can return data synchronously and
+  // asynchronously, these variables ensure that the throttle doesn't:
+  // - Defer if `FetchHeadersCallback` has already ran
+  // - Resume if `FetchHeadersCallback` runs before the throttle is deferred
+  bool fetch_headers_callback_ran_ = false;
+  bool is_deferred_ = false;
+
+  // Track which headers were attached to remove them from the request on
+  // redirects.
+  std::vector<std::string> attached_headers_;
+
+  base::WeakPtrFactory<PlatformAuthNavigationThrottle> weak_ptr_factory_{this};
+};
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle_unittest.cc b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle_unittest.cc
new file mode 100644
index 0000000..27b86c7
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_navigation_throttle_unittest.cc
@@ -0,0 +1,269 @@
+// 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_navigation_throttle.h"
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/task/thread_pool.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+#include "chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::NavigationThrottle;
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace {
+
+void EnableManager(enterprise_auth::PlatformAuthProviderManager& manager,
+                   bool enabled) {
+  base::MockCallback<base::OnceClosure> callback;
+  base::RunLoop run_loop;
+  EXPECT_CALL(callback, Run()).WillOnce([&run_loop]() {
+    run_loop.QuitWhenIdle();
+  });
+  manager.SetEnabled(enabled, callback.Get());
+  EXPECT_EQ(manager.IsEnabled(), enabled);
+  run_loop.Run();
+  EXPECT_EQ(manager.IsEnabled(), enabled);
+}
+
+}  // namespace
+
+namespace enterprise_auth {
+
+class PlatformAuthNavigationThrottleTest : public testing::Test {
+ public:
+  PlatformAuthNavigationThrottleTest() : mock_provider_(owned_provider_.get()) {
+    // Set up a default that will clear the raw pointer upon destruction.
+    ON_CALL(*mock_provider_, Die()).WillByDefault([this]() {
+      this->mock_provider_ = nullptr;
+    });
+    // Expect the provider to be destroyed at some point.
+    EXPECT_CALL(*mock_provider_, Die());
+  }
+
+  PlatformAuthProviderManager& manager() {
+    return PlatformAuthProviderManager::GetInstance();
+  }
+
+  std::unique_ptr<PlatformAuthNavigationThrottle> CreateThrottle(
+      content::NavigationHandle* navigation_handle) {
+    return std::make_unique<PlatformAuthNavigationThrottle>(navigation_handle);
+  }
+
+  content::WebContents* web_contents() const { return web_contents_.get(); }
+  content::RenderFrameHost* main_frame() const {
+    return web_contents()->GetPrimaryMainFrame();
+  }
+
+  std::unique_ptr<PlatformAuthProvider> TakeProvider() {
+    return std::move(owned_provider_);
+  }
+
+  ::testing::StrictMock<MockPlatformAuthProvider>* mock_provider() {
+    return mock_provider_;
+  }
+
+ protected:
+  void SetUp() override {
+    web_contents_ =
+        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
+  }
+
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  content::RenderViewHostTestEnabler rvh_test_enabler_;
+  TestingProfile profile_;
+  std::unique_ptr<content::WebContents> web_contents_;
+  std::unique_ptr<::testing::StrictMock<MockPlatformAuthProvider>>
+      owned_provider_{
+          std::make_unique<::testing::StrictMock<MockPlatformAuthProvider>>()};
+  ::testing::StrictMock<MockPlatformAuthProvider>* mock_provider_ = nullptr;
+};
+
+// The manager is disabled, so no origins or data are fetched.
+TEST_F(PlatformAuthNavigationThrottleTest, ManagerDisabled) {
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_)).Times(0);
+  ScopedSetProviderForTesting set_provider(TakeProvider());
+
+  content::MockNavigationHandle test_handle(GURL("https://www.example.test/"),
+                                            main_frame());
+  auto throttle = CreateThrottle(&test_handle);
+  EXPECT_CALL(test_handle, SetAllowCookiesFromBrowser(false)).Times(1);
+  EXPECT_CALL(test_handle, SetRequestHeader(_, _)).Times(0);
+
+  EXPECT_FALSE(manager().IsEnabled());
+  EXPECT_EQ(manager().GetOriginsForTesting(), std::vector<url::Origin>());
+  EXPECT_EQ(NavigationThrottle::PROCEED, throttle->WillStartRequest().action());
+  EXPECT_EQ("PlatformAuthNavigationThrottle", throttle->GetNameForLogging());
+}
+
+// The manager is enabled, so an origin fetch happens. No data is fetched when
+// an empty set of origins is returned.
+TEST_F(PlatformAuthNavigationThrottleTest, EmptyOrigins) {
+  ScopedSetProviderForTesting set_provider(TakeProvider());
+
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .WillOnce([](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(nullptr);
+      });
+  EnableManager(manager(), true);
+  EXPECT_TRUE(manager().IsEnabled());
+
+  content::MockNavigationHandle test_handle(GURL("https://www.example.test/"),
+                                            main_frame());
+  auto throttle = CreateThrottle(&test_handle);
+  EXPECT_CALL(test_handle, SetAllowCookiesFromBrowser(true)).Times(1);
+  EXPECT_CALL(test_handle, SetRequestHeader(_, _)).Times(0);
+
+  EXPECT_EQ(NavigationThrottle::PROCEED, throttle->WillStartRequest().action());
+  EXPECT_EQ(manager().GetOriginsForTesting(), std::vector<url::Origin>());
+}
+
+// The manager is enabled and a set of origins is returned, so the throttle
+// tries to fetch data. The fetched data is empty, so the throttle does not
+// attach any headers to the request.
+TEST_F(PlatformAuthNavigationThrottleTest, EmptyData) {
+  auto url = GURL("https://www.example.test/");
+  ScopedSetProviderForTesting set_provider(TakeProvider());
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .WillOnce([&url](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(std::make_unique<std::vector<url::Origin>>(
+            std::vector<url::Origin>{url::Origin::Create(url)}));
+      });
+  EnableManager(manager(), true);
+  EXPECT_TRUE(manager().IsEnabled());
+
+  content::MockNavigationHandle test_handle(url, main_frame());
+  auto throttle = CreateThrottle(&test_handle);
+  EXPECT_CALL(test_handle, SetAllowCookiesFromBrowser(true)).Times(1);
+  EXPECT_CALL(test_handle, SetRequestHeader(_, _)).Times(0);
+
+  // The provider returns an empty set of authentication headers.
+  EXPECT_CALL(*mock_provider(), GetData(_, _))
+      .WillOnce([](const GURL& url,
+                   PlatformAuthProviderManager::GetDataCallback callback) {
+        std::move(callback).Run(net::HttpRequestHeaders());
+      });
+
+  EXPECT_EQ(manager().GetOriginsForTesting(),
+            std::vector<url::Origin>({url::Origin::Create(url)}));
+  EXPECT_EQ(NavigationThrottle::PROCEED, throttle->WillStartRequest().action());
+}
+
+// The manager is enabled and a set of origins is returned, so the throttle
+// fetches data and attaches all received headers to the request. On redirect to
+// another origin, all previously added headers are removed from the request.
+TEST_F(PlatformAuthNavigationThrottleTest, DataReceived) {
+  const char kOldCookieValue[] = "old-cookie=old-data";
+  const char kCookieValue[] = "cookie-1=cookie-1-data; cookie-2=cookie-2-data";
+  const char kOldHeaderName[] = "old-header";
+  const char kOldHeaderValue[] = "old-header-data";
+  const char kHeader1Name[] = "header-1";
+  const char kHeader1Value[] = "header-1-data";
+  const char kHeader2Name[] = "header-2";
+  const char kHeader2Value[] = "header-2-data";
+
+  auto url = GURL("https://www.example.test/");
+  ScopedSetProviderForTesting set_provider(TakeProvider());
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .WillOnce([&url](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(std::make_unique<std::vector<url::Origin>>(
+            std::vector<url::Origin>{url::Origin::Create(url)}));
+      });
+  EnableManager(manager(), true);
+  EXPECT_TRUE(manager().IsEnabled());
+
+  content::MockNavigationHandle test_handle(url, main_frame());
+  // Set some existing request headers.
+  net::HttpRequestHeaders headers;
+  headers.SetHeader(net::HttpRequestHeaders::kCookie,
+                    base::JoinString({kOldCookieValue, kCookieValue}, "; "));
+  headers.SetHeader(kOldHeaderName, kOldHeaderValue);
+  headers.SetHeader(kHeader1Name, kHeader1Value);
+  test_handle.set_request_headers(std::move(headers));
+
+  auto throttle = CreateThrottle(&test_handle);
+  throttle->set_resume_callback_for_testing(base::DoNothing());
+
+  // Headers are added to the request whose origin matches the origin stored in
+  // the manager.
+  EXPECT_CALL(test_handle, SetAllowCookiesFromBrowser(true)).Times(1);
+  EXPECT_CALL(test_handle,
+              SetRequestHeader(net::HttpRequestHeaders::kCookie, kCookieValue))
+      .Times(1);
+  EXPECT_CALL(test_handle, SetRequestHeader(kHeader1Name, kHeader1Value))
+      .Times(1);
+  EXPECT_CALL(test_handle, SetRequestHeader(kHeader2Name, kHeader2Value))
+      .Times(1);
+
+  // The provider returns a non-empty set of authentication headers.
+  EXPECT_CALL(*mock_provider(), GetData(_, _))
+      .WillOnce([&kCookieValue, &kHeader1Name, &kHeader1Value, &kHeader2Name,
+                 &kHeader2Value](
+                    const GURL& url,
+                    PlatformAuthProviderManager::GetDataCallback callback) {
+        net::HttpRequestHeaders auth_headers;
+        auth_headers.SetHeader(net::HttpRequestHeaders::kCookie, kCookieValue);
+        auth_headers.SetHeader(kHeader1Name, kHeader1Value);
+        auth_headers.SetHeader(kHeader2Name, kHeader2Value);
+        base::ThreadPool::PostTask(base::BindOnce(
+            [](net::HttpRequestHeaders auth_headers,
+               PlatformAuthProviderManager::GetDataCallback callback) {
+              // Simulates a data fetch delay to cause the throttle to be
+              // deferred.
+              base::PlatformThread::Sleep(base::Milliseconds(10));
+              std::move(callback).Run(std::move(auth_headers));
+            },
+            std::move(auth_headers), std::move(callback)));
+      });
+
+  EXPECT_EQ(manager().GetOriginsForTesting(),
+            std::vector<url::Origin>({url::Origin::Create(url)}));
+  EXPECT_EQ(NavigationThrottle::DEFER, throttle->WillStartRequest().action());
+
+  // Ensure the async data fetch completes.
+  task_environment_.RunUntilIdle();
+
+  // The redirect is to another origin, so cookies and headers set by the
+  // throttle are removed.
+  EXPECT_CALL(test_handle,
+              RemoveRequestHeader(net::HttpRequestHeaders::kCookie))
+      .Times(1);
+  EXPECT_CALL(test_handle, RemoveRequestHeader(kHeader1Name)).Times(1);
+  EXPECT_CALL(test_handle, RemoveRequestHeader(kHeader2Name)).Times(1);
+
+  auto redirect_url = GURL("https://www.redirect.test/");
+  test_handle.set_url(redirect_url);
+  EXPECT_EQ(NavigationThrottle::PROCEED,
+            throttle->WillRedirectRequest().action());
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_provider.h b/chrome/browser/enterprise/platform_auth/platform_auth_provider.h
new file mode 100644
index 0000000..ff361ac
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_provider.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_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_PROVIDER_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_PROVIDER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+#include "url/origin.h"
+
+class GURL;
+
+namespace enterprise_auth {
+
+// An interface to authentication functionality provided by the platform.
+class PlatformAuthProvider {
+ public:
+  PlatformAuthProvider(const PlatformAuthProvider&) = delete;
+  PlatformAuthProvider& operator=(const PlatformAuthProvider&) = delete;
+  virtual ~PlatformAuthProvider() = default;
+
+  // A callback run with the results of a call to `FetchOrigins()`. If the arg
+  // is null, platform-based auth is not supported and no subsequent calls will
+  // ever succeed. Otherwise, the arg contains zero or more IdP (identity
+  // provider) / STS (security token service) origins to which auth (SSO) should
+  // be attempted.
+  using FetchOriginsCallback =
+      base::OnceCallback<void(std::unique_ptr<std::vector<url::Origin>>)>;
+
+  // Initiates an asynchronous fetch for IdP/STS origins. `on_fetch_complete`
+  // will be run on the caller's sequence with the results. Note: destruction of
+  // this fetcher instance is not guaranteed to prevent the callback from being
+  // run.
+  virtual void FetchOrigins(FetchOriginsCallback on_fetch_complete) = 0;
+
+  // Initiates an asynchronous fetch for proof of possession cookies and headers
+  // to present to `url`. `callback` will be run on the caller's sequence
+  // (possibly synchronously) with the results.
+  virtual void GetData(
+      const GURL& url,
+      PlatformAuthProviderManager::GetDataCallback callback) = 0;
+
+ protected:
+  PlatformAuthProvider() = default;
+};
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_PROVIDER_H_
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.cc b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.cc
new file mode 100644
index 0000000..da1386d
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.cc
@@ -0,0 +1,161 @@
+// 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_provider_manager.h"
+
+#include <stdint.h>
+
+#include <iterator>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/no_destructor.h"
+#include "base/numerics/clamped_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/ranges/algorithm.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/timer/elapsed_timer.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.h"
+#endif
+
+namespace enterprise_auth {
+
+namespace {
+
+std::unique_ptr<PlatformAuthProvider> MakeProvider() {
+#if BUILDFLAG(IS_WIN)
+  return std::make_unique<CloudApProviderWin>();
+#else
+  return nullptr;
+#endif
+}
+
+}  // namespace
+
+// static
+PlatformAuthProviderManager& PlatformAuthProviderManager::GetInstance() {
+  static base::NoDestructor<PlatformAuthProviderManager> instance;
+  return *instance;
+}
+
+void PlatformAuthProviderManager::SetEnabled(bool enabled,
+                                             base::OnceClosure on_complete) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Drop any pending fetch; its callback will never be run.
+  weak_factory_.InvalidateWeakPtrs();
+  on_enable_complete_.Reset();
+
+  // Drop origins if previously enabled.
+  if (!enabled && !origins_.empty())
+    origins_.clear();
+
+  enabled_ = enabled;
+
+  on_enable_complete_ = std::move(on_complete);
+  StartFetchOrigins();
+
+  // TODO(crbug.com/1246839): Users may add/remove WebAccounts, which could
+  // change the set of origins. Consider polling on a low-frequency timer and/or
+  // using a `WebAccountMonitor` (obtained from `WebAuthenticationCoreManager`)
+  // to watch for account removals. I don't see a way to watch for additions.
+}
+
+bool PlatformAuthProviderManager::IsEnabled() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return enabled_;
+}
+
+bool PlatformAuthProviderManager::IsEnabledFor(const GURL& url) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return base::Contains(origins_, url::Origin::Create(url));
+}
+
+void PlatformAuthProviderManager::GetData(const GURL& url,
+                                          GetDataCallback callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(url.is_valid());
+
+  // In general, callers should only request data for requests that are headed
+  // toward one of the origins stored in `origins_`. Given the async nature of
+  // changes to the set of origins, it's possible that a request could come in
+  // after the manager had been disabled or after a change to the set of
+  // origins.
+  if (!IsEnabledFor(url)) {
+    std::move(callback).Run(net::HttpRequestHeaders());
+  } else {
+    DCHECK(provider_);
+    provider_->GetData(url, std::move(callback));
+  }
+}
+
+PlatformAuthProviderManager::PlatformAuthProviderManager()
+    : PlatformAuthProviderManager(MakeProvider()) {}
+
+PlatformAuthProviderManager::PlatformAuthProviderManager(
+    std::unique_ptr<PlatformAuthProvider> provider)
+    : provider_(std::move(provider)) {}
+
+PlatformAuthProviderManager::~PlatformAuthProviderManager() = default;
+
+void PlatformAuthProviderManager::StartFetchOrigins() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (enabled_ && provider_) {
+    provider_->FetchOrigins(base::BindOnce(
+        &PlatformAuthProviderManager::OnOrigins, weak_factory_.GetWeakPtr()));
+  } else if (on_enable_complete_) {
+    std::move(on_enable_complete_).Run();
+  }
+}
+
+void PlatformAuthProviderManager::OnOrigins(
+    std::unique_ptr<std::vector<url::Origin>> origins) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::flat_set<url::Origin> new_origins;
+
+  if (!origins) {
+    // The provider is indicating that it can never return origins, so never ask
+    // for them again.
+    origins_.clear();
+    provider_.reset();
+  } else {
+    new_origins = base::flat_set<url::Origin>(std::move(*origins));
+  }
+
+  if (origins_ != new_origins)
+    origins_ = std::move(new_origins);
+
+  if (on_enable_complete_)
+    std::move(on_enable_complete_).Run();
+}
+
+std::unique_ptr<PlatformAuthProvider>
+PlatformAuthProviderManager::SetProviderForTesting(
+    std::unique_ptr<PlatformAuthProvider> provider) {
+  return std::exchange(provider_, std::move(provider));
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h
new file mode 100644
index 0000000..bbd266c
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h
@@ -0,0 +1,109 @@
+// 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_PROVIDER_MANAGER_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_PROVIDER_MANAGER_H_
+
+#include <stdint.h>
+
+#include <array>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "net/http/http_request_headers.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/origin.h"
+
+class GURL;
+
+namespace net {
+class HttpRequestHeaders;
+}  // namespace net
+
+namespace enterprise_auth {
+
+class PlatformAuthProvider;
+
+// Provides a means by which a browser can enable and use platform-based
+// authentication (for example Cloud AP SSO on Windows).
+class PlatformAuthProviderManager {
+ public:
+  // Returns the process-wide instance owned by `BrowserMainLoop`.
+  static PlatformAuthProviderManager& GetInstance();
+
+  // Note: do not construct a new instance on demand; use the one returned by
+  // `GetInstance()`.
+  PlatformAuthProviderManager();
+  PlatformAuthProviderManager(const PlatformAuthProviderManager&) = delete;
+  PlatformAuthProviderManager& operator=(const PlatformAuthProviderManager&) =
+      delete;
+  ~PlatformAuthProviderManager();
+
+  // Enables or disables platform-based auth asynchronously. `on_complete` will
+  // be run (possibly synchronously) once processing is complete. Conversely,
+  // `on_complete` will not be run if `SetEnabled()` is called again before a
+  // previous call is fully processed.
+  void SetEnabled(bool enabled, base::OnceClosure on_complete);
+
+  // Returns true if platform-based authentication is enabled.
+  bool IsEnabled() const;
+
+  // Returns true if `url` corresponds to one of the origins `origins_`.
+  bool IsEnabledFor(const GURL& url) const;
+
+  using GetDataCallback = base::OnceCallback<void(net::HttpRequestHeaders)>;
+
+  // Initiates an asynchronous fetch for proof of possession cookies and headers
+  // to present to `url`. Data will only be fetched if platform-based auth is
+  // enabled. `callback` will be run on the caller's sequence (possibly
+  // synchronously) with the results.
+  void GetData(const GURL& url, GetDataCallback callback) const;
+
+  const base::flat_set<url::Origin>& GetOriginsForTesting() { return origins_; }
+
+ private:
+  friend class ScopedSetProviderForTesting;
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerTest, DefaultDisabled);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerTest, DisableNoop);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerTest, NotSupported);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerTest,
+                           SupportedWithEmptyOrigins);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerTest, OriginRemoval);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthProviderManagerMetricsTest, Success);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthNavigationThrottleTest, ManagerDisabled);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthNavigationThrottleTest, EmptyOrigins);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthNavigationThrottleTest, EmptyData);
+  FRIEND_TEST_ALL_PREFIXES(PlatformAuthNavigationThrottleTest, DataReceived);
+
+  explicit PlatformAuthProviderManager(
+      std::unique_ptr<PlatformAuthProvider> provider);
+
+  void StartFetchOrigins();
+  void OnOrigins(std::unique_ptr<std::vector<url::Origin>> origins);
+
+  // Returns the previously-configured instance.
+  std::unique_ptr<PlatformAuthProvider> SetProviderForTesting(
+      std::unique_ptr<PlatformAuthProvider> provider);
+
+  std::unique_ptr<PlatformAuthProvider> provider_;
+  bool enabled_ = false;
+
+  base::OnceClosure on_enable_complete_;
+
+  // The collections of IdP/STS origins.
+  base::flat_set<url::Origin> origins_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<PlatformAuthProviderManager> weak_factory_{this};
+};
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_PROVIDER_MANAGER_H_
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_browsertest.cc b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_browsertest.cc
new file mode 100644
index 0000000..4f6afc4
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_browsertest.cc
@@ -0,0 +1,90 @@
+// 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_provider_manager.h"
+
+#include "chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h"
+#include "chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/http/http_request_headers.h"
+#include "net/test/embedded_test_server/default_handlers.h"
+
+using ::testing::_;
+
+namespace enterprise_auth {
+
+class PlatformAuthManagerBrowserTest : public InProcessBrowserTest {
+ public:
+  PlatformAuthManagerBrowserTest() = default;
+  ~PlatformAuthManagerBrowserTest() override = default;
+
+  PlatformAuthManagerBrowserTest(const PlatformAuthManagerBrowserTest&) =
+      delete;
+  PlatformAuthManagerBrowserTest& operator=(
+      const PlatformAuthManagerBrowserTest&) = delete;
+};
+
+IN_PROC_BROWSER_TEST_F(PlatformAuthManagerBrowserTest, Data) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Install a mock provider.
+  auto mock_provider =
+      std::make_unique<::testing::StrictMock<MockPlatformAuthProvider>>();
+  MockPlatformAuthProvider* unsafe_mock_provider = mock_provider.get();
+  ScopedSetProviderForTesting set_provider(std::move(mock_provider));
+
+  // Enable the manager with no origins configured.
+  {
+    EXPECT_CALL(*unsafe_mock_provider, FetchOrigins(_))
+        .WillOnce([](PlatformAuthProvider::FetchOriginsCallback callback) {
+          std::move(callback).Run(std::make_unique<std::vector<url::Origin>>());
+        });
+    base::RunLoop run_loop;
+    PlatformAuthProviderManager::GetInstance().SetEnabled(
+        true, run_loop.QuitClosure());
+    run_loop.Run();
+    ::testing::Mock::VerifyAndClearExpectations(unsafe_mock_provider);
+  }
+
+  // A request now should not invoke the provider for auth data.
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL("/empty.html")));
+  ::testing::Mock::VerifyAndClearExpectations(unsafe_mock_provider);
+
+  // Configure the manager with the embedded test server as the IdP origin.
+  {
+    EXPECT_CALL(*unsafe_mock_provider, FetchOrigins(_))
+        .WillOnce([origin = embedded_test_server()->GetOrigin()](
+                      PlatformAuthProvider::FetchOriginsCallback callback) {
+          std::move(callback).Run(std::make_unique<std::vector<url::Origin>>(
+              std::vector<url::Origin>{origin}));
+        });
+    base::RunLoop run_loop;
+    PlatformAuthProviderManager::GetInstance().SetEnabled(
+        true, run_loop.QuitClosure());
+    run_loop.Run();
+    ::testing::Mock::VerifyAndClearExpectations(unsafe_mock_provider);
+  }
+
+  // Issue a request to that origin and ensure that auth data is collected.
+  EXPECT_CALL(*unsafe_mock_provider, GetData(_, _))
+      .WillOnce([](const GURL& url,
+                   PlatformAuthProviderManager::GetDataCallback callback) {
+        net::HttpRequestHeaders auth_headers;
+        auth_headers.SetHeader(net::HttpRequestHeaders::kCookie,
+                               "new-cookie=new-cookie-data");
+        std::move(callback).Run(std::move(auth_headers));
+      });
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL("/empty.html")));
+  ::testing::Mock::VerifyAndClearExpectations(unsafe_mock_provider);
+
+  // The provider instance will be destroyed when `set_provider` is destroyed.
+  EXPECT_CALL(*unsafe_mock_provider, Die());
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_unittest.cc b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_unittest.cc
new file mode 100644
index 0000000..e09c00a
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_provider_manager_unittest.cc
@@ -0,0 +1,190 @@
+// 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_provider_manager.h"
+
+#include "base/numerics/safe_conversions.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/enterprise/platform_auth/mock_platform_auth_provider.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider.h"
+#include "net/http/http_request_headers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+
+namespace enterprise_auth {
+
+void EnableManager(PlatformAuthProviderManager& manager, bool enabled) {
+  base::MockCallback<base::OnceClosure> callback;
+  base::RunLoop run_loop;
+  EXPECT_CALL(callback, Run()).WillOnce([&run_loop]() {
+    run_loop.QuitWhenIdle();
+  });
+  manager.SetEnabled(enabled, callback.Get());
+  EXPECT_EQ(manager.IsEnabled(), enabled);
+  run_loop.Run();
+  EXPECT_EQ(manager.IsEnabled(), enabled);
+}
+
+class PlatformAuthProviderManagerTest : public ::testing::Test {
+ protected:
+  PlatformAuthProviderManagerTest() : mock_provider_(owned_provider_.get()) {
+    // Set up a default that will clear the raw pointer upon destruction.
+    ON_CALL(*mock_provider_, Die()).WillByDefault([this]() {
+      this->mock_provider_ = nullptr;
+    });
+    // Expect the provider to be destroyed at some point.
+    EXPECT_CALL(*mock_provider_, Die());
+  }
+
+  std::unique_ptr<PlatformAuthProvider> TakeProvider() {
+    return std::move(owned_provider_);
+  }
+
+  ::testing::StrictMock<MockPlatformAuthProvider>* mock_provider() {
+    return mock_provider_;
+  }
+
+ private:
+  std::unique_ptr<::testing::StrictMock<MockPlatformAuthProvider>>
+      owned_provider_{
+          std::make_unique<::testing::StrictMock<MockPlatformAuthProvider>>()};
+  ::testing::StrictMock<MockPlatformAuthProvider>* mock_provider_ = nullptr;
+  base::test::TaskEnvironment task_environment_;
+};
+
+// Tests that the manager is disabled by default.
+TEST_F(PlatformAuthProviderManagerTest, DefaultDisabled) {
+  PlatformAuthProviderManager manager(TakeProvider());
+
+  EXPECT_FALSE(manager.IsEnabled());
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+}
+
+// Tests that disabling when already disabled does nothing.
+TEST_F(PlatformAuthProviderManagerTest, DisableNoop) {
+  PlatformAuthProviderManager manager(TakeProvider());
+
+  EnableManager(manager, false);
+  EXPECT_FALSE(manager.IsEnabled());
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+}
+
+// Tests that enabling queries the provider and handles null. A second
+// enablement does not query again.
+TEST_F(PlatformAuthProviderManagerTest, NotSupported) {
+  PlatformAuthProviderManager manager(TakeProvider());
+
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .WillOnce([](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(nullptr);
+      });
+  EnableManager(manager, true);
+  EXPECT_EQ(mock_provider(), nullptr);
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+  EnableManager(manager, true);
+}
+
+// Tests that enabling queries the provider and handles an empty set of origins.
+// A second enablement repeats the query.
+TEST_F(PlatformAuthProviderManagerTest, SupportedWithEmptyOrigins) {
+  PlatformAuthProviderManager manager(TakeProvider());
+
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .Times(2)
+      .WillRepeatedly([](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(std::make_unique<std::vector<url::Origin>>());
+      });
+  EnableManager(manager, true);
+  EXPECT_NE(mock_provider(), nullptr);
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+  EnableManager(manager, true);
+  EXPECT_NE(mock_provider(), nullptr);
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+}
+
+// Tests that enabling queries the provider and handles non-empty sets of
+// origins. A second enablement repeats the query, which then returns no
+// origins.
+TEST_F(PlatformAuthProviderManagerTest, OriginRemoval) {
+  ::testing::Sequence sequence;
+
+  PlatformAuthProviderManager manager(TakeProvider());
+
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .InSequence(sequence)
+      .WillOnce([](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(
+            std::make_unique<std::vector<url::Origin>>(std::vector<url::Origin>{
+                url::Origin::Create(GURL("https://org"))}));
+      });
+  EXPECT_CALL(*mock_provider(), FetchOrigins(_))
+      .InSequence(sequence)
+      .WillOnce([](PlatformAuthProvider::FetchOriginsCallback callback) {
+        std::move(callback).Run(std::make_unique<std::vector<url::Origin>>());
+      });
+  EnableManager(manager, true);
+  EXPECT_NE(mock_provider(), nullptr);
+  EXPECT_EQ(
+      manager.GetOriginsForTesting(),
+      std::vector<url::Origin>({url::Origin::Create(GURL("https://org"))}));
+  EnableManager(manager, true);
+  EXPECT_NE(mock_provider(), nullptr);
+  EXPECT_EQ(manager.GetOriginsForTesting(), std::vector<url::Origin>());
+}
+
+// Verifies that the expected metrics are recorded on a cookie fetch.
+TEST(PlatformAuthProviderManagerMetricsTest, Success) {
+  const char kOldCookie[] = "old-cookie=old-cookie-data";
+  base::test::TaskEnvironment task_environment;
+  PlatformAuthProviderManager manager;
+
+  EnableManager(manager, true);
+  const auto origins = manager.GetOriginsForTesting();
+  if (origins.empty())
+    return;  // Nothing to test if there are no IdP/STS origins.
+
+  net::HttpRequestHeaders headers;
+  headers.SetHeader(net::HttpRequestHeaders::kCookie, kOldCookie);
+  base::HistogramTester histogram_tester;
+  base::RunLoop run_loop;
+  base::MockCallback<PlatformAuthProviderManager::GetDataCallback> mock;
+
+  EXPECT_CALL(mock, Run(_))
+      .WillOnce([&run_loop, &headers](net::HttpRequestHeaders auth_headers) {
+        headers = std::move(auth_headers);
+        run_loop.QuitWhenIdle();
+      });
+  manager.GetData(origins.begin()->GetURL(), mock.Get());
+  run_loop.Run();
+  ::testing::Mock::VerifyAndClearExpectations(&mock);
+
+  ASSERT_TRUE(headers.HasHeader(net::HttpRequestHeaders::kCookie));
+  std::string new_cookie;
+  headers.GetHeader(net::HttpRequestHeaders::kCookie, &new_cookie);
+
+  if (histogram_tester
+          .GetAllSamples("Enterprise.PlatformAuth.GetAuthData.FailureHresult")
+          .empty()) {
+    EXPECT_NE(kOldCookie, new_cookie);
+    // There should be a hit in the count histogram.
+    histogram_tester.ExpectTotalCount(
+        "Enterprise.PlatformAuth.GetAuthData.Count", 1);
+    histogram_tester.ExpectTotalCount(
+        "Enterprise.PlatformAuth.GetAuthData.SuccessTime", 1);
+  } else {
+    EXPECT_EQ(kOldCookie, new_cookie);
+    histogram_tester.ExpectTotalCount(
+        "Enterprise.PlatformAuth.GetAuthData.FailureHresult", 1);
+    histogram_tester.ExpectTotalCount(
+        "Enterprise.PlatformAuth.GetAuthData.FailureTime", 1);
+  }
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.cc b/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.cc
new file mode 100644
index 0000000..b7c52a0c
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.cc
@@ -0,0 +1,25 @@
+// 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/scoped_set_provider_for_testing.h"
+
+#include <utility>
+
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_provider_manager.h"
+
+namespace enterprise_auth {
+
+ScopedSetProviderForTesting::ScopedSetProviderForTesting(
+    std::unique_ptr<PlatformAuthProvider> provider)
+    : previous_(PlatformAuthProviderManager::GetInstance()
+                    .SetProviderForTesting(  // IN-TEST
+                        std::move(provider))) {}
+
+ScopedSetProviderForTesting::~ScopedSetProviderForTesting() {
+  PlatformAuthProviderManager::GetInstance().SetProviderForTesting(  // IN-TEST
+      std::move(previous_));
+}
+
+}  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.h b/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.h
new file mode 100644
index 0000000..9563dfc
--- /dev/null
+++ b/chrome/browser/enterprise/platform_auth/scoped_set_provider_for_testing.h
@@ -0,0 +1,31 @@
+// 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_SCOPED_SET_PROVIDER_FOR_TESTING_H_
+#define CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_SCOPED_SET_PROVIDER_FOR_TESTING_H_
+
+#include <memory>
+
+namespace enterprise_auth {
+
+class PlatformAuthProvider;
+
+// Overrides the process-wide `PlatformAuthProviderManager` instance's
+// `PlatformAuthProvider` implementation for test purposes.
+class ScopedSetProviderForTesting {
+ public:
+  explicit ScopedSetProviderForTesting(
+      std::unique_ptr<PlatformAuthProvider> platform);
+  ScopedSetProviderForTesting(const ScopedSetProviderForTesting&) = delete;
+  ScopedSetProviderForTesting& operator=(const ScopedSetProviderForTesting&) =
+      delete;
+  ~ScopedSetProviderForTesting();
+
+ private:
+  std::unique_ptr<PlatformAuthProvider> previous_;
+};
+
+}  // namespace enterprise_auth
+
+#endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_SCOPED_SET_PROVIDER_FOR_TESTING_H_
diff --git a/chrome/browser/extensions/activity_log/activity_log_enabled_unittest.cc b/chrome/browser/extensions/activity_log/activity_log_enabled_unittest.cc
index 4f24825..273bfdc 100644
--- a/chrome/browser/extensions/activity_log/activity_log_enabled_unittest.cc
+++ b/chrome/browser/extensions/activity_log/activity_log_enabled_unittest.cc
@@ -132,7 +132,7 @@
                            .Set("name", "Watchdog Extension ")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetID(kExtensionID)
           .Build();
   extension_service1->AddExtension(extension.get());
@@ -192,7 +192,7 @@
                            .Set("name", "Watchdog Extension ")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetID("fpofdchlamddhnajleknffcbmnjfahpg")
           .Build();
   extension_service1->AddExtension(extension.get());
@@ -239,7 +239,7 @@
                            .Set("name", "Watchdog Extension ")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetID(kExtensionID)
           .Build();
   extension_service->AddExtension(extension.get());
@@ -295,7 +295,7 @@
                            .Set("name", "Watchdog Extension ")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetID(kExtensionID)
           .Build();
   extension_service->AddExtension(extension.get());
diff --git a/chrome/browser/extensions/activity_log/activity_log_unittest.cc b/chrome/browser/extensions/activity_log/activity_log_unittest.cc
index 55e7999..d7b51557 100644
--- a/chrome/browser/extensions/activity_log/activity_log_unittest.cc
+++ b/chrome/browser/extensions/activity_log/activity_log_unittest.cc
@@ -317,7 +317,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   ActivityLog* activity_log = ActivityLog::GetInstance(profile());
@@ -412,7 +412,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
 
   ActivityLog* activity_log = ActivityLog::GetInstance(profile());
diff --git a/chrome/browser/extensions/activity_log/counting_policy_unittest.cc b/chrome/browser/extensions/activity_log/counting_policy_unittest.cc
index 224bd11..cdf083f9 100644
--- a/chrome/browser/extensions/activity_log/counting_policy_unittest.cc
+++ b/chrome/browser/extensions/activity_log/counting_policy_unittest.cc
@@ -400,7 +400,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   scoped_refptr<Action> action = new Action(extension->id(),
@@ -421,7 +421,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
 
@@ -542,7 +542,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   GURL gurl("http://www.google.com");
diff --git a/chrome/browser/extensions/activity_log/fullstream_ui_policy_unittest.cc b/chrome/browser/extensions/activity_log/fullstream_ui_policy_unittest.cc
index 0a5c249c..931bb13 100644
--- a/chrome/browser/extensions/activity_log/fullstream_ui_policy_unittest.cc
+++ b/chrome/browser/extensions/activity_log/fullstream_ui_policy_unittest.cc
@@ -345,7 +345,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   scoped_refptr<Action> action = new Action(extension->id(),
@@ -366,7 +366,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   GURL gurl("http://www.google.com");
@@ -404,7 +404,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   GURL gurl("http://www.google.com");
@@ -484,7 +484,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
 
@@ -776,7 +776,7 @@
                            .Set("name", "Test extension")
                            .Set("version", "1.0.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service_->AddExtension(extension.get());
   GURL gurl("http://www.google.com");
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 60feec0..337819d 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -215,7 +215,7 @@
                        base::Value(shill::kProviderOpenVpn));
     AddServiceToProfile(kUser1ProfilePath, "stub_vpn1");
 
-    AddService("stub_vpn2", "vpn2", shill::kTypeVPN, shill::kStateOffline);
+    AddService("stub_vpn2", "vpn2", shill::kTypeVPN, shill::kStateIdle);
     SetServiceProperty("stub_vpn2", shill::kProviderTypeProperty,
                        base::Value(shill::kProviderThirdPartyVpn));
     SetServiceProperty("stub_vpn2", shill::kProviderHostProperty,
@@ -1017,8 +1017,9 @@
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
                        GetCaptivePortalStatus) {
-  // Ethernet defaults to online. Set wifi1 to idle -> 'Offline', and wifi2 to
-  // redirect-found -> 'Portal'.
+  // Ethernet defaults to online.
+  // Set wifi1 to idle with captive portal status mapping 'Offline'.
+  // Set wifi2 to redirect-found with captive portal statu smapping 'Portal'.
   SetServiceProperty(kWifi1ServicePath, shill::kStateProperty,
                      base::Value(shill::kStateIdle));
   SetServiceProperty(kWifi2ServicePath, shill::kStateProperty,
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
index b76ae1ca..4e19af5 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
@@ -474,12 +474,14 @@
   InstallTracker* tracker = InstallTracker::Get(browser_context());
   DCHECK(tracker);
   bool is_installed =
-      extensions::ExtensionRegistry::Get(browser_context())->GetExtensionById(
-          details().id, extensions::ExtensionRegistry::EVERYTHING) != nullptr;
+      extensions::ExtensionRegistry::Get(browser_context())
+          ->GetExtensionById(details().id,
+                             extensions::ExtensionRegistry::EVERYTHING) !=
+      nullptr;
   if (is_installed || tracker->GetActiveInstall(details().id)) {
-    return RespondNow(BuildResponse(
-        api::webstore_private::RESULT_ALREADY_INSTALLED,
-        kAlreadyInstalledError));
+    return RespondNow(
+        BuildResponse(api::webstore_private::RESULT_ALREADY_INSTALLED,
+                      kAlreadyInstalledError));
   }
   ActiveInstallData install_data(details().id);
   scoped_active_install_ =
@@ -963,8 +965,8 @@
   approval_ =
       g_pending_approvals.Get().PopApproval(profile, params->expected_id);
   if (!approval_) {
-    return RespondNow(Error(kNoPreviousBeginInstallWithManifestError,
-                            params->expected_id));
+    return RespondNow(
+        Error(kNoPreviousBeginInstallWithManifestError, params->expected_id));
   }
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -1023,8 +1025,8 @@
     const std::string& error,
     WebstoreInstaller::FailureReason reason) {
   if (test_webstore_installer_delegate) {
-    test_webstore_installer_delegate->OnExtensionInstallFailure(
-        id, error, reason);
+    test_webstore_installer_delegate->OnExtensionInstallFailure(id, error,
+                                                                reason);
   }
 
   VLOG(1) << "Install failed, sending response";
@@ -1075,8 +1077,7 @@
 WebstorePrivateGetStoreLoginFunction::WebstorePrivateGetStoreLoginFunction() =
     default;
 
-WebstorePrivateGetStoreLoginFunction::
-    ~WebstorePrivateGetStoreLoginFunction() {}
+WebstorePrivateGetStoreLoginFunction::~WebstorePrivateGetStoreLoginFunction() {}
 
 ExtensionFunction::ResponseAction WebstorePrivateGetStoreLoginFunction::Run() {
   return RespondNow(ArgumentList(GetStoreLogin::Results::Create(
@@ -1086,8 +1087,7 @@
 WebstorePrivateSetStoreLoginFunction::WebstorePrivateSetStoreLoginFunction() =
     default;
 
-WebstorePrivateSetStoreLoginFunction::
-    ~WebstorePrivateSetStoreLoginFunction() {}
+WebstorePrivateSetStoreLoginFunction::~WebstorePrivateSetStoreLoginFunction() {}
 
 ExtensionFunction::ResponseAction WebstorePrivateSetStoreLoginFunction::Run() {
   std::unique_ptr<SetStoreLogin::Params> params(
@@ -1164,8 +1164,8 @@
 
 ExtensionFunction::ResponseAction
 WebstorePrivateGetEphemeralAppsEnabledFunction::Run() {
-  return RespondNow(ArgumentList(GetEphemeralAppsEnabled::Results::Create(
-      false)));
+  return RespondNow(
+      ArgumentList(GetEphemeralAppsEnabled::Results::Create(false)));
 }
 
 WebstorePrivateIsPendingCustodianApprovalFunction::
diff --git a/chrome/browser/extensions/content_security_policy_apitest.cc b/chrome/browser/extensions/content_security_policy_apitest.cc
index 728cd89..adf2f26 100644
--- a/chrome/browser/extensions/content_security_policy_apitest.cc
+++ b/chrome/browser/extensions/content_security_policy_apitest.cc
@@ -2,22 +2,187 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/test/result_catcher.h"
+#include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
 
 namespace extensions {
 
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ContentSecurityPolicy) {
+using ExtensionCspApiTest = ExtensionApiTest;
+
+IN_PROC_BROWSER_TEST_F(ExtensionCspApiTest, ContentSecurityPolicy) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("content_security_policy")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, DefaultContentSecurityPolicy) {
+IN_PROC_BROWSER_TEST_F(ExtensionCspApiTest, DefaultContentSecurityPolicy) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("default_content_security_policy")) <<
       message_;
 }
 
+// Tests that the Manifest V3 extension CSP allows localhost sources to be
+// embedded in extension pages.
+IN_PROC_BROWSER_TEST_F(ExtensionCspApiTest,
+                       ManifestV3AllowsLocalhostInPagesForUnpackedExtensions) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+
+  static constexpr char kManifest[] =
+      R"({
+           "name": "manifest v3 allows localhost and 127.0.0.1",
+           "version": "0.1",
+           "manifest_version": 3,
+           "content_security_policy": {
+             "extension_pages":
+                 "script-src 'self' http://localhost:* http://127.0.0.1:*; object-src 'self'"
+           }
+         })";
+  static constexpr char kPageHtmlTemplate[] =
+      R"(<html>
+           <script src="http://localhost:%d/extensions/local_includes/pass1.js">
+           </script>
+           <script src="http://127.0.0.1:%d/extensions/local_includes/pass2.js">
+           </script>
+           <script src="page.js"></script>
+         </html>)";
+  // Note that `jsPass1()` and `jsPass2()` are defined in the pass1.js and
+  // pass2.js resources that are included; they each call chrome.test.succeed().
+  static constexpr char kPageJs[] =
+      R"(chrome.test.runTests([
+           function testLocalHostInclude() {
+             window.jsPass1();
+           },
+           function testLocalHostIPInclude() {
+             window.jsPass2();
+           }]);)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kPageJs);
+  test_dir.WriteFile(
+      FILE_PATH_LITERAL("page.html"),
+      base::StringPrintf(kPageHtmlTemplate, embedded_test_server()->port(),
+                         embedded_test_server()->port()));
+
+  ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(),
+                               {.extension_url = "page.html"}, {}))
+      << message_;
+}
+
+// Tests that the Manifest V3 extension CSP allows for localhost sources being
+// imported from service workers.
+IN_PROC_BROWSER_TEST_F(
+    ExtensionCspApiTest,
+    ManifestV3AllowsLocalhostInServiceWorkersForUnpackedExtensions) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+
+  static constexpr char kManifest[] =
+      R"({
+           "name": "manifest v3 allows localhost and 127.0.0.1",
+           "version": "0.1",
+           "manifest_version": 3,
+           "content_security_policy": {
+             "extension_pages":
+                 "script-src 'self' http://localhost:* http://127.0.0.1:*; object-src 'self'"
+           },
+           "background": {"service_worker": "background.js", "type": "module"}
+         })";
+  static constexpr char kBackgroundJs[] =
+      R"(import {jsPass1} from
+             'http://localhost:%d/extensions/local_includes/module_pass1.js';
+         import {jsPass2} from
+             'http://localhost:%d/extensions/local_includes/module_pass2.js';
+         chrome.test.runTests([
+             function testLocalHostInclude() {
+               jsPass1();
+             },
+             function testLocalHostIPInclude() {
+               jsPass2();
+             }]);)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(
+      FILE_PATH_LITERAL("background.js"),
+      base::StringPrintf(kBackgroundJs, embedded_test_server()->port(),
+                         embedded_test_server()->port()));
+
+  ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(), {}, {})) << message_;
+}
+
+// Tests that MV3 disallows localhost in packed extensions.
+IN_PROC_BROWSER_TEST_F(ExtensionCspApiTest,
+                       ManifestV3DisallowsLocalhostForPackedExtensions) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+
+  static constexpr char kManifest[] =
+      R"({
+           "name": "manifest v3 allows localhost and 127.0.0.1",
+           "version": "0.1",
+           "manifest_version": 3,
+           "content_security_policy": {
+             "extension_pages":
+                 "script-src 'self' http://localhost:* http://127.0.0.1:*; object-src 'self'"
+           }
+         })";
+
+  static constexpr char kPageHtmlTemplate[] =
+      R"(<html>
+           <script src="http://localhost:%d/extensions/local_includes/pass1.js">
+           </script>
+           <script src="http://127.0.0.1:%d/extensions/local_includes/pass2.js">
+           </script>
+           <script src="page.js"></script>
+         </html>)";
+  // Note that `jsPass1()` and `jsPass2()` are defined in the pass1.js and
+  // pass2.js resources that are included. However, since the scripts should be
+  // blocked by CSP, the variables should be undefined.
+  static constexpr char kPageJs[] =
+      R"(chrome.test.runTests([
+           function testLocalHostInclude() {
+             chrome.test.assertTrue(!window.jsPass1);
+             chrome.test.succeed();
+           },
+           function testLocalHostIPInclude() {
+             chrome.test.assertTrue(!window.jsPass2);
+             chrome.test.succeed();
+           }]);)";
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kPageJs);
+  test_dir.WriteFile(
+      FILE_PATH_LITERAL("page.html"),
+      base::StringPrintf(kPageHtmlTemplate, embedded_test_server()->port(),
+                         embedded_test_server()->port()));
+
+  ResultCatcher result_catcher;
+  ChromeTestExtensionLoader test_loader(profile());
+  test_loader.set_pack_extension(true);
+  scoped_refptr<const Extension> extension =
+      test_loader.LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+  ASSERT_FALSE(Manifest::IsUnpackedLocation(extension->location()));
+
+  // Blocking the script load should emit a log.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::WebContentsConsoleObserver console_observer(web_contents);
+  console_observer.SetPattern("Refused to load the script '*");
+
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), extension->GetResourceURL("page.html")));
+  ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
+
+  EXPECT_EQ(2u, console_observer.messages().size());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index fb1bb2b2..b44a281 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -150,8 +150,8 @@
     expected_manifest_check_level_ = approval->manifest_check_level;
     if (expected_manifest_check_level_ !=
         WebstoreInstaller::MANIFEST_CHECK_LEVEL_NONE) {
-      expected_manifest_ = base::DictionaryValue::From(
-          base::Value::ToUniquePtrValue(approval->manifest->value()->Clone()));
+      expected_manifest_ = std::make_unique<base::Value::Dict>(
+          approval->manifest->value()->Clone());
     }
     expected_id_ = approval->extension_id;
   }
@@ -526,7 +526,7 @@
 void CrxInstaller::OnUnpackSuccess(
     const base::FilePath& temp_dir,
     const base::FilePath& extension_dir,
-    std::unique_ptr<base::DictionaryValue> original_manifest,
+    std::unique_ptr<base::Value::Dict> original_manifest,
     const Extension* extension,
     const SkBitmap& install_icon,
     declarative_net_request::RulesetInstallPrefs ruleset_install_prefs) {
@@ -542,7 +542,7 @@
 void CrxInstaller::OnUnpackSuccessOnSharedFileThread(
     base::FilePath temp_dir,
     base::FilePath extension_dir,
-    std::unique_ptr<base::DictionaryValue> original_manifest,
+    std::unique_ptr<base::Value::Dict> original_manifest,
     scoped_refptr<const Extension> extension,
     SkBitmap install_icon,
     declarative_net_request::RulesetInstallPrefs ruleset_install_prefs) {
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index ad78579..f4f028e 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -305,7 +305,7 @@
   void OnUnpackFailure(const CrxInstallError& error) override;
   void OnUnpackSuccess(const base::FilePath& temp_dir,
                        const base::FilePath& extension_dir,
-                       std::unique_ptr<base::DictionaryValue> original_manifest,
+                       std::unique_ptr<base::Value::Dict> original_manifest,
                        const Extension* extension,
                        const SkBitmap& install_icon,
                        declarative_net_request::RulesetInstallPrefs
@@ -364,7 +364,7 @@
   virtual void OnUnpackSuccessOnSharedFileThread(
       base::FilePath temp_dir,
       base::FilePath extension_dir,
-      std::unique_ptr<base::DictionaryValue> original_manifest,
+      std::unique_ptr<base::Value::Dict> original_manifest,
       scoped_refptr<const Extension> extension,
       SkBitmap install_icon,
       declarative_net_request::RulesetInstallPrefs ruleset_install_prefs);
@@ -420,7 +420,7 @@
   // A copy of the expected manifest, before any transformations like
   // localization have taken place. If |approved_| is true, then the extension's
   // manifest must match this for the install to proceed.
-  std::unique_ptr<base::DictionaryValue> expected_manifest_;
+  std::unique_ptr<base::Value::Dict> expected_manifest_;
 
   // The level of checking when comparing the actual manifest against
   // the |expected_manifest_|.
@@ -455,7 +455,7 @@
 
   // A copy of the unmodified original manifest, before any transformations like
   // localization have taken place.
-  std::unique_ptr<base::DictionaryValue> original_manifest_;
+  std::unique_ptr<base::Value::Dict> original_manifest_;
 
   // If valid, contains the current version of the extension we're
   // installing (for upgrades).
diff --git a/chrome/browser/extensions/crx_installer_browsertest.cc b/chrome/browser/extensions/crx_installer_browsertest.cc
index 96df51ab..9b4b42fb 100644
--- a/chrome/browser/extensions/crx_installer_browsertest.cc
+++ b/chrome/browser/extensions/crx_installer_browsertest.cc
@@ -127,10 +127,8 @@
 
 class MockInstallPrompt : public ExtensionInstallPrompt {
  public:
-  MockInstallPrompt(content::WebContents* web_contents,
-                    MockPromptProxy* proxy) :
-      ExtensionInstallPrompt(web_contents),
-      proxy_(proxy) {}
+  MockInstallPrompt(content::WebContents* web_contents, MockPromptProxy* proxy)
+      : ExtensionInstallPrompt(web_contents), proxy_(proxy) {}
 
   MockInstallPrompt(const MockInstallPrompt&) = delete;
   MockInstallPrompt& operator=(const MockInstallPrompt&) = delete;
@@ -261,7 +259,7 @@
                             .Set("name", "My First Extension")
                             .Set("version", version)
                             .Set("manifest_version", 2)
-                            .Build());
+                            .BuildDict());
     builder.SetID(extension_id);
     builder.SetPath(temp_dir.GetPath());
     extension_service()->AddExtension(builder.Build().get());
@@ -400,8 +398,8 @@
                        ExperimentalExtensionFromOutsideGallery) {
   // Non-gallery-installed extensions should lose their experimental
   // permission if the flag isn't enabled.
-  const Extension* extension = InstallExtension(
-      test_data_dir_.AppendASCII("experimental.crx"), 1);
+  const Extension* extension =
+      InstallExtension(test_data_dir_.AppendASCII("experimental.crx"), 1);
   ASSERT_TRUE(extension);
   EXPECT_FALSE(extension->permissions_data()->HasAPIPermission(
       mojom::APIPermissionID::kExperimental));
@@ -411,8 +409,8 @@
                        ExperimentalExtensionFromOutsideGalleryWithFlag) {
   // Non-gallery-installed extensions should maintain their experimental
   // permission if the flag is enabled.
-  const Extension* extension = InstallExtension(
-      test_data_dir_.AppendASCII("experimental.crx"), 1);
+  const Extension* extension =
+      InstallExtension(test_data_dir_.AppendASCII("experimental.crx"), 1);
   ASSERT_TRUE(extension);
   EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
       mojom::APIPermissionID::kExperimental));
@@ -468,8 +466,8 @@
 
   const int kNumDownloadsExpected = 1;
 
-  base::FilePath crx_path = PackExtension(
-      test_data_dir_.AppendASCII("common/background_page"));
+  base::FilePath crx_path =
+      PackExtension(test_data_dir_.AppendASCII("common/background_page"));
   ASSERT_FALSE(crx_path.empty());
   std::string crx_path_string(crx_path.value().begin(), crx_path.value().end());
   GURL url = GURL(std::string("file:///").append(crx_path_string));
@@ -555,14 +553,15 @@
     // is done.
     run_loop.Run();
     EXPECT_EQ(kTestData[i], mock_prompt->did_succeed());
-    EXPECT_EQ(kTestData[i], mock_prompt->confirmation_requested()) <<
-        kTestData[i];
+    EXPECT_EQ(kTestData[i], mock_prompt->confirmation_requested())
+        << kTestData[i];
     if (kTestData[i]) {
       EXPECT_EQ(std::u16string(), mock_prompt->error()) << kTestData[i];
     } else {
-      EXPECT_EQ(l10n_util::GetStringUTF16(
-          IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE),
-          mock_prompt->error()) << kTestData[i];
+      EXPECT_EQ(
+          l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE),
+          mock_prompt->error())
+          << kTestData[i];
     }
   }
 }
@@ -574,10 +573,9 @@
   ASSERT_TRUE(InstallExtension(crx_path, 1));
 
   const std::string extension_id("gllekhaobjnhgeagipipnkpmmmpchacm");
-  ExtensionRegistry* registry = ExtensionRegistry::Get(
-      browser()->profile());
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser()->profile());
   const extensions::Extension* extension =
-     registry->enabled_extensions().GetByID(extension_id);
+      registry->enabled_extensions().GetByID(extension_id);
   ASSERT_TRUE(extension);
   EXPECT_EQ(extension_id, extension->id());
 
@@ -592,8 +590,7 @@
 
   ExtensionService* service = extension_service();
   ASSERT_TRUE(service);
-  ExtensionRegistry* registry = ExtensionRegistry::Get(
-      browser()->profile());
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser()->profile());
   ASSERT_TRUE(registry);
 
   // Install version 1 of the test extension. This extension does not have
@@ -602,7 +599,7 @@
   ASSERT_FALSE(v1_path.empty());
   ASSERT_TRUE(InstallExtension(v1_path, 1));
   const extensions::Extension* extension =
-     registry->enabled_extensions().GetByID(extension_id);
+      registry->enabled_extensions().GetByID(extension_id);
   ASSERT_TRUE(extension);
   ASSERT_EQ(extension_id, extension->id());
   ASSERT_EQ("1.0", extension->version().GetString());
@@ -654,7 +651,7 @@
   blocklist_db->SetUnsafe(extension_id);
 
   base::FilePath crx_path = test_data_dir_.AppendASCII("theme_hidpi_crx")
-                                          .AppendASCII("theme_hidpi.crx");
+                                .AppendASCII("theme_hidpi.crx");
   EXPECT_FALSE(InstallExtension(crx_path, 0));
 
   auto installation_failure =
@@ -975,8 +972,7 @@
 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, KioskOnlyTest) {
   base::ScopedAllowBlockingForTesting allow_io;
   // kiosk_only is allowlisted from non-chromeos.
-  base::FilePath crx_path =
-      test_data_dir_.AppendASCII("kiosk/kiosk_only.crx");
+  base::FilePath crx_path = test_data_dir_.AppendASCII("kiosk/kiosk_only.crx");
   EXPECT_FALSE(InstallExtension(crx_path, 0));
   // Simulate ChromeOS kiosk mode. |scoped_user_manager| will take over
   // lifetime of |user_manager|.
@@ -1007,8 +1003,7 @@
 
   std::string extension_id = extension->id();
   UninstallExtension(extension_id);
-  ExtensionRegistry* registry = ExtensionRegistry::Get(
-      browser()->profile());
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser()->profile());
   EXPECT_FALSE(registry->enabled_extensions().GetByID(extension_id));
 
   content::RunAllTasksUntilIdle();
diff --git a/chrome/browser/extensions/error_console/error_console_unittest.cc b/chrome/browser/extensions/error_console/error_console_unittest.cc
index 06845760..a773cad 100644
--- a/chrome/browser/extensions/error_console/error_console_unittest.cc
+++ b/chrome/browser/extensions/error_console/error_console_unittest.cc
@@ -86,7 +86,7 @@
                            .Set("name", "apps dev tools")
                            .Set("version", "0.2.0")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetID(kAppsDeveloperToolsExtensionId)
           .Build();
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_.get());
@@ -201,7 +201,7 @@
                            .Set("name", "unpacked")
                            .Set("version", "0.0.1")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetLocation(mojom::ManifestLocation::kUnpacked)
           .SetID(crx_file::id_util::GenerateId("unpacked"))
           .Build();
@@ -211,7 +211,7 @@
                            .Set("name", "packed")
                            .Set("version", "0.0.1")
                            .Set("manifest_version", 2)
-                           .Build())
+                           .BuildDict())
           .SetLocation(mojom::ManifestLocation::kInternal)
           .SetID(crx_file::id_util::GenerateId("packed"))
           .Build();
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index d40ee31a..41b4d91 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -321,12 +321,13 @@
   DictionaryBuilder manifest;
   manifest.Set("name", name).Set("version", "1").Set("manifest_version", 2);
   if (action_key)
-    manifest.Set(action_key, DictionaryBuilder().Build());
+    manifest.Set(action_key, DictionaryBuilder().BuildDict());
   if (!host_permission.empty())
-    manifest.Set("permissions", ListBuilder().Append(host_permission).Build());
+    manifest.Set("permissions",
+                 ListBuilder().Append(host_permission).BuildList());
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(manifest.Build())
+          .SetManifest(manifest.BuildDict())
           .SetID(crx_file::id_util::GenerateId(name))
           .SetLocation(location)
           .Build();
@@ -512,19 +513,18 @@
   InitializeEmptyExtensionService();
 
   std::string name("component");
-  std::unique_ptr<base::DictionaryValue> manifest =
+  base::Value::Dict manifest =
       DictionaryBuilder()
           .Set("name", name)
           .Set("version", "1")
           .Set("manifest_version", 2)
-          .Set("browser_action", DictionaryBuilder().Build())
-          .Build();
+          .Set("browser_action", DictionaryBuilder().BuildDict())
+          .BuildDict();
 
   {
     scoped_refptr<const Extension> extension =
         ExtensionBuilder()
-            .SetManifest(base::DictionaryValue::From(
-                base::Value::ToUniquePtrValue(manifest->Clone())))
+            .SetManifest(manifest.Clone())
             .SetID(crx_file::id_util::GenerateId("component"))
             .SetLocation(ManifestLocation::kComponent)
             .Build();
@@ -554,7 +554,7 @@
   {
     // Check that a component extension with an options page does have the
     // options menu item, and it is enabled.
-    manifest->SetStringKey("options_page", "options_page.html");
+    manifest.Set("options_page", "options_page.html");
     scoped_refptr<const Extension> extension =
         ExtensionBuilder()
             .SetManifest(std::move(manifest))
diff --git a/chrome/browser/extensions/extension_error_controller_unittest.cc b/chrome/browser/extensions/extension_error_controller_unittest.cc
index 427610b..e573f34 100644
--- a/chrome/browser/extensions/extension_error_controller_unittest.cc
+++ b/chrome/browser/extensions/extension_error_controller_unittest.cc
@@ -94,10 +94,11 @@
 // Builds and returns a simple extension.
 scoped_refptr<const Extension> BuildExtension() {
   return ExtensionBuilder()
-      .SetManifest(DictionaryBuilder().Set("name", "My Wonderful Extension")
-                                      .Set("version", "0.1.1.0")
-                                      .Set("manifest_version", 2)
-                                      .Build())
+      .SetManifest(DictionaryBuilder()
+                       .Set("name", "My Wonderful Extension")
+                       .Set("version", "0.1.1.0")
+                       .Set("manifest_version", 2)
+                       .BuildDict())
       .Build();
 }
 
diff --git a/chrome/browser/extensions/extension_install_prompt_unittest.cc b/chrome/browser/extensions/extension_install_prompt_unittest.cc
index d0a58eb..7172020 100644
--- a/chrome/browser/extensions/extension_install_prompt_unittest.cc
+++ b/chrome/browser/extensions/extension_install_prompt_unittest.cc
@@ -118,7 +118,7 @@
                            .Set("version", "1.0")
                            .Set("manifest_version", 2)
                            .Set("description", "Random Ext")
-                           .Build())
+                           .BuildDict())
           .Build();
 
   content::TestWebContentsFactory factory;
@@ -139,16 +139,17 @@
        DelegatedPromptShowsOptionalPermissions) {
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(DictionaryBuilder()
-                           .Set("name", "foo")
-                           .Set("version", "1.0")
-                           .Set("manifest_version", 2)
-                           .Set("description", "Random Ext")
-                           .Set("permissions",
-                                ListBuilder().Append("clipboardRead").Build())
-                           .Set("optional_permissions",
-                                ListBuilder().Append("tabs").Build())
-                           .Build())
+          .SetManifest(
+              DictionaryBuilder()
+                  .Set("name", "foo")
+                  .Set("version", "1.0")
+                  .Set("manifest_version", 2)
+                  .Set("description", "Random Ext")
+                  .Set("permissions",
+                       ListBuilder().Append("clipboardRead").BuildList())
+                  .Set("optional_permissions",
+                       ListBuilder().Append("tabs").BuildList())
+                  .BuildDict())
           .Build();
 
   content::TestWebContentsFactory factory;
diff --git a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
index 25dd5ca..7297cac 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
@@ -203,7 +203,7 @@
                             .Set("name", std::string("Extension " + index))
                             .Set("version", "1.0")
                             .Set("manifest_version", 2)
-                            .Build());
+                            .BuildDict());
     builder.SetLocation(location);
     builder.SetID(id);
     service_->AddExtension(builder.Build().get());
@@ -224,8 +224,8 @@
             .Set("manifest_version", 2)
             .Set("browser_action", DictionaryBuilder()
                                        .Set("default_title", "Default title")
-                                       .Build())
-            .Build());
+                                       .BuildDict())
+            .BuildDict());
     builder.SetLocation(location);
     builder.SetID(id);
     service_->AddExtension(builder.Build().get());
@@ -247,8 +247,8 @@
                             .Set("chrome_settings_overrides",
                                  DictionaryBuilder()
                                      .Set("homepage", "http://www.google.com")
-                                     .Build())
-                            .Build());
+                                     .BuildDict())
+                            .BuildDict());
     builder.SetLocation(location);
     builder.SetID(id);
     service_->AddExtension(builder.Build().get());
@@ -270,10 +270,11 @@
             .Set("manifest_version", 2)
             .Set("chrome_settings_overrides",
                  DictionaryBuilder()
-                     .Set("startup_pages",
-                          ListBuilder().Append("http://www.google.com").Build())
-                     .Build())
-            .Build());
+                     .Set("startup_pages", ListBuilder()
+                                               .Append("http://www.google.com")
+                                               .BuildList())
+                     .BuildDict())
+            .BuildDict());
     builder.SetLocation(location);
     builder.SetID(id);
     service_->AddExtension(builder.Build().get());
@@ -294,8 +295,8 @@
             .Set("version", "1.0")
             .Set("manifest_version", 2)
             .Set("chrome_url_overrides",
-                 DictionaryBuilder().Set("newtab", "Default.html").Build())
-            .Build());
+                 DictionaryBuilder().Set("newtab", "Default.html").BuildDict())
+            .BuildDict());
 
     builder.SetLocation(location);
     builder.SetID(id);
@@ -316,8 +317,8 @@
             .Set("name", std::string("Extension " + index))
             .Set("version", "1.0")
             .Set("manifest_version", 2)
-            .Set("permissions", ListBuilder().Append("proxy").Build())
-            .Build());
+            .Set("permissions", ListBuilder().Append("proxy").BuildList())
+            .BuildDict());
 
     builder.SetLocation(location);
     builder.SetID(id);
diff --git a/chrome/browser/extensions/extension_navigation_throttle_unittest.cc b/chrome/browser/extensions/extension_navigation_throttle_unittest.cc
index 00ad5a5..30035411 100644
--- a/chrome/browser/extensions/extension_navigation_throttle_unittest.cc
+++ b/chrome/browser/extensions/extension_navigation_throttle_unittest.cc
@@ -80,10 +80,12 @@
         .Set("description", "something")
         .Set("version", "0.1")
         .Set("manifest_version", 2)
-        .Set("web_accessible_resources",
-             ListBuilder().Append(kAccessible).Append(kAccessibleDir).Build());
+        .Set("web_accessible_resources", ListBuilder()
+                                             .Append(kAccessible)
+                                             .Append(kAccessibleDir)
+                                             .BuildList());
     extension_ = ExtensionBuilder()
-                     .SetManifest(manifest.Build())
+                     .SetManifest(manifest.BuildDict())
                      .SetID(crx_file::id_util::GenerateId("foo"))
                      .Build();
     ASSERT_TRUE(extension_);
diff --git a/chrome/browser/extensions/extension_prefs_unittest.cc b/chrome/browser/extensions/extension_prefs_unittest.cc
index f809ff002..b1476e4 100644
--- a/chrome/browser/extensions/extension_prefs_unittest.cc
+++ b/chrome/browser/extensions/extension_prefs_unittest.cc
@@ -497,7 +497,7 @@
     std::unique_ptr<ExtensionInfo> info(prefs()->GetDelayedInstallInfo(id));
     ASSERT_TRUE(info);
     const std::string* version =
-        info->extension_manifest->GetDict().FindString("version");
+        info->extension_manifest->FindString("version");
     ASSERT_TRUE(version);
     ASSERT_EQ("1." + base::NumberToString(num), *version);
     ASSERT_EQ(base::NumberToString(num),
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
index 04f6365..ab1cb6ea 100644
--- a/chrome/browser/extensions/extension_protocols_unittest.cc
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -108,16 +108,17 @@
 }
 
 scoped_refptr<Extension> CreateWebStoreExtension() {
-  std::unique_ptr<base::DictionaryValue> manifest =
+  base::Value::Dict manifest =
       DictionaryBuilder()
           .Set("name", "WebStore")
           .Set("version", "1")
           .Set("manifest_version", 2)
-          .Set("icons",
-               DictionaryBuilder().Set("16", "webstore_icon_16.png").Build())
+          .Set(
+              "icons",
+              DictionaryBuilder().Set("16", "webstore_icon_16.png").BuildDict())
           .Set("web_accessible_resources",
-               ListBuilder().Append("webstore_icon_16.png").Build())
-          .Build();
+               ListBuilder().Append("webstore_icon_16.png").BuildList())
+          .BuildDict();
 
   base::FilePath path;
   EXPECT_TRUE(base::PathService::Get(chrome::DIR_RESOURCES, &path));
@@ -125,7 +126,7 @@
 
   std::string error;
   scoped_refptr<Extension> extension(
-      Extension::Create(path, mojom::ManifestLocation::kComponent, *manifest,
+      Extension::Create(path, mojom::ManifestLocation::kComponent, manifest,
                         Extension::NO_FLAGS, &error));
   EXPECT_TRUE(extension.get()) << error;
   return extension;
@@ -134,7 +135,7 @@
 scoped_refptr<const Extension> CreateTestResponseHeaderExtension() {
   return ExtensionBuilder("An extension with web-accessible resources")
       .SetManifestKey("web_accessible_resources",
-                      ListBuilder().Append("test.dat").Build())
+                      ListBuilder().Append("test.dat").BuildList())
       .SetPath(GetTestPath("response_headers"))
       .Build();
 }
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 26fdd59..296b14b 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -295,16 +295,15 @@
               base::WriteFile(file, data.c_str(), data.size()));
   }
 
-  std::unique_ptr<base::DictionaryValue> manifest =
-      DictionaryBuilder()
-          .Set(keys::kName, "Test extension")
-          .Set(keys::kVersion, "1.0")
-          .Set(keys::kManifestVersion, 2)
-          .Build();
+  base::Value::Dict manifest = DictionaryBuilder()
+                                   .Set(keys::kName, "Test extension")
+                                   .Set(keys::kVersion, "1.0")
+                                   .Set(keys::kManifestVersion, 2)
+                                   .BuildDict();
 
   // Persist manifest file.
   base::FilePath manifest_path = extension_dir.Append(kManifestFilename);
-  JSONFileValueSerializer(manifest_path).Serialize(*manifest);
+  JSONFileValueSerializer(manifest_path).Serialize(manifest);
   EXPECT_TRUE(base::PathExists(manifest_path));
 }
 
@@ -3794,8 +3793,8 @@
 
   {
     ManagementPrefUpdater pref(profile_->GetTestingPrefService());
-    // // Blocklist everything.
-    // pref.SetBlocklistedByDefault(true);
+    // Blocklist everything.
+    pref.SetBlocklistedByDefault(true);
     // Mark good.crx for force-installation.
     pref.SetIndividualExtensionAutoInstalled(
         good_crx, "http://example.com/update_url", true);
@@ -3804,18 +3803,12 @@
   // Have policy force-install an extension.
   MockExternalProvider* provider =
       AddMockExternalProvider(ManifestLocation::kExternalPolicyDownload);
-  provider->UpdateOrAddExtension(
-      good_crx, "1.0.0.0", data_dir().AppendASCII("good_crx"));
+  provider->UpdateOrAddExtension(good_crx, "1.0.0.0",
+                                 data_dir().AppendASCII("good.crx"));
 
   // Reloading extensions should find our externally registered extension
   // and install it.
-  // WaitForExternalExtensionInstalled();
-  // Installation actually fails with
-  // "Package is invalid: 'CRX_FILE_NOT_READABLE'"
-  // Thus commenting out the call above.
-  // TODO(crbug.com/1378548): Test seems to be broken as it passes even though
-  // the extension never got installed in the first place. And maybe that was
-  // always the case?
+  WaitForExternalExtensionInstalled(good_crx);
 
   AssertExtensionBlocksAndUnblocks(false, good_crx);
 }
@@ -4099,10 +4092,10 @@
   // Create a fake extension to be loaded as though it were read from prefs.
   base::FilePath path =
       data_dir().AppendASCII("management").AppendASCII("simple_extension");
-  base::DictionaryValue manifest;
-  manifest.SetStringPath(keys::kName, "simple_extension");
-  manifest.SetStringPath(keys::kVersion, "1");
-  manifest.SetIntPath(keys::kManifestVersion, 2);
+  base::Value::Dict manifest;
+  manifest.Set(keys::kName, "simple_extension");
+  manifest.Set(keys::kVersion, "1");
+  manifest.Set(keys::kManifestVersion, 2);
   // UNPACKED is for extensions loaded from a directory. We use it here, even
   // though we're testing loading from prefs, so that we don't need to provide
   // an extension key.
@@ -7644,7 +7637,7 @@
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("Shared Module")
           .SetManifestPath("export.resources",
-                           ListBuilder().Append("foo.js").Build())
+                           ListBuilder().Append("foo.js").BuildList())
           .AddFlags(Extension::FROM_WEBSTORE)
           .Build();
 
diff --git a/chrome/browser/extensions/extension_shared_array_buffer_browsertest.cc b/chrome/browser/extensions/extension_shared_array_buffer_browsertest.cc
index 5dd08cc0..97a79dba 100644
--- a/chrome/browser/extensions/extension_shared_array_buffer_browsertest.cc
+++ b/chrome/browser/extensions/extension_shared_array_buffer_browsertest.cc
@@ -123,21 +123,21 @@
   if (is_cross_origin_isolated) {
     builder
         .Set("cross_origin_opener_policy",
-             DictionaryBuilder().Set("value", "same-origin").Build())
+             DictionaryBuilder().Set("value", "same-origin").BuildDict())
         .Set("cross_origin_embedder_policy",
-             DictionaryBuilder().Set("value", "require-corp").Build());
+             DictionaryBuilder().Set("value", "require-corp").BuildDict());
   }
 
   DictionaryBuilder background_builder;
   background_builder.Set("scripts",
-                         ListBuilder().Append("background.js").Build());
+                         ListBuilder().Append("background.js").BuildList());
 
   if (is_platform_app) {
     builder.Set("app", DictionaryBuilder()
-                           .Set("background", background_builder.Build())
-                           .Build());
+                           .Set("background", background_builder.BuildDict())
+                           .BuildDict());
   } else {
-    builder.Set("background", background_builder.Build());
+    builder.Set("background", background_builder.BuildDict());
   }
 
   test_dir().WriteManifest(builder.ToJSON());
diff --git a/chrome/browser/extensions/extension_web_ui_unittest.cc b/chrome/browser/extensions/extension_web_ui_unittest.cc
index cb8a7557..e4ab7b4 100644
--- a/chrome/browser/extensions/extension_web_ui_unittest.cc
+++ b/chrome/browser/extensions/extension_web_ui_unittest.cc
@@ -89,10 +89,10 @@
       .Set(manifest_keys::kVersion, "0.1")
       .Set(manifest_keys::kManifestVersion, 2)
       .Set(api::chrome_url_overrides::ManifestKeys::kChromeUrlOverrides,
-           DictionaryBuilder().Set("bookmarks", kOverrideResource).Build());
+           DictionaryBuilder().Set("bookmarks", kOverrideResource).BuildDict());
   scoped_refptr<const Extension> ext_unpacked(
       ExtensionBuilder()
-          .SetManifest(manifest.Build())
+          .SetManifest(manifest.BuildDict())
           .SetLocation(ManifestLocation::kUnpacked)
           .SetID("abcdefghijabcdefghijabcdefghijaa")
           .Build());
@@ -124,11 +124,12 @@
   manifest2.Set(manifest_keys::kName, "ext2")
       .Set(manifest_keys::kVersion, "0.1")
       .Set(manifest_keys::kManifestVersion, 2)
-      .Set(api::chrome_url_overrides::ManifestKeys::kChromeUrlOverrides,
-           DictionaryBuilder().Set("bookmarks", kOverrideResource2).Build());
+      .Set(
+          api::chrome_url_overrides::ManifestKeys::kChromeUrlOverrides,
+          DictionaryBuilder().Set("bookmarks", kOverrideResource2).BuildDict());
   scoped_refptr<const Extension> ext_component(
       ExtensionBuilder()
-          .SetManifest(manifest2.Build())
+          .SetManifest(manifest2.BuildDict())
           .SetLocation(ManifestLocation::kComponent)
           .SetID("bbabcdefghijabcdefghijabcdefghij")
           .Build());
@@ -265,8 +266,8 @@
 
 TEST_F(ExtensionWebUITest, TestNumExtensionsOverridingURL) {
   auto load_extension_overriding_newtab = [this](const char* name) {
-    std::unique_ptr<base::Value> chrome_url_overrides =
-        DictionaryBuilder().Set("newtab", "newtab.html").Build();
+    base::Value::Dict chrome_url_overrides =
+        DictionaryBuilder().Set("newtab", "newtab.html").BuildDict();
     scoped_refptr<const Extension> extension =
         ExtensionBuilder(name)
             .SetLocation(ManifestLocation::kInternal)
diff --git a/chrome/browser/extensions/forced_extensions/force_installed_metrics_unittest.cc b/chrome/browser/extensions/forced_extensions/force_installed_metrics_unittest.cc
index 5bdc3ab..b6361ed 100644
--- a/chrome/browser/extensions/forced_extensions/force_installed_metrics_unittest.cc
+++ b/chrome/browser/extensions/forced_extensions/force_installed_metrics_unittest.cc
@@ -182,15 +182,15 @@
   }
 
   void SetupExtensionManagementPref() {
-    std::unique_ptr<base::DictionaryValue> extension_entry =
+    base::Value::Dict extension_entry =
         DictionaryBuilder()
             .Set("installation_mode", "allowed")
             .Set(ExternalProviderImpl::kExternalUpdateUrl, kExtensionUpdateUrl)
-            .Build();
+            .BuildDict();
     prefs()->SetManagedPref(pref_names::kExtensionManagement,
                             DictionaryBuilder()
                                 .Set(kExtensionId1, std::move(extension_entry))
-                                .Build());
+                                .BuildDict());
   }
 
   void CreateExtensionService(bool extensions_enabled) {
@@ -1361,8 +1361,8 @@
        NonMisconfigurationFailureNotPresentDisallowedByPolicyTypeError) {
   SetupForceList(ExtensionOrigin::kWebStore);
   // Set TYPE_EXTENSION and TYPE_THEME as the allowed extension types.
-  std::unique_ptr<base::Value> list =
-      ListBuilder().Append("extension").Append("theme").Build();
+  base::Value::List list =
+      ListBuilder().Append("extension").Append("theme").BuildList();
   prefs()->SetManagedPref(pref_names::kAllowedTypes, std::move(list));
 
   scoped_refptr<const Extension> ext1 = CreateNewExtension(
@@ -1391,8 +1391,8 @@
   SetupForceList(ExtensionOrigin::kWebStore);
 
   // Set TYPE_EXTENSION and TYPE_THEME as the allowed extension types.
-  std::unique_ptr<base::Value> list =
-      ListBuilder().Append("extension").Append("theme").Build();
+  base::Value::List list =
+      ListBuilder().Append("extension").Append("theme").BuildList();
   prefs()->SetManagedPref(pref_names::kAllowedTypes, std::move(list));
 
   scoped_refptr<const Extension> ext1 = CreateNewExtension(
diff --git a/chrome/browser/extensions/forced_extensions/force_installed_test_base.cc b/chrome/browser/extensions/forced_extensions/force_installed_test_base.cc
index 44281379..50aaece 100644
--- a/chrome/browser/extensions/forced_extensions/force_installed_test_base.cc
+++ b/chrome/browser/extensions/forced_extensions/force_installed_test_base.cc
@@ -65,17 +65,17 @@
                                      : kOffStoreUpdateUrl;
   list.Append(base::StrCat({kExtensionId1, ";", update_url}));
   list.Append(base::StrCat({kExtensionId2, ";", update_url}));
-  std::unique_ptr<base::Value> dict =
+  base::Value::Dict dict =
       DictionaryBuilder()
           .Set(kExtensionId1,
                DictionaryBuilder()
                    .Set(ExternalProviderImpl::kExternalUpdateUrl, update_url)
-                   .Build())
+                   .BuildDict())
           .Set(kExtensionId2,
                DictionaryBuilder()
                    .Set(ExternalProviderImpl::kExternalUpdateUrl, update_url)
-                   .Build())
-          .Build();
+                   .BuildDict())
+          .BuildDict();
   prefs_->SetManagedPref(pref_names::kInstallForceList, std::move(dict));
 
   EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
@@ -92,7 +92,7 @@
 }
 
 void ForceInstalledTestBase::SetupEmptyForceList() {
-  std::unique_ptr<base::Value> dict = DictionaryBuilder().Build();
+  base::Value::Dict dict = DictionaryBuilder().BuildDict();
   prefs_->SetManagedPref(pref_names::kInstallForceList, std::move(dict));
 
   EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
diff --git a/chrome/browser/extensions/install_verifier_unittest.cc b/chrome/browser/extensions/install_verifier_unittest.cc
index fbe4f6a..110fd8c 100644
--- a/chrome/browser/extensions/install_verifier_unittest.cc
+++ b/chrome/browser/extensions/install_verifier_unittest.cc
@@ -39,11 +39,11 @@
 
   // Adds an extension as being allowed by policy.
   void AddExtensionAsPolicyInstalled(const ExtensionId& id) {
-    std::unique_ptr<base::DictionaryValue> extension_entry =
-        DictionaryBuilder().Set("installation_mode", "allowed").Build();
+    base::Value::Dict extension_entry =
+        DictionaryBuilder().Set("installation_mode", "allowed").BuildDict();
     testing_profile()->GetTestingPrefService()->SetManagedPref(
         pref_names::kExtensionManagement,
-        DictionaryBuilder().Set(id, std::move(extension_entry)).Build());
+        DictionaryBuilder().Set(id, std::move(extension_entry)).BuildDict());
     EXPECT_TRUE(ExtensionManagementFactory::GetForBrowserContext(profile())
                     ->IsInstallationExplicitlyAllowed(id));
   }
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc
index 45645f58..0653c94 100644
--- a/chrome/browser/extensions/installed_loader.cc
+++ b/chrome/browser/extensions/installed_loader.cc
@@ -96,15 +96,13 @@
   EXTERNAL_ITEM_MAX_ITEMS
 };
 
-bool IsManifestCorrupt(const base::DictionaryValue& manifest) {
+bool IsManifestCorrupt(const base::Value::Dict& manifest) {
   // Because of bug #272524 sometimes manifests got mangled in the preferences
   // file, one particularly bad case resulting in having both a background page
   // and background scripts values. In those situations we want to reload the
   // manifest from the extension to fix this.
-  const base::Value* background_page;
-  const base::Value* background_scripts;
-  return manifest.Get(manifest_keys::kBackgroundPage, &background_page) &&
-         manifest.Get(manifest_keys::kBackgroundScripts, &background_scripts);
+  return manifest.contains(manifest_keys::kBackgroundPage) &&
+         manifest.contains(manifest_keys::kBackgroundScripts);
 }
 
 ManifestReloadReason ShouldReloadExtensionManifest(const ExtensionInfo& info) {
@@ -117,8 +115,7 @@
     return NOT_NEEDED;
 
   // Reload the manifest if it needs to be relocalized.
-  if (extension_l10n_util::ShouldRelocalizeManifest(
-          info.extension_manifest->GetDict()))
+  if (extension_l10n_util::ShouldRelocalizeManifest(*info.extension_manifest))
     return NEEDS_RELOCALIZATION;
 
   // Reload if the copy of the manifest in the preferences is corrupt.
@@ -386,8 +383,8 @@
       }
 
       extensions_info->at(i)->extension_manifest =
-          base::DictionaryValue::From(base::Value::ToUniquePtrValue(
-              extension->manifest()->value()->Clone()));
+          std::make_unique<base::Value::Dict>(
+              extension->manifest()->value()->Clone());
       should_write_prefs = true;
     }
   }
diff --git a/chrome/browser/extensions/isolated_world_csp_browsertest.cc b/chrome/browser/extensions/isolated_world_csp_browsertest.cc
index f8ab2a4..d1d36cd 100644
--- a/chrome/browser/extensions/isolated_world_csp_browsertest.cc
+++ b/chrome/browser/extensions/isolated_world_csp_browsertest.cc
@@ -64,8 +64,7 @@
   content::WebContentsConsoleObserver console_observer(web_contents);
   console_observer.SetPattern(
       "Refused to run the JavaScript URL because it violates the following "
-      "Content Security Policy directive: \"script-src 'self' "
-      "'wasm-unsafe-eval'\".*");
+      "Content Security Policy directive: *");
 
   GURL url = embedded_test_server()->GetURL("js-url.com",
                                             "/page_with_script_src_csp.html");
diff --git a/chrome/browser/extensions/options_page_apitest.cc b/chrome/browser/extensions/options_page_apitest.cc
index 191e3d1..f3f6d23 100644
--- a/chrome/browser/extensions/options_page_apitest.cc
+++ b/chrome/browser/extensions/options_page_apitest.cc
@@ -84,7 +84,7 @@
           .Set("manifest_version", 2)
           .Set("name", "Extension for options param test")
           .Set("options_ui",
-               DictionaryBuilder().Set("page", "options.html").Build())
+               DictionaryBuilder().Set("page", "options.html").BuildDict())
           .Set("version", "1")
           .ToJSON());
 
diff --git a/chrome/browser/extensions/permissions_updater_unittest.cc b/chrome/browser/extensions/permissions_updater_unittest.cc
index 9cd34ca..1aec0bf0 100644
--- a/chrome/browser/extensions/permissions_updater_unittest.cc
+++ b/chrome/browser/extensions/permissions_updater_unittest.cc
@@ -46,8 +46,8 @@
 namespace {
 
 scoped_refptr<const Extension> CreateExtensionWithOptionalPermissions(
-    std::unique_ptr<base::Value> optional_permissions,
-    std::unique_ptr<base::Value> permissions,
+    base::Value::List optional_permissions,
+    base::Value::List permissions,
     const std::string& name) {
   return ExtensionBuilder()
       .SetLocation(mojom::ManifestLocation::kInternal)
@@ -59,7 +59,7 @@
               .Set("version", "0.1.2.3")
               .Set("permissions", std::move(permissions))
               .Set("optional_permissions", std::move(optional_permissions))
-              .Build())
+              .BuildDict())
       .SetID(crx_file::id_util::GenerateId(name))
       .Build();
 }
@@ -87,7 +87,7 @@
                           ListBuilder()
                               .Append("http://*.c.com/*")
                               .Append("notifications")
-                              .Build())
+                              .BuildList())
           .Build();
 
   {
@@ -223,8 +223,8 @@
     ListBuilder required_permissions;
     required_permissions.Append("topSites");
     scoped_refptr<const Extension> extension =
-        CreateExtensionWithOptionalPermissions(optional_permissions.Build(),
-                                               required_permissions.Build(),
+        CreateExtensionWithOptionalPermissions(optional_permissions.BuildList(),
+                                               required_permissions.BuildList(),
                                                "My Extension");
 
     PermissionsUpdater updater(profile());
@@ -283,8 +283,8 @@
     ListBuilder required_permissions;
     required_permissions.Append("tabs").Append("http://*/*");
     scoped_refptr<const Extension> extension =
-        CreateExtensionWithOptionalPermissions(optional_permissions.Build(),
-                                               required_permissions.Build(),
+        CreateExtensionWithOptionalPermissions(optional_permissions.BuildList(),
+                                               required_permissions.BuildList(),
                                                "ExtensionSettings");
     AddPattern(&default_policy_blocked_hosts, "http://*.google.com/*");
     PermissionsUpdater updater(profile());
@@ -379,7 +379,7 @@
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("extension")
           .SetManifestKey("optional_permissions",
-                          extensions::ListBuilder().Append("tabs").Build())
+                          extensions::ListBuilder().Append("tabs").BuildList())
           .Build();
 
   PermissionsUpdater updater(profile());
@@ -503,8 +503,8 @@
     SCOPED_TRACE(test_name);
     scoped_refptr<const Extension> extension =
         CreateExtensionWithOptionalPermissions(
-            std::make_unique<base::ListValue>(),
-            ListBuilder().Append(test_case.permission).Build(), test_name);
+            base::Value::List(),
+            ListBuilder().Append(test_case.permission).BuildList(), test_name);
     PermissionsUpdater updater(profile());
     updater.InitializePermissions(extension.get());
 
@@ -763,8 +763,9 @@
 
   scoped_refptr<const Extension> extension =
       CreateExtensionWithOptionalPermissions(
-          /*optional=*/ListBuilder().Append("tabs").Build(),
-          /*required=*/ListBuilder().Append("https://example.com/*").Build(),
+          /*optional_permissions=*/ListBuilder().Append("tabs").BuildList(),
+          /*permissions=*/
+          ListBuilder().Append("https://example.com/*").BuildList(),
           "optional grant");
   ASSERT_TRUE(extension);
 
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc
index 2ff7aeaa..9e25e5f 100644
--- a/chrome/browser/extensions/process_manager_browsertest.cc
+++ b/chrome/browser/extensions/process_manager_browsertest.cc
@@ -221,14 +221,15 @@
              "script-src 'self' 'unsafe-eval'; object-src 'self'")
         .Set("sandbox",
              DictionaryBuilder()
-                 .Set("pages", ListBuilder().Append("sandboxed.html").Build())
-                 .Build())
+                 .Set("pages",
+                      ListBuilder().Append("sandboxed.html").BuildList())
+                 .BuildDict())
         .Set("web_accessible_resources",
-             ListBuilder().Append("*.html").Build());
+             ListBuilder().Append("*.html").BuildList());
 
     if (has_background_process) {
       manifest.Set("background",
-                   DictionaryBuilder().Set("page", "bg.html").Build());
+                   DictionaryBuilder().Set("page", "bg.html").BuildDict());
       dir->WriteFile(FILE_PATH_LITERAL("bg.html"),
                      "<iframe id='bgframe' src='empty.html'></iframe>");
     }
diff --git a/chrome/browser/extensions/shared_module_service_unittest.cc b/chrome/browser/extensions/shared_module_service_unittest.cc
index 24d8826..ec8fb0f7 100644
--- a/chrome/browser/extensions/shared_module_service_unittest.cc
+++ b/chrome/browser/extensions/shared_module_service_unittest.cc
@@ -40,11 +40,11 @@
   if (!import_ids.empty()) {
     ListBuilder import_list;
     for (const std::string& import_id : import_ids)
-      import_list.Append(DictionaryBuilder().Set("id", import_id).Build());
-    builder.Set("import", import_list.Build());
+      import_list.Append(DictionaryBuilder().Set("id", import_id).BuildDict());
+    builder.Set("import", import_list.BuildList());
   }
   return ExtensionBuilder()
-      .SetManifest(builder.Build())
+      .SetManifest(builder.BuildDict())
       .AddFlags(Extension::FROM_WEBSTORE)
       .SetID(id)
       .Build();
@@ -52,16 +52,16 @@
 
 scoped_refptr<const Extension> CreateSharedModule(
     const std::string& module_id) {
-  std::unique_ptr<base::DictionaryValue> manifest =
+  base::Value::Dict manifest =
       DictionaryBuilder()
           .Set("name", "Shared Module")
           .Set("version", "1.0")
           .Set("manifest_version", 2)
           .Set("export",
                DictionaryBuilder()
-                   .Set("resources", ListBuilder().Append("foo.js").Build())
-                   .Build())
-          .Build();
+                   .Set("resources", ListBuilder().Append("foo.js").BuildList())
+                   .BuildDict())
+          .BuildDict();
 
   return ExtensionBuilder()
       .SetManifest(std::move(manifest))
@@ -178,16 +178,16 @@
       CreateSharedModule("shared_module_1");
   EXPECT_TRUE(InstallExtension(shared_module_1.get(), false));
 
-  std::unique_ptr<base::DictionaryValue> manifest_2 =
+  base::Value::Dict manifest_2 =
       DictionaryBuilder()
           .Set("name", "Shared Module 2")
           .Set("version", "1.0")
           .Set("manifest_version", 2)
           .Set("export",
                DictionaryBuilder()
-                   .Set("resources", ListBuilder().Append("foo.js").Build())
-                   .Build())
-          .Build();
+                   .Set("resources", ListBuilder().Append("foo.js").BuildList())
+                   .BuildDict())
+          .BuildDict();
   scoped_refptr<const Extension> shared_module_2 =
       CreateSharedModule("shared_module_2");
   EXPECT_TRUE(InstallExtension(shared_module_2.get(), false));
@@ -237,7 +237,7 @@
   std::string nonallowlisted_id =
       crx_file::id_util::GenerateId("nonallowlisted");
   // Create a module which exports to a restricted allowlist.
-  std::unique_ptr<base::DictionaryValue> manifest =
+  base::Value::Dict manifest =
       DictionaryBuilder()
           .Set("name", "Shared Module")
           .Set("version", "1.0")
@@ -245,10 +245,10 @@
           .Set("export",
                DictionaryBuilder()
                    .Set("allowlist",
-                        ListBuilder().Append(allowlisted_id).Build())
-                   .Set("resources", ListBuilder().Append("*").Build())
-                   .Build())
-          .Build();
+                        ListBuilder().Append(allowlisted_id).BuildList())
+                   .Set("resources", ListBuilder().Append("*").BuildList())
+                   .BuildDict())
+          .BuildDict();
   scoped_refptr<const Extension> shared_module =
       ExtensionBuilder()
           .SetManifest(std::move(manifest))
diff --git a/chrome/browser/extensions/startup_helper.cc b/chrome/browser/extensions/startup_helper.cc
index cb410583..b275a66 100644
--- a/chrome/browser/extensions/startup_helper.cc
+++ b/chrome/browser/extensions/startup_helper.cc
@@ -111,7 +111,7 @@
 
   void OnUnpackSuccess(const base::FilePath& temp_dir,
                        const base::FilePath& extension_root,
-                       std::unique_ptr<base::DictionaryValue> original_manifest,
+                       std::unique_ptr<base::Value::Dict> original_manifest,
                        const Extension* extension,
                        const SkBitmap& install_icon,
                        declarative_net_request::RulesetInstallPrefs
diff --git a/chrome/browser/extensions/test_extension_environment.cc b/chrome/browser/extensions/test_extension_environment.cc
index cedb4d3..9fdba25 100644
--- a/chrome/browser/extensions/test_extension_environment.cc
+++ b/chrome/browser/extensions/test_extension_environment.cc
@@ -35,17 +35,14 @@
 
 namespace {
 
-std::unique_ptr<base::DictionaryValue> MakeExtensionManifest(
-    const base::Value& manifest_extra) {
-  std::unique_ptr<base::DictionaryValue> manifest =
-      DictionaryBuilder()
-          .Set("name", "Extension")
-          .Set("version", "1.0")
-          .Set("manifest_version", 2)
-          .Build();
-  const base::DictionaryValue* manifest_extra_dict;
-  if (manifest_extra.GetAsDictionary(&manifest_extra_dict)) {
-    manifest->MergeDictionary(manifest_extra_dict);
+base::Value::Dict MakeExtensionManifest(const base::Value& manifest_extra) {
+  base::Value::Dict manifest = DictionaryBuilder()
+                                   .Set("name", "Extension")
+                                   .Set("version", "1.0")
+                                   .Set("manifest_version", 2)
+                                   .BuildDict();
+  if (manifest_extra.is_dict()) {
+    manifest.Merge(manifest_extra.GetDict().Clone());
   } else {
     std::string manifest_json;
     base::JSONWriter::Write(manifest_extra, &manifest_json);
@@ -54,7 +51,7 @@
   return manifest;
 }
 
-std::unique_ptr<base::DictionaryValue> MakePackagedAppManifest() {
+base::Value::Dict MakePackagedAppManifest() {
   return extensions::DictionaryBuilder()
       .Set("name", "Test App Name")
       .Set("version", "2.0")
@@ -64,10 +61,10 @@
                            extensions::DictionaryBuilder()
                                .Set("scripts", extensions::ListBuilder()
                                                    .Append("background.js")
-                                                   .Build())
-                               .Build())
-                      .Build())
-      .Build();
+                                                   .BuildList())
+                               .BuildDict())
+                      .BuildDict())
+      .BuildDict();
 }
 
 }  // namespace
@@ -132,8 +129,7 @@
 
 const Extension* TestExtensionEnvironment::MakeExtension(
     const base::Value& manifest_extra) {
-  std::unique_ptr<base::DictionaryValue> manifest =
-      MakeExtensionManifest(manifest_extra);
+  base::Value::Dict manifest = MakeExtensionManifest(manifest_extra);
   scoped_refptr<const Extension> result =
       ExtensionBuilder().SetManifest(std::move(manifest)).Build();
   GetExtensionService()->AddExtension(result.get());
@@ -143,8 +139,7 @@
 const Extension* TestExtensionEnvironment::MakeExtension(
     const base::Value& manifest_extra,
     const std::string& id) {
-  std::unique_ptr<base::DictionaryValue> manifest =
-      MakeExtensionManifest(manifest_extra);
+  base::Value::Dict manifest = MakeExtensionManifest(manifest_extra);
   scoped_refptr<const Extension> result =
       ExtensionBuilder().SetManifest(std::move(manifest)).SetID(id).Build();
   GetExtensionService()->AddExtension(result.get());
diff --git a/chrome/browser/extensions/unpacked_installer.cc b/chrome/browser/extensions/unpacked_installer.cc
index 7444723d..3a9e368 100644
--- a/chrome/browser/extensions/unpacked_installer.cc
+++ b/chrome/browser/extensions/unpacked_installer.cc
@@ -266,8 +266,7 @@
 
   return extension() &&
          extension_l10n_util::ValidateExtensionLocales(
-             extension_path_, extension()->manifest()->value()->GetDict(),
-             error) &&
+             extension_path_, *extension()->manifest()->value(), error) &&
          IndexAndPersistRulesIfNeeded(error);
 }
 
diff --git a/chrome/browser/extensions/update_install_gate_unittest.cc b/chrome/browser/extensions/update_install_gate_unittest.cc
index 3e73e5d8..ad5c04e 100644
--- a/chrome/browser/extensions/update_install_gate_unittest.cc
+++ b/chrome/browser/extensions/update_install_gate_unittest.cc
@@ -62,10 +62,10 @@
                                 DictionaryBuilder()
                                     .Set("scripts", ListBuilder()
                                                         .Append("background.js")
-                                                        .Build())
-                                    .Build())
-                           .Build())
-                  .Build())
+                                                        .BuildList())
+                                    .BuildDict())
+                           .BuildDict())
+                  .BuildDict())
           .SetID(extension_id)
           .Build();
   return app;
@@ -84,8 +84,8 @@
                   .Set("background", DictionaryBuilder()
                                          .Set("page", "background.html")
                                          .Set("persistent", persistent)
-                                         .Build())
-                  .Build())
+                                         .BuildDict())
+                  .BuildDict())
           .SetID(extension_id)
           .Build();
   return extension;
diff --git a/chrome/browser/extensions/user_script_listener_unittest.cc b/chrome/browser/extensions/user_script_listener_unittest.cc
index 1a71689..73ef91f 100644
--- a/chrome/browser/extensions/user_script_listener_unittest.cc
+++ b/chrome/browser/extensions/user_script_listener_unittest.cc
@@ -33,6 +33,7 @@
 #include "extensions/browser/api/scripting/scripting_utils.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
+#include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/url_pattern_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -115,8 +116,10 @@
                                         .AppendASCII("Extensions")
                                         .AppendASCII(kTestExtensionId)
                                         .AppendASCII("1.0.0.0");
+    extensions::TestExtensionRegistryObserver observer(
+        ExtensionRegistry::Get(profile_), kTestExtensionId);
     UnpackedInstaller::Create(service_)->Load(extension_path);
-    content::RunAllTasksUntilIdle();
+    observer.WaitForExtensionLoaded();
   }
 
   void UnloadTestExtension() {
diff --git a/chrome/browser/extensions/webstore_installer_browsertest.cc b/chrome/browser/extensions/webstore_installer_browsertest.cc
index 6326f7dff..e27b75d2 100644
--- a/chrome/browser/extensions/webstore_installer_browsertest.cc
+++ b/chrome/browser/extensions/webstore_installer_browsertest.cc
@@ -66,9 +66,8 @@
   base::OnceClosure deleted_closure_;
 };
 
-class WebstoreInstallerBrowserTest
-    : public WebstoreInstallerTest,
-      public WebstoreInstaller::Delegate {
+class WebstoreInstallerBrowserTest : public WebstoreInstallerTest,
+                                     public WebstoreInstaller::Delegate {
  public:
   WebstoreInstallerBrowserTest(const std::string& webstore_domain,
                                const std::string& test_data_path,
@@ -184,8 +183,7 @@
           .SetManifest(std::move(manifest))
           .Build();
   extension_service()->OnExtensionInstalled(extension.get(),
-                                            syncer::StringOrdinal(),
-                                            0);
+                                            syncer::StringOrdinal(), 0);
 
   run_loop.Run();
 
diff --git a/chrome/browser/extensions/webstore_reinstaller_browsertest.cc b/chrome/browser/extensions/webstore_reinstaller_browsertest.cc
index 3b2c772..3e01b418 100644
--- a/chrome/browser/extensions/webstore_reinstaller_browsertest.cc
+++ b/chrome/browser/extensions/webstore_reinstaller_browsertest.cc
@@ -72,7 +72,7 @@
                            .Set("description", "Foo")
                            .Set("manifest_version", 2)
                            .Set("version", "1.0")
-                           .Build())
+                           .BuildDict())
           .Build();
   extension_service()->AddExtension(extension.get());
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service.cc b/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
index ac7d31e..f9db5e9 100644
--- a/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
+++ b/chrome/browser/first_party_sets/first_party_sets_policy_service.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/first_party_sets_handler.h"
 #include "content/public/common/content_features.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "services/network/public/mojom/first_party_sets_access_delegate.mojom.h"
@@ -258,9 +259,9 @@
   if (!config_.has_value() || !is_enabled())
     return false;
 
-  absl::optional<absl::optional<net::FirstPartySetEntry>> maybe_override =
+  absl::optional<net::FirstPartySetEntryOverride> maybe_override =
       config_->FindOverride(site);
-  return maybe_override.has_value() && maybe_override->has_value();
+  return maybe_override.has_value() && !maybe_override->IsDeletion();
 }
 
 void FirstPartySetsPolicyService::OnReadyToNotifyDelegates(
diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc b/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
index 9d5afed..23993239 100644
--- a/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
+++ b/chrome/browser/first_party_sets/first_party_sets_policy_service_unittest.cc
@@ -20,6 +20,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "net/first_party_sets/global_first_party_sets.h"
@@ -235,9 +236,9 @@
 TEST_F(FirstPartySetsPolicyServiceTest, IsSiteInManagedSet_SiteNotInConfig) {
   SetContextConfig(net::FirstPartySetsContextConfig(
       {{net::SchemefulSite(GURL("https://example.test")),
-        {net::FirstPartySetEntry(
+        net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
             net::SchemefulSite(GURL("https://primary.test")),
-            net::SiteType::kAssociated, absl::nullopt)}}}));
+            net::SiteType::kAssociated, absl::nullopt))}}));
   service()->InitForTesting();
 
   EXPECT_FALSE(service()->IsSiteInManagedSet(
@@ -248,8 +249,8 @@
 TEST_F(FirstPartySetsPolicyServiceTest,
        IsSiteInManagedSet_SiteInConfig_AsDeletion) {
   net::SchemefulSite example_site(GURL("https://example.test"));
-  SetContextConfig(
-      net::FirstPartySetsContextConfig({{example_site, {absl::nullopt}}}));
+  SetContextConfig(net::FirstPartySetsContextConfig(
+      {{example_site, net::FirstPartySetEntryOverride()}}));
   service()->InitForTesting();
   EXPECT_FALSE(service()->IsSiteInManagedSet(example_site));
   env().RunUntilIdle();
@@ -259,10 +260,9 @@
        IsSiteInManagedSet_SiteInConfig_AsModification) {
   net::SchemefulSite example_site(GURL("https://example.test"));
   SetContextConfig(net::FirstPartySetsContextConfig(
-      {{example_site,
-        {net::FirstPartySetEntry(
-            net::SchemefulSite(GURL("https://primary.test")),
-            net::SiteType::kAssociated, absl::nullopt)}}}));
+      {{example_site, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                          net::SchemefulSite(GURL("https://primary.test")),
+                          net::SiteType::kAssociated, absl::nullopt))}}));
   service()->InitForTesting();
   EXPECT_TRUE(service()->IsSiteInManagedSet(example_site));
   env().RunUntilIdle();
@@ -272,10 +272,9 @@
        IsSiteInManagedSet_SiteInConfig_PrefDisabled) {
   net::SchemefulSite example_site(GURL("https://example.test"));
   SetContextConfig(net::FirstPartySetsContextConfig(
-      {{example_site,
-        {net::FirstPartySetEntry(
-            net::SchemefulSite(GURL("https://primary.test")),
-            net::SiteType::kAssociated, absl::nullopt)}}}));
+      {{example_site, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                          net::SchemefulSite(GURL("https://primary.test")),
+                          net::SiteType::kAssociated, absl::nullopt))}}));
   SetEnabledPref(false);
   service()->InitForTesting();
   EXPECT_FALSE(service()->IsSiteInManagedSet(example_site));
@@ -288,10 +287,9 @@
   features.InitAndDisableFeature(features::kFirstPartySets);
   net::SchemefulSite example_site(GURL("https://example.test"));
   SetContextConfig(net::FirstPartySetsContextConfig(
-      {{example_site,
-        {net::FirstPartySetEntry(
-            net::SchemefulSite(GURL("https://primary.test")),
-            net::SiteType::kAssociated, absl::nullopt)}}}));
+      {{example_site, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                          net::SchemefulSite(GURL("https://primary.test")),
+                          net::SiteType::kAssociated, absl::nullopt))}}));
   service()->InitForTesting();
   EXPECT_FALSE(service()->IsSiteInManagedSet(example_site));
   env().RunUntilIdle();
@@ -483,7 +481,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
   SetContextConfig(test_config.Clone());
 
   service()->InitForTesting();
@@ -542,7 +541,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
   SetContextConfig(test_config.Clone());
 
   service()->InitForTesting();
@@ -573,7 +573,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
   net::FirstPartySetsCacheFilter test_cache_filter({{test_primary, 1}},
                                                    /*browser_run_id=*/1);
   SetContextConfig(test_config.Clone());
@@ -593,7 +594,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
 
   base::test::TestFuture<net::FirstPartySetMetadata> future;
   service()->ComputeFirstPartySetMetadata(test_primary, &test_primary,
@@ -613,7 +615,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
 
   SetContextConfig(test_config.Clone());
   SetInvokeCallbacksAsynchronously(/*asynchronous=*/true);
@@ -631,7 +634,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
 
   SetContextConfig(test_config.Clone());
   SetInvokeCallbacksAsynchronously(/*asynchronous=*/false);
@@ -650,7 +654,8 @@
   net::SchemefulSite test_primary(GURL("https://a.test"));
   net::FirstPartySetEntry test_entry(test_primary, net::SiteType::kPrimary,
                                      absl::nullopt);
-  net::FirstPartySetsContextConfig test_config({{test_primary, {test_entry}}});
+  net::FirstPartySetsContextConfig test_config(
+      {{test_primary, net::FirstPartySetEntryOverride(test_entry)}});
 
   SetContextConfig(test_config.Clone());
   SetInvokeCallbacksAsynchronously(/*asynchronous=*/false);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 6754a3d..03c2898 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3661,6 +3661,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "fedcm-user-info",
+    "owners": ["yigu", "web-identity-eng@google.com"],
+    "expiry_milestone": 120
+  },
+  {
     "name": "fedcm-without-third-party-cookies",
     "owners": ["tanzachary", "web-identity-eng@google.com"],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ab2b64a2..70e6a7e 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1427,6 +1427,11 @@
     "Allows the FedCM API to send performance measurement to the metrics "
     "endpoint on the identity provider side. Requires FedCM to be enabled.";
 
+const char kFedCmUserInfoName[] = "FedCmUserInfo";
+const char kFedCmUserInfoDescription[] =
+    "Allows an identity provider to request user info instead of token from "
+    "its own iframe. Requires FedCM to be enabled.";
+
 const char kFedCmMultiIdpName[] = "FedCmMultiIdp";
 const char kFedCmMultiIdpDescription[] =
     "Allows the FedCM API to request multiple identity providers "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index a1cfec59..e61caaa4 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -805,6 +805,9 @@
 extern const char kFedCmMultiIdpName[];
 extern const char kFedCmMultiIdpDescription[];
 
+extern const char kFedCmUserInfoName[];
+extern const char kFedCmUserInfoDescription[];
+
 extern const char kFileHandlingAPIName[];
 extern const char kFileHandlingAPIDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index a2d29d2..3777b0a6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -529,7 +529,7 @@
 
 BASE_FEATURE(kClearOmniboxFocusAfterNavigation,
              "ClearOmniboxFocusAfterNavigation",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kCloseTabSuggestions,
              "CloseTabSuggestions",
@@ -647,7 +647,7 @@
 
 BASE_FEATURE(kCacheDeprecatedSystemLocationSetting,
              "CacheDeprecatedSystemLocationSetting",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kChromeNewDownloadTab,
              "ChromeNewDownloadTab",
@@ -853,7 +853,7 @@
 
 BASE_FEATURE(kOptimizeGeolocationHeaderGeneration,
              "OptimizeGeolocationHeaderGeneration",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kOptimizeLayoutsForPullRefresh,
              "OptimizeLayoutsForPullRefresh",
@@ -861,7 +861,7 @@
 
 BASE_FEATURE(kPostTaskFocusTab,
              "PostTaskFocusTab",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kProbabilisticCryptidRenderer,
              "ProbabilisticCryptidRenderer",
diff --git a/chrome/browser/lifetime/browser_close_manager_browsertest.cc b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
index 978bc08..c5112c4 100644
--- a/chrome/browser/lifetime/browser_close_manager_browsertest.cc
+++ b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
@@ -228,13 +228,13 @@
                            const base::FilePath& target_path,
                            download::DownloadItem::TargetDisposition disp,
                            download::DownloadDangerType danger_type,
-                           download::DownloadItem::MixedContentStatus mcs,
+                           download::DownloadItem::InsecureDownloadStatus ids,
                            const base::FilePath& intermediate_path,
                            const base::FilePath& display_name,
                            const std::string& mime_type,
                            download::DownloadInterruptReason reason) {
     std::move(callback).Run(target_path, disp,
-                            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, mcs,
+                            download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, ids,
                             intermediate_path, display_name, mime_type, reason);
   }
 };
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 764a09a8..7306465 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -1159,21 +1159,41 @@
     ukm_service_->Purge();
 }
 
-void ChromeMetricsServiceClient::OnUkmAllowedStateChanged(bool total_purge) {
+void ChromeMetricsServiceClient::OnUkmAllowedStateChanged(
+    bool total_purge,
+    ukm::UkmConsentState previous_consent_state) {
   if (!ukm_service_)
     return;
 
   const ukm::UkmConsentState consent_state = GetUkmConsentState();
 
-  // Purge recording if the required consent has been revoked.
+  // Manages purging of events and sources.
   if (total_purge) {
     ukm_service_->Purge();
     ukm_service_->ResetClientState(ukm::ResetReason::kOnUkmAllowedStateChanged);
   } else {
-    if (!consent_state.Has(ukm::UkmConsentType::EXTENSIONS))
+    // Purge recording if required consent has been revoked.
+    if (!consent_state.Has(ukm::MSBB))
+      ukm_service_->PurgeMsbbData();
+    if (!consent_state.Has(ukm::EXTENSIONS))
       ukm_service_->PurgeExtensionsData();
-    if (!consent_state.Has(ukm::UkmConsentType::APPS))
+    if (!consent_state.Has(ukm::APPS))
       ukm_service_->PurgeAppsData();
+
+    // If MSBB or App-sync consent changed from on to off then,
+    // the client id, or client state, must be reset. When
+    // kAppMetricsOnlyRelyOnAppSync feature is disabled function will no-op.
+    //
+    // In the non-feature case client reset is handled above because
+    // |total_purge| will be true. MSBB is used to determine if UKM is enabled
+    // or disabled. When the consent is revoked UkmService will be disabled,
+    // triggering |total_purge| to be true. At which point the client state will
+    // be reset.
+    //
+    // When the feature is enabled disabling MSBB or App-Sync will not trigger a
+    // total purge. Resetting the client state has to be handled specifically
+    // for this case.
+    ResetClientStateWhenMsbbOrAppConsentIsRevoked(previous_consent_state);
   }
 
   // Notify the recording service of changed metrics consent.
@@ -1376,4 +1396,26 @@
   return absl::nullopt;
 }
 
-#endif
+#endif  //  BUILDFLAG(IS_CHROMEOS_ASH)
+
+void ChromeMetricsServiceClient::ResetClientStateWhenMsbbOrAppConsentIsRevoked(
+    ukm::UkmConsentState previous_consent_state) {
+  // TODO(crbug/1396481): enable by default once validated.
+  if (!base::FeatureList::IsEnabled(ukm::kAppMetricsOnlyRelyOnAppSync))
+    return;
+
+  const auto ukm_consent_state = GetUkmConsentState();
+
+  // True if MSBB consent change from on to off, False otherwise.
+  const bool msbb_revoked = previous_consent_state.Has(ukm::MSBB) &&
+                            !ukm_consent_state.Has(ukm::MSBB);
+
+  // True if APPS consent change from on to off, False otherwise.
+  const bool apps_revoked = previous_consent_state.Has(ukm::APPS) &&
+                            !ukm_consent_state.Has(ukm::APPS);
+
+  // If either condition is true, then reset client state.
+  if (msbb_revoked || apps_revoked) {
+    ukm_service_->ResetClientState(ukm::ResetReason::kOnUkmAllowedStateChanged);
+  }
+}
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.h b/chrome/browser/metrics/chrome_metrics_service_client.h
index c0f46060..eb7e828 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.h
+++ b/chrome/browser/metrics/chrome_metrics_service_client.h
@@ -129,7 +129,9 @@
   void OnHistoryDeleted() override;
 
   // ukm::UkmConsentStateObserver:
-  void OnUkmAllowedStateChanged(bool must_purge) override;
+  void OnUkmAllowedStateChanged(
+      bool must_purge,
+      ukm::UkmConsentState previous_consent_state) override;
 
   // content::RenderProcessHostCreationObserver:
   void OnRenderProcessHostCreated(content::RenderProcessHost* host) override;
@@ -159,6 +161,7 @@
 
  private:
   friend class ChromeMetricsServiceClientTest;
+  friend class ChromeMetricsServiceClientTestIgnoredForAppMetrics;
   FRIEND_TEST_ALL_PREFIXES(ChromeMetricsServiceClientTest, IsWebstoreExtension);
 
   // Registers providers to the MetricsService. These provide data from
@@ -211,6 +214,12 @@
   // Check if an extension is installed via the Web Store.
   static bool IsWebstoreExtension(base::StringPiece id);
 
+  // Resets client state (i.e. client id) if MSBB or App-sync consent
+  // is changed from on to off. NOOP when kAppMetricsOnlyRelyOnAppSync is
+  // disabled.
+  void ResetClientStateWhenMsbbOrAppConsentIsRevoked(
+      ukm::UkmConsentState previous_consent_state);
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   // Chrome's privacy budget identifiability study state.
diff --git a/chrome/browser/metrics/chrome_metrics_service_client_ash_unittest.cc b/chrome/browser/metrics/chrome_metrics_service_client_ash_unittest.cc
new file mode 100644
index 0000000..d516e59ea
--- /dev/null
+++ b/chrome/browser/metrics/chrome_metrics_service_client_ash_unittest.cc
@@ -0,0 +1,521 @@
+// 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/metrics/chrome_metrics_service_client.h"
+
+#include "base/test/metrics/user_action_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
+#include "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
+#include "chrome/browser/unified_consent/unified_consent_service_factory.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/ash/components/login/login_state/login_state.h"
+#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
+#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
+#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client_impl.h"
+#include "chromeos/dbus/power/power_manager_client.h"
+#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
+#include "chromeos/system/fake_statistics_provider.h"
+#include "chromeos/system/statistics_provider.h"
+#include "components/metrics/log_decoder.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test/test_enabled_state_provider.h"
+#include "components/metrics/unsent_log_store.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/sync/test/test_sync_service.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/ukm/ukm_pref_names.h"
+#include "components/ukm/ukm_service.h"
+#include "components/ukm/unsent_log_store_metrics_impl.h"
+#include "components/unified_consent/pref_names.h"
+#include "components/unified_consent/unified_consent_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "services/metrics/public/cpp/ukm_entry_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/ukm/report.pb.h"
+
+namespace {
+using TestEvent1 = ukm::builders::PageLoad;
+
+// Needed to fake System Profile which is provided when ukm report is generated.
+// TODO(crbug/1396482): Refactor to remove the classes needed to fake
+// SystemProfile.
+class FakeMultiDeviceSetupClientImplFactory
+    : public ash::multidevice_setup::MultiDeviceSetupClientImpl::Factory {
+ public:
+  explicit FakeMultiDeviceSetupClientImplFactory(
+      std::unique_ptr<ash::multidevice_setup::FakeMultiDeviceSetupClient>
+          fake_multidevice_setup_client)
+      : fake_multidevice_setup_client_(
+            std::move(fake_multidevice_setup_client)) {}
+
+  ~FakeMultiDeviceSetupClientImplFactory() override = default;
+
+  // ash::multidevice_setup::MultiDeviceSetupClientImpl::Factory:
+  std::unique_ptr<ash::multidevice_setup::MultiDeviceSetupClient>
+  CreateInstance(
+      mojo::PendingRemote<ash::multidevice_setup::mojom::MultiDeviceSetup>)
+      override {
+    // NOTE: At most, one client should be created per-test.
+    EXPECT_TRUE(fake_multidevice_setup_client_);
+    return std::move(fake_multidevice_setup_client_);
+  }
+
+ private:
+  std::unique_ptr<ash::multidevice_setup::FakeMultiDeviceSetupClient>
+      fake_multidevice_setup_client_;
+};
+
+class MockSyncService : public syncer::TestSyncService {
+ public:
+  MockSyncService() {
+    SetTransportState(TransportState::INITIALIZING);
+    SetLastCycleSnapshot(syncer::SyncCycleSnapshot());
+  }
+
+  MockSyncService(const MockSyncService&) = delete;
+  MockSyncService& operator=(const MockSyncService&) = delete;
+
+  ~MockSyncService() override { Shutdown(); }
+
+  void SetStatus(bool has_passphrase, bool history_enabled, bool active) {
+    SetTransportState(active ? TransportState::ACTIVE
+                             : TransportState::INITIALIZING);
+    SetIsUsingExplicitPassphrase(has_passphrase);
+
+    GetUserSettings()->SetSelectedTypes(
+        /*sync_everything=*/false,
+        /*types=*/history_enabled ? syncer::UserSelectableTypeSet(
+                                        syncer::UserSelectableType::kHistory)
+                                  : syncer::UserSelectableTypeSet());
+
+    // It doesn't matter what exactly we set here, it's only relevant that the
+    // SyncCycleSnapshot is initialized at all.
+    SetLastCycleSnapshot(syncer::SyncCycleSnapshot(
+        /*birthday=*/std::string(), /*bag_of_chips=*/std::string(),
+        syncer::ModelNeutralState(), syncer::ProgressMarkerMap(), false, 0,
+        true, base::Time::Now(), base::Time::Now(),
+        sync_pb::SyncEnums::UNKNOWN_ORIGIN, base::Minutes(1), false));
+
+    NotifyObserversOfStateChanged();
+
+    SetAppSync(false);
+  }
+
+  void Shutdown() override {
+    for (auto& observer : observers_) {
+      observer.OnSyncShutdown(this);
+    }
+  }
+
+  void SetAppSync(bool enabled) {
+    auto selected_os_types = GetUserSettings()->GetSelectedOsTypes();
+
+    if (enabled)
+      selected_os_types.Put(syncer::UserSelectableOsType::kOsApps);
+    else
+      selected_os_types.Remove(syncer::UserSelectableOsType::kOsApps);
+
+    GetUserSettings()->SetSelectedOsTypes(false, selected_os_types);
+
+    NotifyObserversOfStateChanged();
+  }
+
+ private:
+  // syncer::TestSyncService:
+  void AddObserver(syncer::SyncServiceObserver* observer) override {
+    observers_.AddObserver(observer);
+  }
+  void RemoveObserver(syncer::SyncServiceObserver* observer) override {
+    observers_.RemoveObserver(observer);
+  }
+
+  void NotifyObserversOfStateChanged() {
+    for (auto& observer : observers_) {
+      observer.OnStateChanged(this);
+    }
+  }
+
+  // The list of observers of the SyncService state.
+  base::ObserverList<syncer::SyncServiceObserver>::Unchecked observers_;
+};
+
+struct IndependentAppMetricsTestParams {
+  IndependentAppMetricsTestParams(ukm::UkmConsentType purged,
+                                  ukm::UkmConsentType remaining)
+      : tested_type(purged), other_type(remaining) {}
+
+  ukm::UkmConsentType tested_type;
+  ukm::UkmConsentType other_type;
+};
+
+}  // namespace
+
+class ChromeMetricsServiceClientTestIgnoredForAppMetrics
+    : public testing::TestWithParam<IndependentAppMetricsTestParams> {
+ public:
+  ChromeMetricsServiceClientTestIgnoredForAppMetrics()
+      : profile_manager_(std::make_unique<TestingProfileManager>(
+            TestingBrowserProcess::GetGlobal())),
+        enabled_state_provider_(false /* consent */, false /* enabled */) {}
+
+  void SetUp() override {
+    testing::Test::SetUp();
+    metrics::MetricsService::RegisterPrefs(prefs_.registry());
+    metrics_state_manager_ = metrics::MetricsStateManager::Create(
+        &prefs_, &enabled_state_provider_, std::wstring(), base::FilePath());
+    metrics_state_manager_->InstantiateFieldTrialList();
+    ASSERT_TRUE(profile_manager_->SetUp());
+    scoped_feature_list_.InitWithFeatures(
+        {features::kUmaStorageDimensions, ukm::kAppMetricsOnlyRelyOnAppSync},
+        {});
+    // ChromeOs Metrics Provider require g_login_state and power manager client
+    // initialized before they can be instantiated.
+    chromeos::PowerManagerClient::InitializeFake();
+    chromeos::LoginState::Initialize();
+    chromeos::TpmManagerClient::InitializeFake();
+
+    SetupMultiDeviceFactory();
+
+    testing_profile_ = profile_manager_->CreateTestingProfile("test_name");
+
+    // Set statistic provider for hardware class tests.
+    chromeos::system::StatisticsProvider::SetTestProvider(
+        &fake_statistics_provider_);
+  }
+
+  void TearDown() override {
+    chromeos::LoginState::Shutdown();
+    chromeos::PowerManagerClient::Shutdown();
+
+    ash::multidevice_setup::MultiDeviceSetupClientImpl::Factory::
+        SetFactoryForTesting(nullptr);
+
+    // ChromeMetricsServiceClient::Initialize() initializes
+    // IdentifiabilityStudySettings as part of creating the
+    // PrivacyBudgetUkmEntryFilter. Reset them after the test.
+    blink::IdentifiabilityStudySettings::ResetStateForTesting();
+    profile_manager_.reset();
+  }
+
+  std::unique_ptr<ChromeMetricsServiceClient> Init(
+      sync_preferences::TestingPrefServiceSyncable& prefs) {
+    ChromeMetricsServiceClient::RegisterPrefs(prefs.registry());
+    RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+    SetUrlKeyedAnonymizedDataCollectionEnabled(prefs, /*enabled=*/true);
+
+    std::unique_ptr<ChromeMetricsServiceClient> chrome_metrics_service_client =
+        ChromeMetricsServiceClient::Create(metrics_state_manager_.get());
+    chrome_metrics_service_client->StartObserving(&sync_service_, &prefs);
+
+    chrome_metrics_service_client_ = chrome_metrics_service_client.get();
+
+    auto* ukm_service = chrome_metrics_service_client_->GetUkmService();
+    ukm_service->SetSamplingForTesting(true);
+    ukm_service->EnableRecording();
+    ukm_service->EnableReporting();
+
+    return chrome_metrics_service_client;
+  }
+
+  ChromeMetricsServiceClient& GetChromeMetricsServiceClient() {
+    return *chrome_metrics_service_client_;
+  }
+
+  ukm::UkmService* GetUkmService() {
+    return chrome_metrics_service_client_->GetUkmService();
+  }
+
+  std::vector<ukm::SourceId> GetSourceIdsForConsentType(
+      ukm::UkmConsentType consent_type) {
+    const auto filter_id_type =
+        GetAppOrNavigationSourceIdTypeForConsent(consent_type);
+    std::vector<ukm::SourceId> result;
+
+    for (const auto& source_id : source_ids_) {
+      if (ukm::GetSourceIdType(source_id) == filter_id_type) {
+        result.push_back(source_id);
+      }
+    }
+
+    return result;
+  }
+
+  static ukm::SourceIdType GetAppOrNavigationSourceIdTypeForConsent(
+      ukm::UkmConsentType consent_type) {
+    return consent_type == ukm::UkmConsentType::APPS
+               ? ukm::SourceIdType::APP_ID
+               : ukm::SourceIdType::NAVIGATION_ID;
+  }
+
+  void RegisterUrlKeyedAnonymizedDataCollectionPref(
+      sync_preferences::TestingPrefServiceSyncable& prefs) {
+    unified_consent::UnifiedConsentService::RegisterPrefs(prefs.registry());
+  }
+
+  void SetUrlKeyedAnonymizedDataCollectionEnabled(
+      sync_preferences::TestingPrefServiceSyncable& prefs,
+      bool enabled) {
+    prefs.SetBoolean(
+        unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
+        enabled);
+  }
+
+  ukm::Report GetUkmReport() {
+    metrics::UnsentLogStore* log_store =
+        GetUkmService()->reporting_service_for_testing().ukm_log_store();
+    EXPECT_GE(log_store->size(), 1ul);
+    log_store->StageNextLog();
+
+    ukm::Report report;
+    EXPECT_TRUE(
+        metrics::DecodeLogDataToProto(log_store->staged_log(), &report));
+    return report;
+  }
+
+ protected:
+  void SetupMultiDeviceFactory() {
+    ash::multidevice_setup::MultiDeviceSetupClientFactory::GetInstance()
+        ->SetServiceIsNULLWhileTestingForTesting(false);
+    auto fake_multidevice_setup_client =
+        std::make_unique<ash::multidevice_setup::FakeMultiDeviceSetupClient>();
+    fake_multidevice_setup_client_ = fake_multidevice_setup_client.get();
+    fake_multidevice_setup_client_impl_factory_ =
+        std::make_unique<FakeMultiDeviceSetupClientImplFactory>(
+            std::move(fake_multidevice_setup_client));
+    ash::multidevice_setup::MultiDeviceSetupClientImpl::Factory::
+        SetFactoryForTesting(fake_multidevice_setup_client_impl_factory_.get());
+  }
+
+  ukm::SourceId AddSourceId(ukm::SourceIdType source_id_type) {
+    const auto source_id =
+        ukm::ConvertToSourceId(source_ids_.size(), source_id_type);
+    UpdateSourceUrl(source_id, source_id_type);
+    source_ids_.push_back(source_id);
+    return source_id;
+  }
+
+  void UpdateSourceUrl(ukm::SourceId source_id,
+                       ukm::SourceIdType source_id_type) {
+    GetUkmService()->UpdateSourceURL(
+        source_id,
+        source_id_type == ukm::SourceIdType::APP_ID ? kAppURL : kURL);
+  }
+
+  void RecordTestEvent1(ukm::SourceIdType source_id_type) {
+    const auto source_id = AddSourceId(source_id_type);
+    TestEvent1(source_id).Record(GetUkmService());
+  }
+
+  GURL kURL = GURL("https://google.com/foobar");
+  GURL kAppURL = GURL("app://google.com/foobar");
+
+  content::BrowserTaskEnvironment task_environment_;
+  TestingPrefServiceSimple prefs_;
+  std::unique_ptr<TestingProfileManager> profile_manager_;
+  base::UserActionTester user_action_runner_;
+  std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
+  metrics::TestEnabledStateProvider enabled_state_provider_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  std::vector<ukm::SourceId> source_ids_;
+  ChromeMetricsServiceClient* chrome_metrics_service_client_;
+
+  MockSyncService sync_service_;
+  chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+  TestingProfile* testing_profile_ = nullptr;
+  ash::multidevice_setup::FakeMultiDeviceSetupClient*
+      fake_multidevice_setup_client_;
+  std::unique_ptr<FakeMultiDeviceSetupClientImplFactory>
+      fake_multidevice_setup_client_impl_factory_;
+};
+
+TEST_P(ChromeMetricsServiceClientTestIgnoredForAppMetrics,
+       VerifyPurgeOnConsentChange) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  auto chrome_metrics_service_client = Init(prefs);
+
+  // Get the params for this test.
+  const auto purged_consent = GetParam().tested_type;
+  const auto remaining_consent = GetParam().other_type;
+
+  auto ukm_consent_state = GetChromeMetricsServiceClient().GetUkmConsentState();
+
+  EXPECT_TRUE(ukm_consent_state.Has(ukm::MSBB));
+  EXPECT_TRUE(ukm_consent_state.Has(ukm::APPS));
+
+  // Record a mix of SourceId's and Events.
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::APP_ID);
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::APP_ID);
+
+  GetUkmService()->Flush();
+
+  // Remove the consent for |purged_consent|. This will cause
+  // UKM metrics associated with this type to be purged.
+  if (purged_consent == ukm::UkmConsentType::APPS)
+    sync_service_.SetAppSync(false);
+  else
+    SetUrlKeyedAnonymizedDataCollectionEnabled(prefs, /*enabled=*/false);
+
+  // Verify the update has propagated by checking the current consent state.
+  ukm_consent_state = GetChromeMetricsServiceClient().GetUkmConsentState();
+
+  EXPECT_TRUE(ukm_consent_state.Has(remaining_consent));
+  EXPECT_FALSE(ukm_consent_state.Has(purged_consent));
+
+  // Generate the ukm report to valid its contents.
+  ukm::Report report = GetUkmReport();
+
+  const auto remaining_source_ids =
+      GetSourceIdsForConsentType(remaining_consent);
+  const int num_elements = remaining_source_ids.size();
+
+  // Expected |num_elements| entries and sources. Only |remaining_consent|
+  // entries and sources should remain. Entries and sources for |purged_consent|
+  // were purged.
+  EXPECT_EQ(num_elements, report.sources_size());
+  EXPECT_EQ(num_elements, report.entries_size());
+
+  const auto expected_source_id_type =
+      GetAppOrNavigationSourceIdTypeForConsent(remaining_consent);
+  std::vector<ukm::SourceId> report_source_ids;
+
+  // Verify the SourceIdType associated with |remaining_consent| are the only
+  // entries and sources remaining.
+  for (int i = 0; i < num_elements; ++i) {
+    report_source_ids.push_back(report.entries(i).source_id());
+    EXPECT_EQ(expected_source_id_type,
+              ukm::GetSourceIdType(report.entries(i).source_id()));
+  }
+
+  EXPECT_THAT(report_source_ids,
+              testing::UnorderedElementsAreArray(remaining_source_ids));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ChromeMetricsServiceClientTestIgnoredForAppMetricsGroup,
+    ChromeMetricsServiceClientTestIgnoredForAppMetrics,
+    testing::Values(IndependentAppMetricsTestParams(ukm::UkmConsentType::APPS,
+                                                    ukm::UkmConsentType::MSBB),
+                    IndependentAppMetricsTestParams(ukm::UkmConsentType::MSBB,
+                                                    ukm::UkmConsentType::APPS)),
+    [](const testing::TestParamInfo<
+        ChromeMetricsServiceClientTestIgnoredForAppMetrics::ParamType>& info) {
+      if (info.param.tested_type == ukm::UkmConsentType::APPS)
+        return "TestApps";
+      else
+        return "TestMSBB";
+    });
+
+TEST_P(ChromeMetricsServiceClientTestIgnoredForAppMetrics,
+       VerifyRecordingWhenConsentAdded) {
+  // The consent type that will be off. Events associated with this type will be
+  // ignored when events are initially recorded.
+  const auto ignored_consent = GetParam().tested_type;
+
+  // The consent type that will remain on. Events associated with this type will
+  // be successfully recorded.
+  const auto existing_consent = GetParam().other_type;
+
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  auto chrome_metrics_service_client = Init(prefs);
+
+  // Make sure the consents are set as expected for the test.
+  SetUrlKeyedAnonymizedDataCollectionEnabled(
+      prefs, existing_consent == ukm::UkmConsentType::MSBB);
+  sync_service_.SetAppSync(existing_consent == ukm::UkmConsentType::APPS);
+
+  auto ukm_consent_state = chrome_metrics_service_client->GetUkmConsentState();
+
+  EXPECT_TRUE(ukm_consent_state.Has(existing_consent));
+  EXPECT_FALSE(ukm_consent_state.Has(ignored_consent));
+
+  // Record a mix of SourceId's and Events.
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::APP_ID);
+  RecordTestEvent1(ukm::SourceIdType::NAVIGATION_ID);
+  RecordTestEvent1(ukm::SourceIdType::APP_ID);
+
+  // Turn on the ignored consent type and make sure the events are
+  // recorded.
+  if (ignored_consent == ukm::UkmConsentType::APPS)
+    sync_service_.SetAppSync(true);
+  else
+    SetUrlKeyedAnonymizedDataCollectionEnabled(prefs, /*enabled=*/true);
+
+  ukm_consent_state = chrome_metrics_service_client->GetUkmConsentState();
+
+  // Verify the both consents are granted.
+  EXPECT_TRUE(ukm_consent_state.Has(existing_consent));
+  EXPECT_TRUE(ukm_consent_state.Has(ignored_consent));
+
+  // Re-add events and sources of ignored type.
+  auto ignored_ids = GetSourceIdsForConsentType(ignored_consent);
+  for (auto id : ignored_ids) {
+    UpdateSourceUrl(id,
+                    GetAppOrNavigationSourceIdTypeForConsent(ignored_consent));
+    TestEvent1(id).Record(GetUkmService());
+  }
+
+  GetUkmService()->Flush();
+
+  // Build UKM report to verity that all of the events and sources have been
+  // recorded.
+  ukm::Report report = GetUkmReport();
+
+  // Expect that all events and sources originally recorded are present.
+  EXPECT_EQ(report.sources_size(), static_cast<int>(source_ids_.size()));
+  EXPECT_EQ(report.entries_size(), static_cast<int>(source_ids_.size()));
+
+  // The source type of the events that were not ignored.
+  const auto remaining_source_id_type =
+      GetAppOrNavigationSourceIdTypeForConsent(existing_consent);
+  const auto remaining_source_ids =
+      GetSourceIdsForConsentType(existing_consent);
+
+  // The source type of the events that were ignored.
+  const auto added_source_id_type =
+      GetAppOrNavigationSourceIdTypeForConsent(ignored_consent);
+  const auto added_source_ids = GetSourceIdsForConsentType(ignored_consent);
+
+  // Sources that were in the report.
+  std::vector<ukm::SourceId> actual_source_ids;
+
+  // Verify the sources ids are the expected value.
+  // Events and sources associated with |existing_consent| will be first because
+  // the events and sources associated with |ignored_consent| were
+  // dropped/ignored when recorded the first time.
+  for (size_t i = 0; i < remaining_source_ids.size(); ++i) {
+    actual_source_ids.push_back(report.entries(i).source_id());
+    EXPECT_EQ(remaining_source_id_type,
+              ukm::GetSourceIdType(report.entries(i).source_id()));
+  }
+
+  // Verify that the source id's are of the expected type.
+  EXPECT_THAT(actual_source_ids,
+              testing::UnorderedElementsAreArray(remaining_source_ids));
+  actual_source_ids.clear();
+
+  // The events that were re-recorded once the tested consent was added are the
+  // remaining elements.
+  for (size_t i = 0; i < added_source_ids.size(); ++i) {
+    const auto source_id =
+        report.entries(i + remaining_source_ids.size()).source_id();
+    actual_source_ids.push_back(source_id);
+    EXPECT_EQ(added_source_id_type, ukm::GetSourceIdType(source_id));
+  }
+
+  // Verify that the source id's are of the expected type.
+  EXPECT_THAT(actual_source_ids,
+              testing::UnorderedElementsAreArray(added_source_ids));
+}
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index 93a937c..407862e 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -325,7 +325,7 @@
 
 class UkmBrowserTest : public UkmBrowserTestBase {
  public:
-  UkmBrowserTest() : UkmBrowserTestBase() {}
+  UkmBrowserTest() = default;
 
   UkmBrowserTest(const UkmBrowserTest&) = delete;
   UkmBrowserTest& operator=(const UkmBrowserTest&) = delete;
@@ -349,7 +349,7 @@
 
 class UkmBrowserTestWithSyncTransport : public UkmBrowserTestBase {
  public:
-  UkmBrowserTestWithSyncTransport() {}
+  UkmBrowserTestWithSyncTransport() = default;
 
   UkmBrowserTestWithSyncTransport(const UkmBrowserTestWithSyncTransport&) =
       delete;
@@ -383,7 +383,7 @@
 class UkmConsentParamBrowserTest : public UkmBrowserTestBase,
                                    public testing::WithParamInterface<bool> {
  public:
-  UkmConsentParamBrowserTest() : UkmBrowserTestBase() {}
+  UkmConsentParamBrowserTest() = default;
 
   UkmConsentParamBrowserTest(const UkmConsentParamBrowserTest&) = delete;
   UkmConsentParamBrowserTest& operator=(const UkmConsentParamBrowserTest&) =
@@ -1543,4 +1543,160 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+class UkmBrowserTestForAppConsent : public UkmBrowserTestBase {
+ public:
+  UkmBrowserTestForAppConsent() {
+    scoped_feature_list_.InitWithFeatures({ukm::kAppMetricsOnlyRelyOnAppSync},
+                                          {});
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(UkmBrowserTestForAppConsent, MetricsClientEnablement) {
+  ukm::UkmService* ukm_service = GetUkmService();
+  ukm::UkmTestHelper ukm_test_helper(ukm_service);
+  MetricsConsentOverride metrics_consent(true);
+  Profile* profile = ProfileManager::GetLastUsedProfileIfLoaded();
+  unified_consent::UnifiedConsentService* consent_service =
+      UnifiedConsentServiceFactory::GetForProfile(profile);
+  std::unique_ptr<SyncServiceImplHarness> harness =
+      EnableSyncForProfile(profile);
+
+  // All consents are on.
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::APPS));
+
+  // Turn off MSBB consent.
+  consent_service->SetUrlKeyedAnonymizedDataCollectionEnabled(false);
+
+  // Still have AppKM consent.
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::APPS));
+
+  // Turn off App-sync.
+  auto* user_settings = harness->service()->GetUserSettings();
+  auto registered_os_sync_types =
+      user_settings->GetRegisteredSelectableOsTypes();
+  registered_os_sync_types.Remove(syncer::UserSelectableOsType::kOsApps);
+  user_settings->SetSelectedOsTypes(false, registered_os_sync_types);
+
+  // UKM recording is now disabled since MSBB and App-sync consent
+  // has been removed.
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::APPS));
+}
+
+IN_PROC_BROWSER_TEST_F(UkmBrowserTestForAppConsent,
+                       ClientIdResetWhenConsentRemoved) {
+  ukm::UkmService* ukm_service = GetUkmService();
+  ukm::UkmTestHelper ukm_test_helper(ukm_service);
+  MetricsConsentOverride metrics_consent(true);
+  Profile* profile = ProfileManager::GetLastUsedProfileIfLoaded();
+  unified_consent::UnifiedConsentService* consent_service =
+      UnifiedConsentServiceFactory::GetForProfile(profile);
+  std::unique_ptr<SyncServiceImplHarness> harness =
+      EnableSyncForProfile(profile);
+  const auto original_client_id = ukm_test_helper.GetClientId();
+  EXPECT_NE(0ul, original_client_id);
+
+  // All consents are on.
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::APPS));
+
+  // Turn off MSBB consent.
+  consent_service->SetUrlKeyedAnonymizedDataCollectionEnabled(false);
+
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::MSBB));
+
+  // Client ID should reset when MSBB is disabled.
+  const auto app_sync_client_id = ukm_test_helper.GetClientId();
+  EXPECT_NE(original_client_id, app_sync_client_id);
+
+  // Turn off app sync.
+  auto* user_settings = harness->service()->GetUserSettings();
+  auto registered_os_sync_types =
+      user_settings->GetRegisteredSelectableOsTypes();
+  registered_os_sync_types.Remove(syncer::UserSelectableOsType::kOsApps);
+  user_settings->SetSelectedOsTypes(false, registered_os_sync_types);
+
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::APPS));
+
+  // Client ID should reset when app sync is disable.
+  const auto final_client_id = ukm_test_helper.GetClientId();
+  EXPECT_NE(app_sync_client_id, final_client_id);
+}
+
+IN_PROC_BROWSER_TEST_F(UkmBrowserTestForAppConsent,
+                       EnsurePurgeOnConsentChange) {
+  ukm::UkmService* ukm_service = GetUkmService();
+  ukm::UkmTestHelper ukm_test_helper(ukm_service);
+  MetricsConsentOverride metrics_consent(true);
+  Profile* profile = ProfileManager::GetLastUsedProfileIfLoaded();
+  unified_consent::UnifiedConsentService* consent_service =
+      UnifiedConsentServiceFactory::GetForProfile(profile);
+  std::unique_ptr<SyncServiceImplHarness> harness =
+      EnableSyncForProfile(profile);
+  Browser* sync_browser = CreateBrowser(profile);
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  const std::vector<GURL> test_urls = {
+      embedded_test_server()->GetURL("/title1.html"),
+      embedded_test_server()->GetURL("/title2.html"),
+      embedded_test_server()->GetURL("/title3.html")};
+
+  // Verify all consents are enabled.
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::APPS));
+
+  int tab_index = 1;
+  // Generate MSBB ukm entries by navigating to some test webpages.
+  for (const auto& url : test_urls) {
+    ASSERT_TRUE(AddTabAtIndexToBrowser(sync_browser, tab_index++,
+                                       GURL(url::kAboutBlankURL),
+                                       ui::PAGE_TRANSITION_TYPED, true));
+    NavigateAndGetSource(url, sync_browser, &ukm_test_helper);
+  }
+
+  // Revoke MSBB consent.
+  consent_service->SetUrlKeyedAnonymizedDataCollectionEnabled(false);
+
+  // Verify that MSBB consent was revoked.
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::MSBB));
+  EXPECT_FALSE(ukm_service->recording_enabled(ukm::EXTENSIONS));
+  EXPECT_TRUE(ukm_service->recording_enabled(ukm::APPS));
+
+  ukm_test_helper.BuildAndStoreLog();
+  const std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
+
+  // Verify that the only sources in the report are APP_ID.
+  // NOTE(crbug/1395143): It was noticed that there was an APP_ID source
+  // generated despite not being explicitly created. No entries are associated
+  // with it though.
+  for (int i = 0; i < report->sources_size(); ++i) {
+    const auto id = report->sources(i).id();
+    EXPECT_EQ(ukm::GetSourceIdType(id), ukm::SourceIdType::APP_ID);
+  }
+
+  // Verify that if an entry exists it is either DEFAULT or APP_ID.
+  // NOTE(crbug/1395144): There were some entries with SourceId of 0, which are
+  // not purged because they do not have a source in the recordings.
+  for (int i = 0; i < report->entries_size(); ++i) {
+    const auto id_type = ukm::GetSourceIdType(report->entries(i).id());
+    EXPECT_EQ(id_type, ukm::SourceIdType::DEFAULT);
+  }
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace metrics
diff --git a/chrome/browser/no_best_effort_tasks_browsertest.cc b/chrome/browser/no_best_effort_tasks_browsertest.cc
index f07f742..00fd11e1 100644
--- a/chrome/browser/no_best_effort_tasks_browsertest.cc
+++ b/chrome/browser/no_best_effort_tasks_browsertest.cc
@@ -173,14 +173,14 @@
   ASSERT_TRUE(have_test_data_dir);
   extension_dir = extension_dir.AppendASCII("extensions")
                       .AppendASCII("no_best_effort_tasks_test_extension");
+  extensions::TestExtensionRegistryObserver observer(
+      extensions::ExtensionRegistry::Get(browser()->profile()));
   extensions::UnpackedInstaller::Create(
       extensions::ExtensionSystem::Get(browser()->profile())
           ->extension_service())
       ->Load(extension_dir);
   scoped_refptr<const extensions::Extension> extension =
-      extensions::TestExtensionRegistryObserver(
-          extensions::ExtensionRegistry::Get(browser()->profile()))
-          .WaitForExtensionReady();
+      observer.WaitForExtensionReady();
   ASSERT_TRUE(extension);
   ASSERT_EQ(kExtensionId, extension->id());
 
diff --git a/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/ExponentialBackoffScheduler.java b/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/ExponentialBackoffScheduler.java
index ab3dea28..7d42b60 100644
--- a/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/ExponentialBackoffScheduler.java
+++ b/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/ExponentialBackoffScheduler.java
@@ -4,18 +4,9 @@
 
 package org.chromium.chrome.browser.omaha;
 
-import android.app.AlarmManager;
-import android.app.PendingIntent;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
 
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.IntentUtils;
-import org.chromium.base.Log;
-
-import java.util.Date;
 import java.util.Random;
 
 import javax.annotation.concurrent.NotThreadSafe;
@@ -77,19 +68,6 @@
         return generateRandomDelay() + getCurrentTime();
     }
 
-    /**
-     * Creates an alarm to fire the specified intent at the specified time.
-     * @param intent The intent to fire.
-     * @return the timestamp of the scheduled intent
-     */
-    public long createAlarm(Intent intent, long timestamp) {
-        PendingIntent retryPIntent = PendingIntent.getService(
-                mContext, 0, intent, IntentUtils.getPendingIntentMutabilityFlag(false));
-        AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        setAlarm(am, timestamp, retryPIntent);
-        return timestamp;
-    }
-
     public int getNumFailedAttempts() {
         SharedPreferences preferences = getSharedPreferences();
         return preferences.getInt(PREFERENCE_FAILED_ATTEMPTS, 0);
@@ -123,20 +101,6 @@
     }
 
     /**
-     * Sets an alarm in the alarm manager.
-     */
-    @VisibleForTesting
-    protected void setAlarm(AlarmManager am, long timestamp, PendingIntent retryPIntent) {
-        Log.d(TAG,
-                "now(" + new Date(getCurrentTime()) + ") refiringAt(" + new Date(timestamp) + ")");
-        try {
-            am.set(AlarmManager.RTC, timestamp, retryPIntent);
-        } catch (SecurityException e) {
-            Log.e(TAG, "Failed to set backoff alarm.");
-        }
-    }
-
-    /**
      * Determines the amount of time to wait for the current delay, then saves it.
      * @return the number of milliseconds to wait.
      */
diff --git a/chrome/browser/persisted_state_db/session_proto_db_factory.h b/chrome/browser/persisted_state_db/session_proto_db_factory.h
index 7c89229..f08b43e 100644
--- a/chrome/browser/persisted_state_db/session_proto_db_factory.h
+++ b/chrome/browser/persisted_state_db/session_proto_db_factory.h
@@ -12,6 +12,8 @@
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/session_proto_db/session_proto_db.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -109,32 +111,37 @@
     return new SessionProtoDB<T>(
         proto_database_provider,
         context->GetPath().AppendASCII(kPersistedStateDBFolder),
-        leveldb_proto::ProtoDbType::PERSISTED_STATE_DATABASE);
+        leveldb_proto::ProtoDbType::PERSISTED_STATE_DATABASE,
+        content::GetUIThreadTaskRunner({}));
   } else if (std::is_base_of<
                  commerce_subscription_db::CommerceSubscriptionContentProto,
                  T>::value) {
     return new SessionProtoDB<T>(
         proto_database_provider,
         context->GetPath().AppendASCII(kCommerceSubscriptionDBFolder),
-        leveldb_proto::ProtoDbType::COMMERCE_SUBSCRIPTION_DATABASE);
+        leveldb_proto::ProtoDbType::COMMERCE_SUBSCRIPTION_DATABASE,
+        content::GetUIThreadTaskRunner({}));
 #if !BUILDFLAG(IS_ANDROID)
   } else if (std::is_base_of<cart_db::ChromeCartContentProto, T>::value) {
     return new SessionProtoDB<T>(
         proto_database_provider,
         context->GetPath().AppendASCII(kChromeCartDBFolder),
-        leveldb_proto::ProtoDbType::CART_DATABASE);
+        leveldb_proto::ProtoDbType::CART_DATABASE,
+        content::GetUIThreadTaskRunner({}));
   } else if (std::is_base_of<coupon_db::CouponContentProto, T>::value) {
     return new SessionProtoDB<T>(
         proto_database_provider,
         context->GetPath().AppendASCII(kCouponDBFolder),
-        leveldb_proto::ProtoDbType::COUPON_DATABASE);
+        leveldb_proto::ProtoDbType::COUPON_DATABASE,
+        content::GetUIThreadTaskRunner({}));
 #else
   } else if (std::is_base_of<merchant_signal_db::MerchantSignalContentProto,
                              T>::value) {
     return new SessionProtoDB<T>(
         proto_database_provider,
         context->GetPath().AppendASCII(kMerchantTrustSignalDBFolder),
-        leveldb_proto::ProtoDbType::MERCHANT_TRUST_SIGNAL_DATABASE);
+        leveldb_proto::ProtoDbType::MERCHANT_TRUST_SIGNAL_DATABASE,
+        content::GetUIThreadTaskRunner({}));
 #endif
   } else {
     // Must add in leveldb_proto::ProtoDbType and database directory folder for
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
index ab88aaec..a7f0c966 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
@@ -43,6 +43,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "net/first_party_sets/global_first_party_sets.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -2416,7 +2417,7 @@
       net::FirstPartySetsContextConfig(
           net::FirstPartySetsContextConfig::OverrideSets{
               {net::SchemefulSite(GURL("https://associate2.test")),
-               {absl::nullopt}}}));
+               net::FirstPartySetEntryOverride()}}));
 
   first_party_sets_policy_service()->InitForTesting();
 
@@ -2541,9 +2542,9 @@
       net::FirstPartySetsContextConfig(
           net::FirstPartySetsContextConfig::OverrideSets{
               {net::SchemefulSite(GURL("https://google.de")),
-               {net::FirstPartySetEntry(
+               net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
                    net::SchemefulSite(GURL("https://new-primary.test")),
-                   net::SiteType::kAssociated, 0)}}}));
+                   net::SiteType::kAssociated, 0))}}));
 
   first_party_sets_policy_service()->InitForTesting();
 
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/accessibility_common_loader.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/accessibility_common_loader.js
index 1603b801..9aea5b6 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/accessibility_common_loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/accessibility_common_loader.js
@@ -24,6 +24,8 @@
     // For tests.
     /** @private {?function()} */
     this.autoclickLoadCallbackForTest_ = null;
+    /** @private {?function()} */
+    this.magnifierLoadCallbackForTest_ = null;
 
     this.init_();
   }
@@ -107,6 +109,11 @@
   onMagnifierUpdated_(type, details) {
     if (details.value && !this.magnifier_) {
       this.magnifier_ = new Magnifier(type);
+      if (this.magnifierLoadCallbackForTest_) {
+        this.magnifier_.setOnLoadDesktopCallbackForTest(
+            this.magnifierLoadCallbackForTest_);
+        this.magnifierLoadCallbackForTest_ = null;
+      }
     } else if (
         !details.value && this.magnifier_ && this.magnifier_.type === type) {
       this.magnifier_.onMagnifierDisabled();
@@ -129,17 +136,27 @@
   }
 
   /**
-   * Used by C++ tests to ensure Autoclick load is completed.
-   * Set on AccessibilityCommon in case Autoclick has not started up yet.
-   * @param {!function()} callback Callback for Autoclick JS load complete.
+   * Used by C++ tests to ensure a feature load is completed.
+   * Set on AccessibilityCommon in case the feature has not started up yet.
+   * @param {string} feature The feature name.
+   * @param {!function()} callback Callback for feature JS load complete.
    */
-  setAutoclickLoadCallbackForTest(callback) {
-    if (!this.autoclick_) {
-      this.autoclickLoadCallbackForTest_ = callback;
-      return;
+  setFeatureLoadCallbackForTest(feature, callback) {
+    if (feature === 'autoclick') {
+      if (!this.autoclick_) {
+        this.autoclickLoadCallbackForTest_ = callback;
+        return;
+      }
+      // Autoclick already loaded.
+      this.autoclick_.setOnLoadDesktopCallbackForTest(callback);
+    } else if (feature === 'magnifier') {
+      if (!this.magnifier_) {
+        this.magnifierLoadCallbackForTest_ = callback;
+        return;
+      }
+      // Magnifier already loaded.
+      this.magnifier_.setOnLoadDesktopCallbackForTest(callback);
     }
-    // Autoclick already loaded.
-    this.autoclick_.setOnLoadDesktopCallbackForTest(callback);
   }
 }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier.js
index b5d29588..6aa1e59 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier.js
@@ -92,6 +92,9 @@
         [], chrome.automation.EventType.MOUSE_DRAGGED,
         event => this.onMouseMovedOrDragged_(event));
 
+    /** @private {?function()} */
+    this.onLoadDesktopCallbackForTest_ = null;
+
     this.init_();
   }
 
@@ -128,6 +131,10 @@
       this.onMouseMovedHandler_.start();
       this.onMouseDraggedHandler_.setNodes(desktop);
       this.onMouseDraggedHandler_.start();
+      if (this.onLoadDesktopCallbackForTest_) {
+        this.onLoadDesktopCallbackForTest_();
+        this.onLoadDesktopCallbackForTest_ = null;
+      }
     });
 
     this.onMagnifierBoundsChangedHandler_.start();
@@ -293,6 +300,20 @@
     this.lastMouseMovedTime_ = new Date();
     this.mouseLocation_ = {x: event.mouseX, y: event.mouseY};
   }
+
+  /**
+   * Used by C++ tests to ensure Magnifier load is competed.
+   * @param {!function()} callback Callback for when desktop is loaded from
+   * automation.
+   */
+  setOnLoadDesktopCallbackForTest(callback) {
+    if (!this.focusHandler_.listening()) {
+      this.onLoadDesktopCallbackForTest_ = callback;
+      return;
+    }
+    // Desktop already loaded.
+    callback();
+  }
 }
 
 /**
diff --git a/chrome/browser/resources/chromeos/accessibility/common/event_handler.js b/chrome/browser/resources/chromeos/accessibility/common/event_handler.js
index 0935898..73d437b 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/event_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/event_handler.js
@@ -83,6 +83,14 @@
     this.listening_ = false;
   }
 
+  /**
+   * @return {boolean} Whether this EventHandler is currently listening for
+   *     events.
+   */
+  listening() {
+    return this.listening_;
+  }
+
   /** @param {?function(!AutomationEvent)} callback */
   setCallback(callback) {
     this.callback_ = callback;
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index b845ac7..dd02a5a 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -201,7 +201,6 @@
     "components/behaviors/login_screen_behavior.m.js",
     "components/behaviors/multi_step_behavior.m.js",
     "components/behaviors/oobe_dialog_host_behavior.m.js",
-    "components/behaviors/oobe_focus_behavior.m.js",
     "components/behaviors/oobe_scrollable_behavior.m.js",
     "components/common_styles/oobe_common_styles.m.js",
     "components/common_styles/oobe_dialog_host_styles.m.js",
@@ -225,6 +224,7 @@
   out_manifest = "$target_gen_dir/$web_components_manifest"
 
   in_files = [
+    "components/behaviors/oobe_focus_behavior.js",
     "components/behaviors/oobe_i18n_behavior.js",
     "components/buttons/oobe_back_button.js",
     "components/buttons/oobe_base_button.js",
diff --git a/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn b/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn
index f0ac960d..c6f983b 100644
--- a/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn
@@ -12,7 +12,7 @@
     ":login_screen_behavior.m",
     ":multi_step_behavior.m",
     ":oobe_dialog_host_behavior.m",
-    ":oobe_focus_behavior.m",
+    ":oobe_focus_behavior",
     ":oobe_i18n_behavior",
     ":oobe_scrollable_behavior.m",
   ]
@@ -41,12 +41,12 @@
   extra_deps = [ ":modulize" ]
 }
 
-js_library("oobe_focus_behavior.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.m.js" ]
+js_library("oobe_focus_behavior") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.js" ]
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":modulize" ]
+  extra_deps = [ ":copy_js" ]
 }
 
 js_library("oobe_i18n_behavior") {
@@ -72,7 +72,6 @@
     "login_screen_behavior.js",
     "multi_step_behavior.js",
     "oobe_dialog_host_behavior.js",
-    "oobe_focus_behavior.js",
     "oobe_scrollable_behavior.js",
   ]
   namespace_rewrites = oobe_namespace_rewrites
@@ -80,6 +79,9 @@
 
 # Copy to target_gen_dir for closure compilation.
 copy("copy_js") {
-  sources = [ "oobe_i18n_behavior.js" ]
+  sources = [
+    "oobe_focus_behavior.js",
+    "oobe_i18n_behavior.js",
+  ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
diff --git a/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.js b/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.js
index b202b36..5c60f1cd 100644
--- a/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.js
+++ b/chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.js
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// clang-format off
-// #import {afterNextRender} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-// clang-format on
+import {afterNextRender} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 /**
  * @fileoverview
  * 'OobeFocusBehavior' is a special behavior which supports focus transferring
- * when new screen is shown.
+ * when a new screen is shown.
  */
 
 /** @polymerBehavior */
-/* #export */ const OobeFocusBehavior = {
+export const OobeFocusBehavior = {
   /**
    * @private
    * Focuses the element. As cr-input uses focusInput() instead of focus() due
@@ -43,13 +41,11 @@
       }
 
       focused = true;
-      Polymer.RenderStatus.afterNextRender(
-          this, () => this.focusOnElement_(focusedElements[i]));
+      afterNextRender(this, () => this.focusOnElement_(focusedElements[i]));
       break;
     }
     if (!focused && focusedElements.length > 0) {
-      Polymer.RenderStatus.afterNextRender(
-          this, () => this.focusOnElement_(focusedElements[0]));
+      afterNextRender(this, () => this.focusOnElement_(focusedElements[0]));
     }
 
     this.fire('show-dialog');
@@ -65,6 +61,6 @@
 OobeFocusBehavior.Proto;
 
 /** @interface */
-/* #export */ class OobeFocusBehaviorInterface {
+export class OobeFocusBehaviorInterface {
   focusMarkedElement(root) {}
 }
diff --git a/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn b/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn
index 7f58b30..c35bbda 100644
--- a/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn
@@ -26,7 +26,7 @@
 js_library("oobe_content_dialog") {
   sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/components/dialogs/oobe_content_dialog.js" ]
   deps = [
-    "../behaviors:oobe_focus_behavior.m",
+    "../behaviors:oobe_focus_behavior",
     "../behaviors:oobe_scrollable_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:cr.m",
diff --git a/chrome/browser/resources/chromeos/login/components/dialogs/oobe_content_dialog.js b/chrome/browser/resources/chromeos/login/components/dialogs/oobe_content_dialog.js
index 45675ea7..c27add2f 100644
--- a/chrome/browser/resources/chromeos/login/components/dialogs/oobe_content_dialog.js
+++ b/chrome/browser/resources/chromeos/login/components/dialogs/oobe_content_dialog.js
@@ -11,7 +11,7 @@
 
 import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {OobeFocusBehavior, OobeFocusBehaviorInterface} from '../behaviors/oobe_focus_behavior.m.js';
+import {OobeFocusBehavior, OobeFocusBehaviorInterface} from '../behaviors/oobe_focus_behavior.js';
 import {OobeScrollableBehavior, OobeScrollableBehaviorInterface} from '../behaviors/oobe_scrollable_behavior.m.js';
 
 /**
diff --git a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
index 304554c..883637d 100644
--- a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
+++ b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
@@ -110,6 +110,7 @@
   "ash/webui/common/resources/network/network_select.html",
   "ash/webui/common/resources/network/onc_mojo.html",
   "chrome/browser/resources/chromeos/assistant_optin_flow.html",
+  "chrome/browser/resources/chromeos/login/components/behaviors/oobe_focus_behavior.html",
   "chrome/browser/resources/chromeos/login/components/behaviors/oobe_i18n_behavior.html",
   "chrome/browser/resources/chromeos/login/components/buttons/oobe_back_button.html",
   "chrome/browser/resources/chromeos/login/components/buttons/oobe_base_button.html",
diff --git a/chrome/browser/resources/downloads/constants.ts b/chrome/browser/resources/downloads/constants.ts
index cf70095b..839f6c58 100644
--- a/chrome/browser/resources/downloads/constants.ts
+++ b/chrome/browser/resources/downloads/constants.ts
@@ -33,6 +33,6 @@
   PAUSED = 'PAUSED',
   DANGEROUS = 'DANGEROUS',
   INTERRUPTED = 'INTERRUPTED',
-  MIXED_CONTENT = 'MIXED_CONTENT',
+  INSECURE = 'INSECURE',
   ASYNC_SCANNING = 'ASYNC_SCANNING',
 }
diff --git a/chrome/browser/resources/downloads/item.ts b/chrome/browser/resources/downloads/item.ts
index d4dcfbe..dbbd06199 100644
--- a/chrome/browser/resources/downloads/item.ts
+++ b/chrome/browser/resources/downloads/item.ts
@@ -273,8 +273,8 @@
         }
         break;
 
-      case States.MIXED_CONTENT:
-        return loadTimeData.getString('mixedContentDownloadDesc');
+      case States.INSECURE:
+        return loadTimeData.getString('insecureDownloadDesc');
 
       case States.DANGEROUS:
         switch (data.dangerType) {
@@ -391,7 +391,7 @@
 
   private computeIsDangerous_(): boolean {
     return this.data.state === States.DANGEROUS ||
-        this.data.state === States.MIXED_CONTENT;
+        this.data.state === States.INSECURE;
   }
 
   private computeIsInProgress_(): boolean {
@@ -577,7 +577,7 @@
       // Make the file name collapsible.
       p.collapsible = !!p.arg;
     });
-    const canUndo = !this.data.isDangerous && !this.data.isMixedContent;
+    const canUndo = !this.data.isDangerous && !this.data.isInsecure;
     getToastManager().showForStringPieces(pieces, /* hideSlotted= */ !canUndo);
 
     // Stop propagating a click to the document to remove toast.
diff --git a/chrome/browser/resources/downloads/manager.ts b/chrome/browser/resources/downloads/manager.ts
index ab4f8240..e9cedd6 100644
--- a/chrome/browser/resources/downloads/manager.ts
+++ b/chrome/browser/resources/downloads/manager.ts
@@ -214,8 +214,8 @@
         loadTimeData.getBoolean('allowDeletingHistory') &&
         this.items_.some(
             ({state}) => state !== States.DANGEROUS &&
-                state !== States.MIXED_CONTENT &&
-                state !== States.IN_PROGRESS && state !== States.PAUSED);
+                state !== States.INSECURE && state !== States.IN_PROGRESS &&
+                state !== States.PAUSED);
 
     if (this.inSearchMode_) {
       this.announcerDebouncer_ = Debouncer.debounce(
@@ -281,7 +281,7 @@
 
     this.mojoHandler_.clearAll();
     const canUndo =
-        this.items_.some(data => !data.isDangerous && !data.isMixedContent);
+        this.items_.some(data => !data.isDangerous && !data.isInsecure);
     getToastManager().show(
         loadTimeData.getString('toastClearedAll'),
         /* hideSlotted= */ !canUndo);
diff --git a/chrome/browser/resources/downloads/toolbar.ts b/chrome/browser/resources/downloads/toolbar.ts
index 7bdfc0f..c23e64b 100644
--- a/chrome/browser/resources/downloads/toolbar.ts
+++ b/chrome/browser/resources/downloads/toolbar.ts
@@ -98,7 +98,7 @@
     this.mojoHandler_!.clearAll();
     this.$.moreActionsMenu.close();
     const canUndo =
-        this.items.some(data => !data.isDangerous && !data.isMixedContent);
+        this.items.some(data => !data.isDangerous && !data.isInsecure);
     getToastManager().show(
         loadTimeData.getString('toastClearedAll'),
         /* hideSlotted= */ !canUndo);
diff --git a/chrome/browser/resources/nearby_internals/shared_style.js b/chrome/browser/resources/nearby_internals/shared_style.js
index 97b8214..5614189 100644
--- a/chrome/browser/resources/nearby_internals/shared_style.js
+++ b/chrome/browser/resources/nearby_internals/shared_style.js
@@ -4,6 +4,8 @@
 
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
+styleMod.appendChild(html`{__html_template__}`.content);
 styleMod.register('shared-style');
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
index c3f278e..4ce84bd 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
@@ -95,15 +95,15 @@
               active="[[isDeviceInhibited_]]">
           </paper-spinner-lite>
         </template>
-        <template is="dom-if" if="[[shouldShowAddESimButton_(
+        <template is="dom-if" if="[[shouldShowAddEsimButton_(
             cellularDeviceState)]]" restamp>
           <cr-policy-indicator indicator-type="devicePolicy"
-              hidden="[[!shouldShowAddESimPolicyIcon_(globalPolicy)]]"
+              hidden="[[!shouldShowAddEsimPolicyIcon_(globalPolicy)]]"
               icon-aria-label="$i18n{internetAddCellular}">
           </cr-policy-indicator>
           <cr-icon-button class="icon-add-cellular add-button"
               aria-label="$i18n{internetAddCellular}" id="addESimButton"
-              disabled="[[isAddESimButtonDisabled_(cellularDeviceState,
+              disabled="[[isAddEsimButtonDisabled_(cellularDeviceState,
                   globalPolicy)]]"
               on-click="onAddEsimButtonTap_">
           </cr-icon-button>
@@ -111,7 +111,7 @@
         </template>
         <cr-icon-button id="moreESim" class="icon-more-vert"
             title="$i18n{moreActions}"
-            on-click="onESimDotsClick_">
+            on-click="onEsimDotsClick_">
         </cr-icon-button>
         <cr-lazy-render id="menu">
           <template>
@@ -145,12 +145,12 @@
       </network-list>
     </div>
   </template>
-  <template is="dom-if" if="[[shouldShowNoESimMessageOrDownloadLink_(
+  <template is="dom-if" if="[[shouldShowNoEsimMessageOrDownloadLink_(
       cellularDeviceState.inhibitReason, eSimNetworks_,
       eSimPendingProfileItems_)]]" restamp>
     <div id="eSimNoNetworkFound"
         class="cellular-network-content cellular-not-setup flex">
-      <div hidden="[[!shouldShowNoESimSubtextMessage_(
+      <div hidden="[[!shouldShowNoEsimSubtextMessage_(
                globalPolicy.allowOnlyPolicyCellularNetworks)]]">
         $i18n{eSimNetworkNotSetup}
       </div>
@@ -158,14 +158,14 @@
           link-disabled = "[[isDeviceInhibited_]]"
           on-link-clicked="onEsimLearnMoreClicked_"
           localized-string="$i18n{eSimNetworkNotSetupWithDownloadLink}"
-          hidden="[[shouldShowNoESimSubtextMessage_(
+          hidden="[[shouldShowNoEsimSubtextMessage_(
               globalPolicy.allowOnlyPolicyCellularNetworks)]]">
       </localized-link>
     </div>
   </template>
 </template>
 <template is="dom-if"
-    if="[[shouldShowPSimSection_(pSimNetworks_, cellularDeviceState,
+    if="[[shouldShowPsimSection_(pSimNetworks_, cellularDeviceState,
       cellularDeviceState.*)]]" restamp>
   <div class="cellular-network-list-separator"></div>
   <div class="cellular-network-list-header settings-box-text">
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.ts b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.ts
index 43610449..5b6280b 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.ts
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.ts
@@ -239,7 +239,7 @@
 
     this.networkConfig_ =
         MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
-    this.fetchEuiccAndESimPendingProfileList_();
+    this.fetchEuiccAndEsimPendingProfileList_();
   }
 
   override ready(): void {
@@ -256,11 +256,11 @@
   }
 
   override onProfileListChanged(euicc: EuiccRemote): void {
-    this.fetchESimPendingProfileListForEuicc_(euicc);
+    this.fetchEsimPendingProfileListForEuicc_(euicc);
   }
 
   override onAvailableEuiccListChanged(): void {
-    this.fetchEuiccAndESimPendingProfileList_();
+    this.fetchEuiccAndEsimPendingProfileList_();
   }
 
   override async onProfileChanged(profile: ESimProfileRemote): Promise<void> {
@@ -278,7 +278,7 @@
         NetworkList.CustomItemType.ESIM_PENDING_PROFILE;
   }
 
-  private fetchEuiccAndESimPendingProfileList_(): void {
+  private fetchEuiccAndEsimPendingProfileList_(): void {
     getEuicc().then(euicc => {
       if (!euicc) {
         return;
@@ -293,7 +293,7 @@
         return;
       }
 
-      this.fetchESimPendingProfileListForEuicc_(euicc);
+      this.fetchEsimPendingProfileListForEuicc_(euicc);
     });
   }
 
@@ -311,30 +311,30 @@
     return !!this.euicc_ && eSimSlots > 0;
   }
 
-  private async fetchESimPendingProfileListForEuicc_(euicc: EuiccRemote):
+  private async fetchEsimPendingProfileListForEuicc_(euicc: EuiccRemote):
       Promise<void> {
     const profiles = await getPendingESimProfiles(euicc);
-    this.processESimPendingProfiles_(profiles);
+    this.processEsimPendingProfiles_(profiles);
   }
 
-  private async processESimPendingProfiles_(profiles: ESimProfileRemote[]):
+  private async processEsimPendingProfiles_(profiles: ESimProfileRemote[]):
       Promise<void> {
     this.profilesMap_ = new Map();
     const eSimPendingProfilePromises =
-        profiles.map(this.createESimPendingProfilePromise_.bind(this));
+        profiles.map(this.createEsimPendingProfilePromise_.bind(this));
     const eSimPendingProfileItems =
         await Promise.all(eSimPendingProfilePromises);
     this.eSimPendingProfileItems_ = eSimPendingProfileItems;
   }
 
-  private async createESimPendingProfilePromise_(profile: ESimProfileRemote):
+  private async createEsimPendingProfilePromise_(profile: ESimProfileRemote):
       Promise<NetworkList.CustomItemState> {
     const response = await profile.getProperties();
     this.profilesMap_.set(response.properties.iccid, profile);
-    return this.createESimPendingProfileItem_(response.properties);
+    return this.createEsimPendingProfileItem_(response.properties);
   }
 
-  private createESimPendingProfileItem_(properties: ESimProfileProperties):
+  private createEsimPendingProfileItem_(properties: ESimProfileProperties):
       NetworkList.CustomItemState {
     return {
       customItemType: properties.state === ProfileState.kInstalling ?
@@ -381,7 +381,7 @@
     return totalListLength > 0;
   }
 
-  private shouldShowPSimSection_(
+  private shouldShowPsimSection_(
       pSimNetworks: OncMojo.NetworkStateProperties[],
       cellularDeviceState: OncMojo.DeviceStateProperties|undefined): boolean {
     const {pSimSlots} = getSimSlotCount(cellularDeviceState);
@@ -421,7 +421,7 @@
     this.dispatchEvent(showCellularSetupEvent);
   }
 
-  private onESimDotsClick_(e: Event): void {
+  private onEsimDotsClick_(e: Event): void {
     const menu = this.shadowRoot!
                      .querySelector<CrLazyRenderElement<CrActionMenuElement>>(
                          '#menu')!.get();
@@ -470,14 +470,14 @@
     this.shouldShowInstallErrorDialog_ = false;
   }
 
-  private shouldShowAddESimButton_(cellularDeviceState:
+  private shouldShowAddEsimButton_(cellularDeviceState:
                                        OncMojo.DeviceStateProperties|
                                    undefined): boolean {
     assert(this.euicc_);
     return this.deviceIsEnabled_(cellularDeviceState);
   }
 
-  private isAddESimButtonDisabled_(
+  private isAddEsimButtonDisabled_(
       cellularDeviceState: OncMojo.DeviceStateProperties|undefined,
       globalPolicy: GlobalPolicy): boolean {
     if (this.isDeviceInhibited_) {
@@ -497,7 +497,7 @@
    * should be shown. This policy icon indicates the reason of disabling the
    * add cellular button.
    */
-  private shouldShowAddESimPolicyIcon_(globalPolicy: GlobalPolicy): boolean {
+  private shouldShowAddEsimPolicyIcon_(globalPolicy: GlobalPolicy): boolean {
     return globalPolicy && globalPolicy.allowOnlyPolicyCellularNetworks;
   }
 
@@ -563,7 +563,7 @@
    * download eSIM profile link should be shown in eSIM section. This message
    * should not be shown when adding new eSIM profiles.
    */
-  private shouldShowNoESimMessageOrDownloadLink_(
+  private shouldShowNoEsimMessageOrDownloadLink_(
       inhibitReason: InhibitReason,
       eSimNetworks: NetworkList.NetworkListItemType[],
       eSimPendingProfiles: NetworkList.CustomItemState[]): boolean {
@@ -579,7 +579,7 @@
    * shown in eSIM section. This message should not be shown when the download
    * eSIM profile link is shown.
    */
-  private shouldShowNoESimSubtextMessage_(): boolean {
+  private shouldShowNoEsimSubtextMessage_(): boolean {
     if (this.globalPolicy &&
         this.globalPolicy.allowOnlyPolicyCellularNetworks) {
       return true;
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
index 98c4d96..8f82da0 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
@@ -26,7 +26,12 @@
     padding-inline-start: 0;
   }
 
-  settings-toggle-button {
+  settings-dropdown-menu {
+    --md-select-width: 100%;
+  }
+
+  settings-toggle-button,
+  cr-link-row {
     padding-inline-end: var(--cr-section-padding);
     padding-inline-start: var(--cr-section-padding);
   }
@@ -159,6 +164,11 @@
       </cr-button>
     </cr-input>
   </div>
+  <cr-link-row class="settings-box"
+      label="$i18n{selectToSpeakTextToSpeechSettingsLink}"
+      on-click="onTextToSpeechSettingsTap_" external
+      embedded>
+  </cr-link-row>
   <template is="dom-if"
       if="[[isExperimentalAccessibilitySelectToSpeakVoiceSwitchingEnabled_]]">
     <!-- TODO(crbug.com/1354821): Figure out how to make settings-toggle-button
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
index e8a1bd26..b50a687 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
@@ -20,11 +20,11 @@
 import {DropdownMenuOptionList} 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} 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 {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
+import {Route, Router} from '../router.js';
 
 import {getTemplate} from './select_to_speak_subpage.html.js';
 import {SelectToSpeakSubpageBrowserProxy, SelectToSpeakSubpageBrowserProxyImpl} from './select_to_speak_subpage_browser_proxy.js';
@@ -578,6 +578,12 @@
       map.set(lang, [voice]);
     }
   }
+
+  private onTextToSpeechSettingsTap_(): void {
+    Router.getInstance().navigateTo(
+        routes.MANAGE_TTS_SETTINGS,
+        /* dynamicParams= */ undefined, /* removeSearch= */ true);
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
index 72d5463..389a62e3 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
@@ -2,10 +2,6 @@
   #appNotificationsList {
     margin-top: 24px;
   }
-
-  :host([is-dnd-enabled_]) #doNotDisturbToggle {
-    color: var(--cros-text-color-prominent);
-  }
 </style>
 <settings-toggle-button
     id="doNotDisturbToggle"
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.html
index 8cc2ec7c..6d7189af 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.html
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.html
@@ -3,6 +3,7 @@
     --power-bookmark-row-image-size: 56px;
     --power-bookmark-row-height: 68px;
     --power-bookmark-row-checkbox-width: 0;
+    --power-bookmark-row-trailing-icon-size: 0px;
     display: block;
     user-select: none;
     white-space: nowrap;
@@ -69,8 +70,14 @@
     background-color: var(--cr-primary-text-color);
   }
 
+  .description {
+    color: var(--cr-secondary-text-color);
+    font-size: var(--mwb-secondary-text-font-size);
+  }
+
   .row {
-    --power-bookmark-row-image-padding: 32px;
+    --row-end-padding: 18px;
+    --row-start-padding: 32px;
     align-items: center;
     appearance: none;
     background: transparent;
@@ -79,10 +86,12 @@
     color: currentColor;
     display: grid;
     font-size: var(--mwb-primary-text-font-size);
-    grid-template-areas: 'checkbox image content';
+    grid-template-areas: 'checkbox image content trailing-icon';
     grid-template-columns: var(--power-bookmark-row-checkbox-width)
                            calc(var(--power-bookmark-row-image-size) +
-                                var(--power-bookmark-row-image-padding)) auto;
+                                var(--row-start-padding)) auto
+                           calc(var(--power-bookmark-row-trailing-icon-size) +
+                                var(--row-end-padding));
     height: var(--power-bookmark-row-height);
     padding: 0;
     text-align: start;
@@ -90,26 +99,12 @@
   }
 
   .row:hover {
+    --power-bookmark-row-trailing-icon-size: 24px;
     background-color: var(--mwb-list-item-hover-background-color);
   }
 
-  .row:focus-within,
-  .row:focus-within:hover {
-    background-color: var(--mwb-list-item-selected-background-color);
-    outline: none;
-  }
-
-  .row:focus-visible:focus-within {
-    outline: none;
-  }
-
-  .row:active {
-    background-color: var(--cr-active-background-color);
-  }
-
-  .description {
-    color: var(--cr-secondary-text-color);
-    font-size: var(--mwb-secondary-text-font-size);
+  .row:hover > .trailing-icon {
+    display: block;
   }
 
   .text-container {
@@ -127,6 +122,13 @@
     gap: 4px;
   }
 
+  .trailing-icon {
+    --cr-icon-button-margin-start: 8px;
+    --cr-icon-button-size: var(--power-bookmark-row-trailing-icon-size);
+    display: none;
+    grid-area: trailing-icon;
+  }
+
   .url-icon {
     background-position: center;
     background-repeat: no-repeat;
@@ -143,6 +145,7 @@
       title="[[bookmark.title]]"
       data-bookmark="[[bookmark]]"
       on-click="onRowClicked_"
+      on-contextmenu="onContextMenu_"
       disabled="[[hasCheckbox]]">
     <div id="bookmarkImage" class="bookmark-image"></div>
     <div class="text-container">
@@ -150,5 +153,9 @@
       <div class="content description">[[description]]</div>
       <slot name="extra-content-container"></slot>
     </div>
+    <cr-icon-button class="trailing-icon" iron-icon="[[trailingIcon]]"
+        hidden="[[!trailingIcon]]" on-click="onTrailingIconClicked_"
+        disabled="[[hasCheckbox]]" aria-label="[[trailingIconAriaLabel_]]">
+    </cr-button>
   </button>
 </div>
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.ts
index 23926d2..4038acc6 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmark_row.ts
@@ -4,6 +4,7 @@
 
 import './power_bookmark_chip.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
+import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
@@ -51,12 +52,25 @@
         reflectToAttribute: true,
         value: false,
       },
+
+      trailingIcon: {
+        type: String,
+        value: '',
+      },
+
+      trailingIconAriaLabel: {
+        type: String,
+        value: '',
+      },
     };
   }
 
   bookmark: chrome.bookmarks.BookmarkTreeNode;
   compact: boolean;
+  description: string;
   hasCheckbox: boolean;
+  trailingIcon: string;
+  trailingIconAriaLabel: string;
 
   /**
    * Add the appropriate image for the given bookmark and compact/expanded
@@ -100,6 +114,40 @@
   }
 
   /**
+   * Dispatches a custom click event when the user right-clicks anywhere on the
+   * row.
+   */
+  private onContextMenu_(event: MouseEvent) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.dispatchEvent(new CustomEvent('context-menu', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        bookmark: this.bookmark,
+        event: event,
+      },
+    }));
+  }
+
+  /**
+   * Dispatches a custom click event when the user clicks anywhere on the
+   * trailing icon button.
+   */
+  private onTrailingIconClicked_(event: MouseEvent) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.dispatchEvent(new CustomEvent('trailing-icon-clicked', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        bookmark: this.bookmark,
+        event: event,
+      },
+    }));
+  }
+
+  /**
    * Dispatches a custom click event when the user clicks on the checkbox.
    */
   private onCheckboxChange_(event: Event) {
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html
new file mode 100644
index 0000000..68bbfeb
--- /dev/null
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html
@@ -0,0 +1,19 @@
+<style include="cr-icons cr-hidden-style">
+  iron-icon {
+    --icon-size: 16px;
+    height: var(--icon-size);
+    width: var(--icon-size);
+  }
+
+  .dropdown-item {
+    gap: 18px;
+  }
+</style>
+
+<cr-action-menu id="menu">
+  <template is="dom-repeat" items="[[menuItems_]]">
+    <button class="dropdown-item" on-click="onMenuItemClicked_">
+      [[item]]
+    </button>
+  </template>
+</cr-action-menu>
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts
new file mode 100644
index 0000000..4658bee9
--- /dev/null
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts
@@ -0,0 +1,96 @@
+// 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 '../strings.m.js';
+import './icons.html.js';
+import '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
+import '//resources/cr_elements/icons.html.js';
+
+import {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {ActionSource} from './bookmarks.mojom-webui.js';
+import {BookmarksApiProxy, BookmarksApiProxyImpl} from './bookmarks_api_proxy.js';
+import {getTemplate} from './power_bookmarks_context_menu.html.js';
+
+export interface PowerBookmarksContextMenuElement {
+  $: {
+    menu: CrActionMenuElement,
+  };
+}
+
+export class PowerBookmarksContextMenuElement extends PolymerElement {
+  static get is() {
+    return 'power-bookmarks-context-menu';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      bookmark_: Object,
+
+      depth_: Number,
+
+      menuItems_: {
+        type: Array,
+        value: () => [loadTimeData.getString('menuOpenNewTab')],
+      },
+    };
+  }
+
+  private bookmarksApi_: BookmarksApiProxy =
+      BookmarksApiProxyImpl.getInstance();
+  private bookmark_: chrome.bookmarks.BookmarkTreeNode;
+  private depth_: number;
+  private menuItems_: string[];
+
+  showAt(
+      event: MouseEvent, bookmark: chrome.bookmarks.BookmarkTreeNode,
+      depth: number) {
+    this.bookmark_ = bookmark;
+    this.depth_ = depth;
+    this.$.menu.showAt(event.target as HTMLElement);
+  }
+
+  showAtPosition(
+      event: MouseEvent, bookmark: chrome.bookmarks.BookmarkTreeNode,
+      depth: number) {
+    this.bookmark_ = bookmark;
+    this.depth_ = depth;
+    this.$.menu.showAtPosition({top: event.clientY, left: event.clientX});
+  }
+
+  private onMenuItemClicked_(event: DomRepeatEvent<string>) {
+    event.preventDefault();
+    event.stopPropagation();
+    switch (event.model.index) {
+      case 0:
+        // Open in new tab
+        this.bookmarksApi_.openBookmark(
+            this.bookmark_!.id, this.depth_, {
+              middleButton: true,
+              altKey: false,
+              ctrlKey: false,
+              metaKey: false,
+              shiftKey: false,
+            },
+            ActionSource.kBookmark);
+    }
+    this.$.menu.close();
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'power-bookmarks-context-menu': PowerBookmarksContextMenuElement;
+  }
+}
+
+customElements.define(
+    PowerBookmarksContextMenuElement.is, PowerBookmarksContextMenuElement);
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
index 7684284..d6b5f741 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
@@ -269,9 +269,12 @@
       <power-bookmark-row id="bookmark-[[item.id]]" bookmark="[[item]]"
           description= "[[getBookmarkDescription_(item, compactDescriptions_.*,
                           expandedDescriptions_.*, compact_)]]"
-          compact="[[compact_]]"
+          compact="[[compact_]]" trailing-icon="cr:more-vert"
+          trailing-icon-aria-label="TODO"
           has-checkbox="[[editing_]]"
           on-row-clicked="onRowClicked_"
+          on-context-menu="onShowContextMenuClicked_"
+          on-trailing-icon-clicked="onShowContextMenuClicked_"
           on-checkbox-change="onRowSelectedChange_">
         <div slot="extra-content-container">
           <template is="dom-if"
@@ -336,3 +339,9 @@
     $i18n{compactView}
   </button>
 </cr-action-menu>
+
+<cr-lazy-render id="contextMenu">
+  <template>
+    <power-bookmarks-context-menu></power-bookmarks-context-menu>
+  </template>
+</cr-lazy-render>
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
index 7457908..22dbe3d 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
@@ -6,24 +6,28 @@
 import './commerce/shopping_list.js';
 import './icons.html.js';
 import './power_bookmark_chip.js';
+import './power_bookmarks_context_menu.js';
 import './power_bookmark_row.js';
 import '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import '//resources/cr_elements/cr_button/cr_button.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
+import '//resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import '//resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import '//resources/cr_elements/icons.html.js';
 
+import {getInstance as getAnnouncerInstance} from '//resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
 import {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
-import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
-import {listenOnce} from 'chrome://resources/js/util_ts.js';
-import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLazyRenderElement} from '//resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
+import {loadTimeData} from '//resources/js/load_time_data.js';
+import {PluralStringProxyImpl} from '//resources/js/plural_string_proxy.js';
+import {listenOnce} from '//resources/js/util_ts.js';
+import {DomRepeatEvent, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ActionSource} from './bookmarks.mojom-webui.js';
 import {BookmarksApiProxy, BookmarksApiProxyImpl} from './bookmarks_api_proxy.js';
 import {BookmarkProductInfo} from './commerce/shopping_list.mojom-webui.js';
 import {ShoppingListApiProxy, ShoppingListApiProxyImpl} from './commerce/shopping_list_api_proxy.js';
+import {PowerBookmarksContextMenuElement} from './power_bookmarks_context_menu.js';
 import {getTemplate} from './power_bookmarks_list.html.js';
 
 function getBookmarkName(bookmark: chrome.bookmarks.BookmarkTreeNode): string {
@@ -38,6 +42,7 @@
 
 export interface PowerBookmarksListElement {
   $: {
+    contextMenu: CrLazyRenderElement<PowerBookmarksContextMenuElement>,
     powerBookmarksContainer: HTMLElement,
     sortMenu: CrActionMenuElement,
   };
@@ -633,6 +638,22 @@
     this.searchQuery_ = e.detail.toLocaleLowerCase();
   }
 
+  private onShowContextMenuClicked_(
+      event: CustomEvent<
+          {bookmark: chrome.bookmarks.BookmarkTreeNode, event: MouseEvent}>) {
+    event.preventDefault();
+    event.stopPropagation();
+    if (event.detail.event.button === 0) {
+      this.$.contextMenu.get().showAt(
+          event.detail.event, event.detail.bookmark,
+          this.activeFolderPath_.length);
+    } else {
+      this.$.contextMenu.get().showAtPosition(
+          event.detail.event, event.detail.bookmark,
+          this.activeFolderPath_.length);
+    }
+  }
+
   private onShowSortMenuClicked_(event: MouseEvent) {
     event.preventDefault();
     event.stopPropagation();
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 c3f8d30..81297530 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
@@ -8,10 +8,15 @@
     width: 288px;
   }
 
-  #themeSnapshot img {
+  .image {
+    border: 1px solid rgba(0, 0, 0, .14);
     border-radius: 20px;
     height: 162px;
     margin-bottom: 4px;
+    width: inherit;
+  }
+
+  #themeSnapshot img {
     object-fit: fill;
   }
 
@@ -20,10 +25,22 @@
     overflow: hidden;
     white-space: nowrap;
   }
+
+  #classicChrome {
+    background-color: var(--customize-chrome-color-background-color);
+  }
 </style>
 <div id="themeSnapshot">
-  <img is="cr-auto-img" auto-src="[[theme_.backgroundImage.url.url]]"
-      draggable="false">
-  </img>
-  <label id="themeTitle">[[theme_.backgroundImage.title]]</label>
+  <template is="dom-if" if="[[showThemeSnapshot_]]">
+    <img class="image" is="cr-auto-img"
+        auto-src="[[theme_.backgroundImage.url.url]]" draggable="false">
+    </img>
+    <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>
+    <label id="themeTitle">$i18n{classicChrome}</label>
+  </template>
 </div>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
index dd60169..22c030ae 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
@@ -5,6 +5,7 @@
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {skColorToRgba} from 'chrome://resources/js/color_utils.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerInterface, Theme} from './customize_chrome.mojom-webui.js';
@@ -24,11 +25,17 @@
   static get properties() {
     return {
       theme_: Object,
+
+      showThemeSnapshot_: {
+        type: Boolean,
+        computed: 'computeShowThemeSnapshot_(theme_)',
+      },
     };
   }
 
   private theme_: Theme;
   private setThemeListenerId_: number|null = null;
+  private showThemeSnapshot_: boolean;
 
   private callbackRouter_: CustomizeChromePageCallbackRouter;
   private pageHandler_: CustomizeChromePageHandlerInterface;
@@ -44,6 +51,10 @@
     this.setThemeListenerId_ =
         this.callbackRouter_.setTheme.addListener((theme: Theme) => {
           this.theme_ = theme;
+          this.updateStyles({
+            '--customize-chrome-color-background-color':
+                skColorToRgba(this.theme_.backgroundColor),
+          });
         });
     this.pageHandler_.updateTheme();
   }
@@ -54,6 +65,10 @@
     assert(this.setThemeListenerId_);
     this.callbackRouter_.removeListener(this.setThemeListenerId_);
   }
+
+  private computeShowThemeSnapshot_(): boolean {
+    return !!this.theme_.backgroundImage;
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/side_panel.gni b/chrome/browser/resources/side_panel/side_panel.gni
index 70c83783..1ecb7159 100644
--- a/chrome/browser/resources/side_panel/side_panel.gni
+++ b/chrome/browser/resources/side_panel/side_panel.gni
@@ -16,6 +16,7 @@
   "bookmarks/bookmarks_list.ts",
   "bookmarks/power_bookmark_chip.ts",
   "bookmarks/power_bookmark_row.ts",
+  "bookmarks/power_bookmarks_context_menu.ts",
   "bookmarks/power_bookmarks_list.ts",
   "history_clusters/app.ts",
   "bookmarks/commerce/shopping_list.ts",
diff --git a/chrome/browser/search/background/ntp_custom_background_service.h b/chrome/browser/search/background/ntp_custom_background_service.h
index f75a87f..39061396 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.h
+++ b/chrome/browser/search/background/ntp_custom_background_service.h
@@ -53,7 +53,8 @@
   void UpdateBackgroundFromSync();
 
   // Invoked when the background is reset on the NTP.
-  void ResetCustomBackgroundInfo();
+  // Virtual for testing.
+  virtual void ResetCustomBackgroundInfo();
 
   // Invoked when a custom background is configured on the NTP.
   void SetCustomBackgroundInfo(const GURL& background_url,
diff --git a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.cc b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.cc
index 928af296..a21729f3e 100644
--- a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.cc
+++ b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android.cc
@@ -85,6 +85,7 @@
       "segment_unknown_selection_ttl_days", kChromeStartDefaultUnknownTTLDays);
   config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
   config->unknown_selection_ttl = base::Days(unknown_selection_ttl_days);
+  config->is_boolean_segment = true;
 
   return config;
 }
diff --git a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.cc b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.cc
index 3562c1a..52e9ffa 100644
--- a/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.cc
+++ b/chrome/browser/segmentation_platform/default_model/chrome_start_model_android_v2.cc
@@ -63,6 +63,7 @@
       kChromeStartV2DefaultSelectionTTLDays);
   config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
   config->unknown_selection_ttl = config->segment_selection_ttl;
+  config->is_boolean_segment = true;
 
   return config;
 }
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
index 43c404c..04039d3 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -47,8 +47,6 @@
 
 namespace {
 
-constexpr int kChromeLowUserEngagementSelectionTTLDays = 7;
-
 #if BUILDFLAG(IS_ANDROID)
 
 constexpr int kAdaptiveToolbarDefaultSelectionTTLDays = 56;
@@ -117,52 +115,6 @@
 
 #endif  // BUILDFLAG(IS_ANDROID)
 
-bool IsLowEngagementFeatureEnabled() {
-  // TODO(ssid): Remove this extra feature and change feature guide to use the
-  // segmentation defined feature.
-#if BUILDFLAG(IS_ANDROID)
-  if (base::FeatureList::IsEnabled(
-          feature_guide::features::kSegmentationModelLowEngagedUsers)) {
-    return true;
-  }
-#endif
-  return base::FeatureList::IsEnabled(
-      features::kSegmentationPlatformLowEngagementFeature);
-}
-
-std::unique_ptr<ModelProvider> GetLowEngagementDefaultModel() {
-  if (!base::GetFieldTrialParamByFeatureAsBool(
-          features::kSegmentationPlatformLowEngagementFeature,
-          kDefaultModelEnabledParam, true)) {
-    return nullptr;
-  }
-  return std::make_unique<LowUserEngagementModel>();
-}
-std::unique_ptr<Config> GetConfigForChromeLowUserEngagement() {
-  auto config = std::make_unique<Config>();
-  config->segmentation_key = kChromeLowUserEngagementSegmentationKey;
-  config->segmentation_uma_name = kChromeLowUserEngagementUmaName;
-  config->AddSegmentId(
-      SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT,
-      GetLowEngagementDefaultModel());
-
-#if BUILDFLAG(IS_ANDROID)
-  int segment_selection_ttl_days = base::GetFieldTrialParamByFeatureAsInt(
-      feature_guide::features::kSegmentationModelLowEngagedUsers,
-      kVariationsParamNameSegmentSelectionTTLDays,
-      kChromeLowUserEngagementSelectionTTLDays);
-#else
-  int segment_selection_ttl_days = base::GetFieldTrialParamByFeatureAsInt(
-      features::kSegmentationPlatformLowEngagementFeature,
-      kVariationsParamNameSegmentSelectionTTLDays,
-      kChromeLowUserEngagementSelectionTTLDays);
-#endif
-
-  config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
-  config->unknown_selection_ttl = base::Days(segment_selection_ttl_days);
-  return config;
-}
-
 }  // namespace
 
 std::vector<std::unique_ptr<Config>> GetSegmentationPlatformConfig(
@@ -184,11 +136,8 @@
   configs.emplace_back(PowerUserSegment::GetConfig());
   configs.emplace_back(FrequentFeatureUserModel::GetConfig());
 #endif
-  // TODO(ssid): Move this check into the model.
-  if (IsLowEngagementFeatureEnabled()) {
-    configs.emplace_back(GetConfigForChromeLowUserEngagement());
-  }
 
+  configs.emplace_back(LowUserEngagementModel::GetConfig());
   configs.emplace_back(SearchUserModel::GetConfig());
   configs.emplace_back(FeedUserSegment::GetConfig());
   configs.emplace_back(ShoppingUserModel::GetConfig());
diff --git a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
index b044b7f..0f42e093 100644
--- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
+++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.cc
@@ -8,6 +8,8 @@
 #include <utility>
 
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
+#include "base/containers/fixed_flat_set.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/speech/cros_speech_recognition_service.h"
@@ -56,14 +58,95 @@
       kAudioSampleRate, kAudioSampleRate / kPollingTimesPerSecond);
 }
 
+inline bool IsLanguageSupported(const speech::SodaInstaller* soda_installer,
+                                const speech::LanguageCode language_code) {
+  for (auto const& language : soda_installer->GetAvailableLanguages()) {
+    if (speech::GetLanguageCode(language) == language_code)
+      return true;
+  }
+  return false;
+}
+
+inline ash::SpeechRecognitionAvailability InstallationErrorToAvailability(
+    speech::SodaInstaller::ErrorCode error_code) {
+  switch (error_code) {
+    case speech::SodaInstaller::ErrorCode::kUnspecifiedError:
+      return ash::SpeechRecognitionAvailability::
+          kSodaInstallationErrorUnspecified;
+    case speech::SodaInstaller::ErrorCode::kNeedsReboot:
+      return ash::SpeechRecognitionAvailability::
+          kSodaInstallationErrorNeedsReboot;
+  }
+}
+
 }  // namespace
 
-bool SpeechRecognitionRecognizerClientImpl::IsOnDeviceSpeechRecognizerAvailable(
+ash::SpeechRecognitionAvailability
+SpeechRecognitionRecognizerClientImpl::GetOnDeviceSpeechRecognitionAvailability(
     const std::string& language) {
-  if (!base::FeatureList::IsEnabled(ash::features::kOnDeviceSpeechRecognition))
-    return false;
+  if (!base::FeatureList::IsEnabled(
+          ash::features::kOnDeviceSpeechRecognition)) {
+    return ash::SpeechRecognitionAvailability::kSodaNotAvailable;
+  }
+
+  const auto language_code = speech::GetLanguageCode(language);
   speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
-  return soda_installer->IsSodaInstalled(speech::GetLanguageCode(language));
+
+  if (soda_installer->IsSodaInstalled(language_code))
+    return ash::SpeechRecognitionAvailability::kSodaAvailable;
+
+  if (!IsLanguageSupported(soda_installer, language_code))
+    return ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable;
+
+  // Maybe SODA is currently installing.
+  if (soda_installer->IsSodaDownloading(language_code) ||
+      soda_installer->IsSodaDownloading(speech::LanguageCode::kNone)) {
+    return ash::SpeechRecognitionAvailability::kSodaInstalling;
+  }
+
+  // It is possible that there was some installation issues for SODA which we
+  // can surface to the user.
+  const auto binary_error_code =
+      soda_installer->GetSodaInstallErrorCode(speech::LanguageCode::kNone);
+  if (binary_error_code)
+    return InstallationErrorToAvailability(binary_error_code.value());
+
+  const auto language_error_code =
+      soda_installer->GetSodaInstallErrorCode(language_code);
+  if (language_error_code)
+    return InstallationErrorToAvailability(language_error_code.value());
+
+  return ash::SpeechRecognitionAvailability::kSodaNotInstalled;
+}
+
+ash::SpeechRecognitionAvailability
+SpeechRecognitionRecognizerClientImpl::GetServerBasedRecognitionAvailability(
+    const std::string& language) {
+  if (!ash::features::IsInternalServerSideSpeechRecognitionEnabled()) {
+    return ash::SpeechRecognitionAvailability::
+        kServerBasedRecognitionNotAvailable;
+  }
+
+  static constexpr auto kSupportedLocales =
+      base::MakeFixedFlatSet<base::StringPiece>(
+          {"ar-x-maghrebi", "cmn-hant-tw", "cs-cz", "da-dk", "de-de", "en-au",
+           "en-gb",         "en-in",       "en-us", "es-es", "es-us", "fi-fi",
+           "fr-fr",         "hi-in",       "id-id", "it-it", "ja-jp", "ko-kr",
+           "nl-nl",         "pt-br",       "ru-ru", "sv-se", "tr-tr", "vi-vn"});
+
+  // The locals that we get come from ui/base/l10n/l10n_util.cc and can
+  // therefore just be language names.
+  static constexpr auto kDefaultLanguages =
+      base::MakeFixedFlatSet<base::StringPiece>(
+          {"cs", "da", "de", "en", "es", "fi", "fr", "hi", "id", "it", "ja",
+           "ko", "nl", "pt", "ru", "sv", "tr", "vi"});
+
+  if (kSupportedLocales.contains(base::ToLowerASCII(language)) ||
+      kDefaultLanguages.contains(base::ToLowerASCII(language))) {
+    return ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable;
+  }
+
+  return ash::SpeechRecognitionAvailability::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 9fe9452..335e62e9 100644
--- a/chrome/browser/speech/speech_recognition_recognizer_client_impl.h
+++ b/chrome/browser/speech/speech_recognition_recognizer_client_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/speech/speech_recognizer.h"
 #include "chrome/browser/speech/speech_recognizer_delegate.h"
@@ -29,9 +30,15 @@
     : public SpeechRecognizer,
       public media::mojom::SpeechRecognitionRecognizerClient {
  public:
-  // Returns true if on-device speech recognition is available and installed
-  // on-device for the given language (BCP-47 format, e.g. "en-US").
-  static bool IsOnDeviceSpeechRecognizerAvailable(const std::string& language);
+  // Returns the availability of on-device speech recognition for the given
+  // language (BCP-47 format, e.g. "en-US").
+  static ash::SpeechRecognitionAvailability
+  GetOnDeviceSpeechRecognitionAvailability(const std::string& language);
+
+  // Returns the availability of server-based speech recognition for the given
+  // language.
+  static ash::SpeechRecognitionAvailability
+  GetServerBasedRecognitionAvailability(const std::string& language);
 
   SpeechRecognitionRecognizerClientImpl(
       const base::WeakPtr<SpeechRecognizerDelegate>& delegate,
diff --git a/chrome/browser/supervised_user/kids_chrome_management/kids_external_fetcher.h b/chrome/browser/supervised_user/kids_chrome_management/kids_external_fetcher.h
index c078901..ae58155 100644
--- a/chrome/browser/supervised_user/kids_chrome_management/kids_external_fetcher.h
+++ b/chrome/browser/supervised_user/kids_chrome_management/kids_external_fetcher.h
@@ -13,6 +13,30 @@
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
+// -----------------------------------------------------------------------------
+// Usage documentation
+// -----------------------------------------------------------------------------
+//
+// Overview: KidsExternalFetcher provides an interface for generic fetchers that
+// use classes to represent Request and Response objects. The default mechanism
+// under the hood takes care of the fetch process, including:
+// * obtaining the right access token,
+// * serializing the request and parsing the response,
+// * submitting metrics.
+//
+// If you want to create new fetcher factory method, then some implementation
+// details must be provided in order to enable fetching for said <Request,
+// Response> pair. The new fetcher factory should have at least the following
+// arguments: signin::IdentityManager, network::SharedURLLoaderFactory, url of
+// the endpoint and consuming callback provided.
+//
+// In the corresponding cc file, there should be:
+// * a traffic annotation tag for the request, assuming that one Request
+// represents one API endpoint, in the implementation cc file (example:
+// GetDefaultNetworkTrafficAnnotationTag),
+// * a request path method for the request (example: GetPathForRequest),
+// * a metrics key constructing method (example: CreateMetricKey).
+
 // Holds the status of the fetch. The callback's response will be set iff the
 // status is ok.
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a75f24f..8820aaa8 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -4473,12 +4473,17 @@
       "views/extensions/extension_uninstall_dialog_view.cc",
       "views/extensions/extensions_dialogs_utils.cc",
       "views/extensions/extensions_dialogs_utils.h",
+      "views/extensions/extensions_menu_base_view.cc",
+      "views/extensions/extensions_menu_base_view.h",
       "views/extensions/extensions_menu_button.cc",
       "views/extensions/extensions_menu_button.h",
       "views/extensions/extensions_menu_coordinator.cc",
       "views/extensions/extensions_menu_coordinator.h",
       "views/extensions/extensions_menu_item_view.cc",
       "views/extensions/extensions_menu_item_view.h",
+      "views/extensions/extensions_menu_main_page_view.cc",
+      "views/extensions/extensions_menu_main_page_view.h",
+      "views/extensions/extensions_menu_navigation_handler.h",
       "views/extensions/extensions_menu_view.cc",
       "views/extensions/extensions_menu_view.h",
       "views/extensions/extensions_request_access_button.cc",
diff --git a/chrome/browser/ui/android/logo/BUILD.gn b/chrome/browser/ui/android/logo/BUILD.gn
index a5b74cf..04b05869 100644
--- a/chrome/browser/ui/android/logo/BUILD.gn
+++ b/chrome/browser/ui/android/logo/BUILD.gn
@@ -6,9 +6,10 @@
 
 android_library("java") {
   sources = [
+    "java/src/org/chromium/chrome/browser/logo/CachedTintedBitmap.java",
     "java/src/org/chromium/chrome/browser/logo/LogoBridge.java",
     "java/src/org/chromium/chrome/browser/logo/LogoCoordinator.java",
-    "java/src/org/chromium/chrome/browser/logo/LogoDelegateImpl.java",
+    "java/src/org/chromium/chrome/browser/logo/LogoMediator.java",
     "java/src/org/chromium/chrome/browser/logo/LogoProperties.java",
     "java/src/org/chromium/chrome/browser/logo/LogoView.java",
     "java/src/org/chromium/chrome/browser/logo/LogoViewBinder.java",
@@ -19,8 +20,6 @@
     "//base:base_java",
     "//base:jni_java",
     "//build/android:build_java",
-    "//chrome/android/features/start_surface:public_java",
-    "//chrome/browser/flags:java",
     "//chrome/browser/preferences:java",
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/search_engines/android:java",
@@ -60,25 +59,30 @@
 
 robolectric_library("junit") {
   sources = [
-    "java/src/org/chromium/chrome/browser/logo/LogoCoordinatorUnitTest.java",
+    "java/src/org/chromium/chrome/browser/logo/CachedTintedBitmapUnitTest.java",
+    "java/src/org/chromium/chrome/browser/logo/LogoMediatorUnitTest.java",
     "java/src/org/chromium/chrome/browser/logo/LogoViewBinderUnitTest.java",
     "java/src/org/chromium/chrome/browser/logo/LogoViewTest.java",
   ]
 
   deps = [
     ":java",
+    "//base:base_java",
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
     "//chrome/android:chrome_java",
     "//chrome/browser/flags:java",
+    "//chrome/browser/preferences:java",
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/search_engines/android:java",
+    "//components/image_fetcher:java",
     "//components/search_engines/android:java",
     "//content/public/android:content_full_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_ext_junit_java",
     "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/gif_player:gif_player_java",
     "//third_party/junit:junit",
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_java_test_support",
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmap.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmap.java
new file mode 100644
index 0000000..9b13f3c
--- /dev/null
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmap.java
@@ -0,0 +1,79 @@
+// 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.
+
+package org.chromium.chrome.browser.logo;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.DrawableRes;
+
+import java.lang.ref.WeakReference;
+
+/** Tinted {@link Bitmap} get updated based on application environment.*/
+public class CachedTintedBitmap {
+    private final @DrawableRes int mDrawableId;
+    private final @ColorRes int mColorId;
+    private WeakReference<Bitmap> mPreviousBitmap;
+    private @ColorInt int mPreviousTint;
+
+    /**
+     * Creates a {@link CachedTintedBitmap} object.
+     *
+     * @param drawableId  The drawable resource reference for the {@link CachedTintedBitmap}.
+     * @param colorId The color resource reference for the {@link CachedTintedBitmap}.
+     */
+    public CachedTintedBitmap(int drawableId, int colorId) {
+        mDrawableId = drawableId;
+        mColorId = colorId;
+    }
+
+    /**
+     * Update the information on the tinted {@link Bitmap} and return the bitmap itself.
+     * @param context Used to load colors and resources.
+     * @return The updated bitmap.
+     */
+    public Bitmap getBitmap(Context context) {
+        Bitmap newBitmap = mPreviousBitmap == null ? null : mPreviousBitmap.get();
+        final @ColorInt int tint = context.getColor(mColorId);
+        if (newBitmap == null || mPreviousTint != tint) {
+            final Resources resources = context.getResources();
+            if (tint == Color.TRANSPARENT) {
+                newBitmap = BitmapFactory.decodeResource(resources, mDrawableId);
+            } else {
+                // Apply color filter on a bitmap, which will cause some performance overhead, but
+                // it is worth the APK space savings by avoiding adding another large asset for the
+                // Bitmap in night mode. Not using vector drawable here because it is close to the
+                // maximum recommended vector drawable size 200dpx200dp.
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inMutable = true;
+                newBitmap = BitmapFactory.decodeResource(resources, mDrawableId, options);
+                Paint paint = new Paint();
+                paint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP));
+                Canvas canvas = new Canvas(newBitmap);
+                canvas.drawBitmap(newBitmap, 0, 0, paint);
+            }
+            mPreviousBitmap = new WeakReference<>(newBitmap);
+            mPreviousTint = tint;
+        }
+        return newBitmap;
+    }
+
+    WeakReference<Bitmap> getPreviousBitmapForTesting() {
+        return mPreviousBitmap;
+    }
+
+    int getPreviousTintForTesting() {
+        return mPreviousTint;
+    }
+}
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmapUnitTest.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmapUnitTest.java
new file mode 100644
index 0000000..a92885fe
--- /dev/null
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/CachedTintedBitmapUnitTest.java
@@ -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.
+
+package org.chromium.chrome.browser.logo;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.DrawableRes;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for the {@link CachedTintedBitmap}.*/
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CachedTintedBitmapUnitTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void testGetDefaultGoogleLogoAndGoogleIsDse() {
+        @DrawableRes
+        int drawableId = R.drawable.google_logo;
+        @ColorRes
+        int colorId = R.color.google_logo_tint_color;
+        Assert.assertEquals(Color.TRANSPARENT, mContext.getColor(colorId));
+        // Build verifyLogo for later comparison.
+        Bitmap verifyLogo = BitmapFactory.decodeResource(mContext.getResources(), drawableId);
+
+        CachedTintedBitmap cachedTintedBitmap = new CachedTintedBitmap(drawableId, colorId);
+        Assert.assertTrue(verifyLogo.sameAs(cachedTintedBitmap.getBitmap(mContext)));
+        Assert.assertTrue(
+                verifyLogo.sameAs(cachedTintedBitmap.getPreviousBitmapForTesting().get()));
+        Assert.assertEquals(Color.TRANSPARENT, cachedTintedBitmap.getPreviousTintForTesting());
+
+        // When the bitmap is already up-to-date, nothing will change.
+        Assert.assertTrue(verifyLogo.sameAs(cachedTintedBitmap.getBitmap(mContext)));
+        Assert.assertTrue(
+                verifyLogo.sameAs(cachedTintedBitmap.getPreviousBitmapForTesting().get()));
+        Assert.assertEquals(Color.TRANSPARENT, cachedTintedBitmap.getPreviousTintForTesting());
+    }
+}
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinator.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinator.java
index 083035cb..034a9aeb 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinator.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinator.java
@@ -4,58 +4,24 @@
 
 package org.chromium.chrome.browser.logo;
 
-import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO;
-
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-
-import androidx.annotation.ColorInt;
+import android.view.View.MeasureSpec;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ObserverList;
 import org.chromium.chrome.browser.logo.LogoBridge.Logo;
-import org.chromium.chrome.browser.logo.LogoBridge.LogoObserver;
-import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
-import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
-import java.lang.ref.WeakReference;
-
 /** Coordinator used to fetch and load logo image for Start surface and NTP.*/
-public class LogoCoordinator implements TemplateUrlServiceObserver {
-    private final Callback<LoadUrlParams> mLogoClickedCallback;
+public class LogoCoordinator {
+    private final LogoMediator mMediator;
     private final PropertyModel mLogoModel;
-    private final LogoView mLogoView;
-    private final Callback<Logo> mOnLogoAvailableRunnable;
-    private final Runnable mOnCachedLogoRevalidatedRunnable;
-    private final Context mContext;
+    private LogoView mLogoView;
 
-    private LogoDelegateImpl mLogoDelegate;
-    private Profile mProfile;
-    private boolean mHasLogoLoadedForCurrentSearchEngine;
-    private boolean mShouldFetchDoodle;
-    private boolean mIsParentSurfaceShown; // This value should always be true when this class
-                                           // is used by NTP.
-    private boolean mShouldShowLogo;
-    private boolean mIsNativeInitialized;
-    private boolean mIsLoadPending;
-
-    // The default logo is shared across all NTPs.
-    private static WeakReference<Bitmap> sDefaultLogo;
-    private static @ColorInt int sDefaultLogoTint;
-
-    private final ObserverList<VisibilityObserver> mVisibilityObservers = new ObserverList<>();
+    // The default google logo that is shared across all NTPs.
+    static final CachedTintedBitmap sDefaultGoogleLogo =
+            new CachedTintedBitmap(R.drawable.google_logo, R.color.google_logo_tint_color);
 
     /** Interface for the observers of the logo visibility change. */
     public interface VisibilityObserver {
@@ -74,106 +40,56 @@
      * @param isParentSurfaceShown Whether Start surface homepage or NTP is shown. This value
      *                             is true when this class is used by NTP; while used by Start,
      *                             it's only true on Start homepage.
+     * @param visibilityObserver Observer object monitoring logo visibility.
      */
     public LogoCoordinator(Context context, Callback<LoadUrlParams> logoClickedCallback,
             LogoView logoView, boolean shouldFetchDoodle, Callback<Logo> onLogoAvailableCallback,
-            Runnable onCachedLogoRevalidatedRunnable, boolean isParentSurfaceShown) {
-        mLogoClickedCallback = logoClickedCallback;
+            Runnable onCachedLogoRevalidatedRunnable, boolean isParentSurfaceShown,
+            VisibilityObserver visibilityObserver) {
+        // TODO(crbug.com/1394983): This is weird that we're passing in our view,
+        //  and we have to expose our view via getView. We shouldn't only have to do one of these.
         mLogoModel = new PropertyModel(LogoProperties.ALL_KEYS);
         mLogoView = logoView;
-        mShouldFetchDoodle = shouldFetchDoodle;
-        mOnLogoAvailableRunnable = onLogoAvailableCallback;
-        mOnCachedLogoRevalidatedRunnable = onCachedLogoRevalidatedRunnable;
-        mIsParentSurfaceShown = isParentSurfaceShown;
-        mContext = context;
         PropertyModelChangeProcessor.create(mLogoModel, mLogoView, new LogoViewBinder());
+        mMediator = new LogoMediator(context, logoClickedCallback, mLogoModel, shouldFetchDoodle,
+                onLogoAvailableCallback, onCachedLogoRevalidatedRunnable, isParentSurfaceShown,
+                visibilityObserver, sDefaultGoogleLogo);
     }
 
-    /**
-     * Initialize the coordinator with the components that had native initialization dependencies,
-     * i.e. Profile..
-     */
+    /** @see LogoMediator#initWithNative */
     public void initWithNative() {
-        if (mIsNativeInitialized) return;
-
-        mIsNativeInitialized = true;
-        mProfile = Profile.getLastUsedRegularProfile();
-        updateVisibility();
-
-        if (mShouldShowLogo) {
-            showSearchProviderInitialView();
-            if (mIsLoadPending) loadSearchProviderLogo(/*animationEnabled=*/false);
-        }
-
-        TemplateUrlServiceFactory.get().addObserver(this);
+        // TODO(crbug.com/1394983): Would be more elegant if we were given an
+        //  onNativeInitializedObserver and didn't rely on the good will of outside callers to
+        //  invoke this.
+        mMediator.initWithNative();
     }
 
-    /** Update the logo based on default search engine changes.*/
-    @Override
-    public void onTemplateURLServiceChanged() {
-        loadSearchProviderLogoWithAnimation();
-    }
-
-    /** Force to load the search provider logo with animation enabled.*/
+    /** @see LogoMediator#loadSearchProviderLogoWithAnimation */
     public void loadSearchProviderLogoWithAnimation() {
-        mHasLogoLoadedForCurrentSearchEngine = false;
-        maybeLoadSearchProviderLogo(mIsParentSurfaceShown, /*shouldDestroyDelegate=*/false, true);
+        mMediator.loadSearchProviderLogoWithAnimation();
+    }
+
+    /** @see LogoMediator#updateVisibilityAndMaybeCleanUp */
+    public void updateVisibilityAndMaybeCleanUp(
+            boolean isParentSurfaceShown, boolean shouldDestroyBridge, boolean animationEnabled) {
+        mMediator.updateVisibilityAndMaybeCleanUp(
+                isParentSurfaceShown, shouldDestroyBridge, animationEnabled);
+    }
+
+    /** @see LogoMediator#destroy */
+    public void destroy() {
+        mMediator.destroy();
+        mLogoView.destroy();
+        mLogoView = null;
     }
 
     /**
-     * If it's on Start surface homepage or on NTP, load search provider logo; If it's not on Start
-     * surface homepage, destroy mLogoDelegate.
-     *
-     * @param isParentSurfaceShown Whether Start surface homepage or NTP is shown. This value
-     *                             should always be true when this class is used by NTP.
-     * @param shouldDestroyDelegate Whether to destroy delegate for saving memory. This value should
-     *                              always be false when this class is used by NTP.
-     *                              TODO(crbug.com/1315676): Remove this variable once the refactor
-     *                              is launched and StartSurfaceState is removed. Now we check this
-     *                              because there are some intermediate StartSurfaceStates,
-     *                              i.e. SHOWING_START.
-     * @param animationEnabled Whether to enable the fade in animation.
+     * Convenience method to call measure() on the logo view with MeasureSpecs converted from the
+     * given dimensions (in pixels) with MeasureSpec.EXACTLY.
      */
-    public void maybeLoadSearchProviderLogo(
-            boolean isParentSurfaceShown, boolean shouldDestroyDelegate, boolean animationEnabled) {
-        assert !isParentSurfaceShown || !shouldDestroyDelegate;
-
-        mIsParentSurfaceShown = isParentSurfaceShown;
-        updateVisibility();
-
-        if (mShouldShowLogo) {
-            if (mProfile != null) {
-                loadSearchProviderLogo(animationEnabled);
-            } else {
-                mIsLoadPending = true;
-            }
-        } else if (shouldDestroyDelegate && mLogoDelegate != null) {
-            mHasLogoLoadedForCurrentSearchEngine = false;
-            // Destroy |mLogoDelegate| when hiding Start surface homepage to save memory.
-            mLogoDelegate.destroy();
-            mLogoDelegate = null;
-        }
-    }
-
-    /** Cleans up any code as necessary.*/
-    public void destroy() {
-        if (mLogoDelegate != null) {
-            mLogoDelegate.destroy();
-            mLogoDelegate = null;
-        }
-
-        if (mLogoView != null) {
-            mLogoModel.set(LogoProperties.DESTROY, true);
-        }
-
-        if (mIsNativeInitialized) {
-            TemplateUrlServiceFactory.get().removeObserver(this);
-        }
-    }
-
-    /** Returns the logo view.*/
-    public LogoView getView() {
-        return mLogoView;
+    public void measureExactlyLogoView(int widthPx) {
+        mLogoView.measure(MeasureSpec.makeMeasureSpec(widthPx, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mLogoView.getMeasuredHeight(), MeasureSpec.EXACTLY));
     }
 
     /** Jumps to the end of the logo view's cross-fading animation, if any.*/
@@ -208,152 +124,13 @@
         mLogoModel.set(LogoProperties.LOGO_BOTTOM_MARGIN, bottomMargin);
     }
 
-    /** Returns whether LogoView is visible.*/
+    /** @see LogoMediator#isLogoVisible */
     public boolean isLogoVisible() {
-        return mShouldShowLogo && mLogoModel.get(LogoProperties.VISIBILITY);
+        return mMediator.isLogoVisible();
     }
 
-    /**
-     * Add {@link Observer} object.
-     * @param observer Observer object monitoring logo visibility.
-     */
-    public void addObserver(VisibilityObserver observer) {
-        mVisibilityObservers.addObserver(observer);
-    }
-
-    /**
-     * Remove {@link Observer} object.
-     * @param observer Observer object monitoring logo visibility.
-     */
-    public void removeObserver(VisibilityObserver observer) {
-        mVisibilityObservers.removeObserver(observer);
-    }
-
-    /**
-     * Load the search provider logo on Start surface.
-     *
-     * @param animationEnabled Whether to enable the fade in animation.
-     */
-    private void loadSearchProviderLogo(boolean animationEnabled) {
-        // If logo is already updated for the current search provider, or profile is null or off the
-        // record, don't bother loading the logo image.
-        if (mHasLogoLoadedForCurrentSearchEngine || mProfile == null || !mShouldShowLogo) return;
-
-        mHasLogoLoadedForCurrentSearchEngine = true;
-        mLogoModel.set(LogoProperties.ANIMATION_ENABLED, animationEnabled);
-        showSearchProviderInitialView();
-
-        // If default search engine is google and doodle is not supported, doesn't bother to fetch
-        // logo image.
-        if (TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle() && !mShouldFetchDoodle) {
-            return;
-        }
-
-        if (mLogoDelegate == null) {
-            mLogoDelegate = new LogoDelegateImpl(mLogoClickedCallback, mLogoView, mProfile);
-        }
-
-        mLogoDelegate.getSearchProviderLogo(new LogoObserver() {
-            @Override
-            public void onLogoAvailable(Logo logo, boolean fromCache) {
-                if (logo == null) {
-                    if (fromCache) {
-                        // There is no cached logo. Wait until we know whether there's a fresh
-                        // one before making any further decisions.
-                        return;
-                    }
-                    mLogoModel.set(
-                            LogoProperties.DEFAULT_GOOGLE_LOGO, getDefaultGoogleLogo(mContext));
-                }
-                mLogoModel.set(LogoProperties.LOGO_DELEGATE, mLogoDelegate);
-                mLogoModel.set(LogoProperties.UPDATED_LOGO, logo);
-
-                if (mOnLogoAvailableRunnable != null) mOnLogoAvailableRunnable.onResult(logo);
-            }
-
-            @Override
-            public void onCachedLogoRevalidated() {
-                if (mOnCachedLogoRevalidatedRunnable != null) {
-                    mOnCachedLogoRevalidatedRunnable.run();
-                }
-            }
-        });
-    }
-
-    private void showSearchProviderInitialView() {
-        mLogoModel.set(LogoProperties.DEFAULT_GOOGLE_LOGO, getDefaultGoogleLogo(mContext));
-        mLogoModel.set(LogoProperties.SHOW_SEARCH_PROVIDER_INITIAL_VIEW, true);
-    }
-
-    private void updateVisibility() {
-        boolean doesDSEHaveLogo = mIsNativeInitialized
-                ? TemplateUrlServiceFactory.get().doesDefaultSearchEngineHaveLogo()
-                : SharedPreferencesManager.getInstance().readBoolean(
-                        APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, true);
-        mShouldShowLogo = mIsParentSurfaceShown && doesDSEHaveLogo;
-        mLogoModel.set(LogoProperties.VISIBILITY, mShouldShowLogo);
-        for (VisibilityObserver observer : mVisibilityObservers) {
-            observer.onLogoVisibilityChanged();
-        }
-    }
-
-    /**
-     * Get the default Google logo if available.
-     * @param context Used to load colors and resources.
-     * @return The default Google logo.
-     */
-    public static Bitmap getDefaultGoogleLogo(Context context) {
-        if (!TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()) return null;
-
-        Bitmap defaultLogo = sDefaultLogo == null ? null : sDefaultLogo.get();
-        final int tint = context.getColor(R.color.google_logo_tint_color);
-        if (defaultLogo == null || sDefaultLogoTint != tint) {
-            final Resources resources = context.getResources();
-            if (tint == Color.TRANSPARENT) {
-                defaultLogo = BitmapFactory.decodeResource(resources, R.drawable.google_logo);
-            } else {
-                // Apply color filter on a bitmap, which will cause some performance overhead, but
-                // it is worth the APK space savings by avoiding adding another large asset for the
-                // logo in night mode. Not using vector drawable here because it is close to the
-                // maximum recommended vector drawable size 200dpx200dp.
-                BitmapFactory.Options options = new BitmapFactory.Options();
-                options.inMutable = true;
-                defaultLogo =
-                        BitmapFactory.decodeResource(resources, R.drawable.google_logo, options);
-                Paint paint = new Paint();
-                paint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP));
-                Canvas canvas = new Canvas(defaultLogo);
-                canvas.drawBitmap(defaultLogo, 0, 0, paint);
-            }
-            sDefaultLogo = new WeakReference<>(defaultLogo);
-            sDefaultLogoTint = tint;
-        }
-        return defaultLogo;
-    }
-
-    /** Returns the logo model.*/
-    public PropertyModel getModelForTesting() {
-        return mLogoModel;
-    }
-
-    void setShouldFetchDoodleForTesting(boolean shouldFetchDoodle) {
-        mShouldFetchDoodle = shouldFetchDoodle;
-    }
-
-    void setLogoDelegateForTesting(LogoDelegateImpl logoDelegate) {
-        mLogoDelegate = logoDelegate;
-    }
-
-    void setHasLogoLoadedForCurrentSearchEngineForTesting(
-            boolean hasLogoLoadedForCurrentSearchEngine) {
-        mHasLogoLoadedForCurrentSearchEngine = hasLogoLoadedForCurrentSearchEngine;
-    }
-
-    boolean getIsLoadPendingForTesting() {
-        return mIsLoadPending;
-    }
-
-    LogoDelegateImpl getLogoDelegateForTesting() {
-        return mLogoDelegate;
+    /** @see LogoMediator#onTemplateURLServiceChanged */
+    public void onTemplateURLServiceChangedForTesting() {
+        mMediator.onTemplateURLServiceChanged();
     }
 }
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinatorUnitTest.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinatorUnitTest.java
deleted file mode 100644
index 70a4b79..0000000
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoCoordinatorUnitTest.java
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.logo;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.Callback;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.JniMocker;
-import org.chromium.chrome.browser.flags.CachedFeatureFlags;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.homepage.HomepageManager;
-import org.chromium.chrome.browser.logo.LogoBridge.Logo;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactoryJni;
-import org.chromium.components.search_engines.TemplateUrlService;
-import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-
-/**
- * Unit tests for the {@link LogoCoordinator}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class LogoCoordinatorUnitTest {
-    @Rule
-    public JniMocker mJniMocker = new JniMocker();
-
-    @Mock
-    private Profile mProfile;
-
-    @Mock
-    LogoBridge.Natives mLogoBridge;
-
-    @Mock
-    TemplateUrlServiceFactory.Natives mTemplateUrlServiceFactory;
-
-    @Mock
-    TemplateUrlService mTemplateUrlService;
-
-    @Mock
-    LogoDelegateImpl mLogoDelegate;
-
-    @Mock
-    LogoView mLogoView;
-
-    @Mock
-    Callback<LoadUrlParams> mLogoClickedCallback;
-
-    @Mock
-    Callback<Logo> mOnLogoAvailableCallback;
-
-    @Mock
-    Runnable mOnCachedLogoRevalidatedRunnable;
-
-    private Context mContext;
-    private LogoCoordinator mLogoCoordinator;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        Profile.setLastUsedProfileForTesting(mProfile);
-
-        mContext = ApplicationProvider.getApplicationContext();
-
-        mJniMocker.mock(TemplateUrlServiceFactoryJni.TEST_HOOKS, mTemplateUrlServiceFactory);
-        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
-        doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-        doReturn(true).when(mTemplateUrlService).doesDefaultSearchEngineHaveLogo();
-
-        mJniMocker.mock(LogoBridgeJni.TEST_HOOKS, mLogoBridge);
-
-        ChromeFeatureList.sStartSurfaceAndroid.setForTesting(true);
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> HomepageManager.getInstance().setPrefHomepageEnabled(true));
-    }
-
-    @After
-    public void tearDown() {
-        CachedFeatureFlags.resetFlagsForTesting();
-    }
-
-    @Test
-    public void testDSEChangedAndGoogleIsDSE() {
-        createCoordinator();
-        doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-        mLogoCoordinator.setShouldFetchDoodleForTesting(true);
-
-        mLogoCoordinator.onTemplateURLServiceChanged();
-
-        Assert.assertNotNull(LogoCoordinator.getDefaultGoogleLogo(mContext));
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-
-        // If doodle isn't supported, getSearchProviderLogo() shouldn't be called by
-        // onTemplateURLServiceChanged().
-        mLogoCoordinator.setShouldFetchDoodleForTesting(false);
-
-        mLogoCoordinator.onTemplateURLServiceChanged();
-
-        Assert.assertNotNull(LogoCoordinator.getDefaultGoogleLogo(mContext));
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-    }
-
-    @Test
-    public void testDSEChangedAndGoogleIsNotDSE() {
-        createCoordinator();
-        doReturn(false).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-
-        mLogoCoordinator.onTemplateURLServiceChanged();
-
-        Assert.assertNull(LogoCoordinator.getDefaultGoogleLogo(mContext));
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-    }
-
-    @Test
-    public void testDSEChangedAndDoesNotHaveLogo() {
-        createCoordinator();
-        when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(false);
-
-        mLogoCoordinator.onTemplateURLServiceChanged();
-
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-    }
-
-    @Test
-    public void testLoadLogoWhenLogoNotLoaded() {
-        createCoordinator();
-        mLogoCoordinator.setHasLogoLoadedForCurrentSearchEngineForTesting(false);
-        doReturn(false).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ true, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-    }
-
-    @Test
-    public void testLoadLogoWhenLogoHasLoaded() {
-        createCoordinator();
-        mLogoCoordinator.setHasLogoLoadedForCurrentSearchEngineForTesting(true);
-        doReturn(false).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ true, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-    }
-
-    @Test
-    public void testInitWithNativeWhenParentSurfaceIsNotVisible() {
-        createCoordinatorWithoutNative(/*isParentSurfaceShown=*/false);
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ false, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-        Assert.assertFalse(mLogoCoordinator.getModelForTesting().get(LogoProperties.VISIBILITY));
-        // When parent surface isn't showing, calling maybeLoadSearchProviderLogo() shouldn't
-        // trigger getSearchProviderLogo() nor add any pending load task.
-        Assert.assertFalse(mLogoCoordinator.getIsLoadPendingForTesting());
-        mLogoCoordinator.initWithNative();
-
-        Assert.assertFalse(mLogoCoordinator.getModelForTesting().get(LogoProperties.VISIBILITY));
-        Assert.assertFalse(mLogoCoordinator.isLogoVisible());
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-        verify(mTemplateUrlService).addObserver(mLogoCoordinator);
-    }
-
-    @Test
-    public void testInitWithNativeWhenParentSurfaceIsVisible() {
-        createCoordinatorWithoutNative(true);
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ true, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-
-        Assert.assertTrue(mLogoCoordinator.isLogoVisible());
-        // When parent surface is shown while native library isn't loaded, calling
-        // maybeLoadSearchProviderLogo() will add a pending load task.
-        Assert.assertTrue(mLogoCoordinator.getIsLoadPendingForTesting());
-        mLogoCoordinator.initWithNative();
-
-        Assert.assertTrue(mLogoCoordinator.isLogoVisible());
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-        verify(mTemplateUrlService).addObserver(mLogoCoordinator);
-    }
-
-    @Test
-    public void testMaybeLoadSearchProviderLogo() {
-        createCoordinator();
-
-        // If parent surface is not shown nor delegate shouldn't be destroyed, logo shouldn't be
-        // loaded and delegate isn't destroyed.
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ false, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-        Assert.assertFalse(mLogoCoordinator.isLogoVisible());
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-        verify(mLogoDelegate, times(0)).destroy();
-
-        // If parent surface is not shown and delegate should be destroyed, logo should be
-        // loaded and delegate is destroyed.
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ false, /*shouldDestroyDelegate*/ true,
-                /*animationEnabled*/ false);
-        Assert.assertFalse(mLogoCoordinator.isLogoVisible());
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-        verify(mLogoDelegate, times(1)).destroy();
-        Assert.assertNull(mLogoCoordinator.getLogoDelegateForTesting());
-
-        // If parent surface is shown, logo should be loaded and delegate shouldn't be
-        // destroyed.
-        mLogoCoordinator.setLogoDelegateForTesting(mLogoDelegate);
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ true, /*shouldDestroyDelegate*/ false,
-                /*animationEnabled*/ false);
-        Assert.assertTrue(mLogoCoordinator.isLogoVisible());
-        verify(mLogoDelegate, times(1)).getSearchProviderLogo(any());
-        verify(mLogoDelegate, times(1)).destroy();
-    }
-
-    @Test(expected = AssertionError.class)
-    public void testMaybeLoadSearchProviderLogoAssertionError() {
-        createCoordinator();
-        verify(mLogoDelegate, times(0)).getSearchProviderLogo(any());
-
-        // If parent surface is shown and delegate should be destroyed, an assertion error
-        // should be thrown.
-        mLogoCoordinator.maybeLoadSearchProviderLogo(
-                /*isParentSurfaceShown*/ true, /*shouldDestroyDelegate*/ true,
-                /*animationEnabled*/ false); // should throw an exception
-    }
-
-    private void createCoordinator() {
-        createCoordinatorWithoutNative(/*isParentSurfaceShown=*/true);
-        mLogoCoordinator.initWithNative();
-    }
-
-    private void createCoordinatorWithoutNative(boolean isParentSurfaceShown) {
-        mLogoCoordinator = new LogoCoordinator(mContext, mLogoClickedCallback, mLogoView,
-                /*shouldFetchDoodle=*/true, mOnLogoAvailableCallback,
-                mOnCachedLogoRevalidatedRunnable, isParentSurfaceShown);
-        mLogoCoordinator.setLogoDelegateForTesting(mLogoDelegate);
-    }
-}
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoDelegateImpl.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoDelegateImpl.java
deleted file mode 100644
index dd8664f..0000000
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoDelegateImpl.java
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.logo;
-
-import androidx.annotation.Nullable;
-
-import org.chromium.base.Callback;
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.chrome.browser.logo.LogoBridge.Logo;
-import org.chromium.chrome.browser.logo.LogoBridge.LogoObserver;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.components.image_fetcher.ImageFetcher;
-import org.chromium.components.image_fetcher.ImageFetcherConfig;
-import org.chromium.components.image_fetcher.ImageFetcherFactory;
-import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.ui.base.PageTransition;
-
-import jp.tomorrowkey.android.gifplayer.BaseGifImage;
-
-/**
- * An implementation of {@link LogoView.Delegate}.
- */
-public class LogoDelegateImpl implements LogoView.Delegate {
-    // UMA enum constants. CTA means the "click-to-action" icon.
-    private static final String LOGO_SHOWN_UMA_NAME = "NewTabPage.LogoShown";
-    private static final String LOGO_SHOWN_FROM_CACHE_UMA_NAME = "NewTabPage.LogoShown.FromCache";
-    private static final String LOGO_SHOWN_FRESH_UMA_NAME = "NewTabPage.LogoShown.Fresh";
-    private static final int STATIC_LOGO_SHOWN = 0;
-    private static final int CTA_IMAGE_SHOWN = 1;
-    private static final int LOGO_SHOWN_COUNT = 2;
-
-    private static final String LOGO_SHOWN_TIME_UMA_NAME = "NewTabPage.LogoShownTime2";
-
-    private static final String LOGO_CLICK_UMA_NAME = "NewTabPage.LogoClick";
-    private static final int STATIC_LOGO_CLICKED = 0;
-    private static final int CTA_IMAGE_CLICKED = 1;
-    private static final int ANIMATED_LOGO_CLICKED = 2;
-
-    private final Callback<LoadUrlParams> mLogoClickedCallback;
-    private final LogoView mLogoView;
-    private final LogoBridge mLogoBridge;
-
-    private ImageFetcher mImageFetcher;
-    private String mOnLogoClickUrl;
-    private String mAnimatedLogoUrl;
-
-    private boolean mShouldRecordLoadTime = true;
-    private boolean mIsDestroyed;
-
-    /**
-     * Construct a new {@link LogoDelegateImpl}.
-     * @param logoClickedCallback A callback for loading the URL when the logo is clicked. May be
-     *         null when click events are not supported.
-     * @param logoView The view that shows the search provider logo. Maybe null when the client is
-     *         controlling the View presentation itself.
-     * @param profile The profile to show the logo for.
-     */
-    public LogoDelegateImpl(Callback<LoadUrlParams> logoClickedCallback,
-            @Nullable LogoView logoView, Profile profile) {
-        mLogoView = logoView;
-        mLogoBridge = new LogoBridge(profile);
-        mImageFetcher = ImageFetcherFactory.createImageFetcher(
-                ImageFetcherConfig.DISK_CACHE_ONLY, profile.getProfileKey());
-        mLogoClickedCallback = logoClickedCallback;
-    }
-
-    public void destroy() {
-        mIsDestroyed = true;
-        mLogoBridge.destroy();
-        mImageFetcher.destroy();
-        mImageFetcher = null;
-    }
-
-    @Override
-    public void onLogoClicked(boolean isAnimatedLogoShowing) {
-        if (mIsDestroyed) return;
-
-        if (!isAnimatedLogoShowing && mAnimatedLogoUrl != null) {
-            RecordHistogram.recordSparseHistogram(LOGO_CLICK_UMA_NAME, CTA_IMAGE_CLICKED);
-            mLogoView.showLoadingView();
-            mImageFetcher.fetchGif(ImageFetcher.Params.create(mAnimatedLogoUrl,
-                                           ImageFetcher.NTP_ANIMATED_LOGO_UMA_CLIENT_NAME),
-                    (BaseGifImage animatedLogoImage) -> {
-                        if (mIsDestroyed || animatedLogoImage == null) return;
-                        mLogoView.playAnimatedLogo(animatedLogoImage);
-                    });
-        } else if (mOnLogoClickUrl != null) {
-            RecordHistogram.recordSparseHistogram(LOGO_CLICK_UMA_NAME,
-                    isAnimatedLogoShowing ? ANIMATED_LOGO_CLICKED : STATIC_LOGO_CLICKED);
-            mLogoClickedCallback.onResult(new LoadUrlParams(mOnLogoClickUrl, PageTransition.LINK));
-        }
-    }
-
-    public void getSearchProviderLogo(final LogoObserver logoObserver) {
-        assert !mIsDestroyed;
-
-        final long loadTimeStart = System.currentTimeMillis();
-
-        LogoObserver wrapperCallback = new LogoObserver() {
-            @Override
-            public void onLogoAvailable(Logo logo, boolean fromCache) {
-                if (mIsDestroyed) return;
-
-                if (logo != null) {
-                    int logoType =
-                            logo.animatedLogoUrl == null ? STATIC_LOGO_SHOWN : CTA_IMAGE_SHOWN;
-                    RecordHistogram.recordEnumeratedHistogram(
-                            LOGO_SHOWN_UMA_NAME, logoType, LOGO_SHOWN_COUNT);
-                    if (fromCache) {
-                        RecordHistogram.recordEnumeratedHistogram(
-                                LOGO_SHOWN_FROM_CACHE_UMA_NAME, logoType, LOGO_SHOWN_COUNT);
-                    } else {
-                        RecordHistogram.recordEnumeratedHistogram(
-                                LOGO_SHOWN_FRESH_UMA_NAME, logoType, LOGO_SHOWN_COUNT);
-                    }
-                    if (mShouldRecordLoadTime) {
-                        long loadTime = System.currentTimeMillis() - loadTimeStart;
-                        RecordHistogram.recordMediumTimesHistogram(
-                                LOGO_SHOWN_TIME_UMA_NAME, loadTime);
-                        // Only record the load time once per NTP, for the first logo we got,
-                        // whether that came from cache or not.
-                        mShouldRecordLoadTime = false;
-                    }
-                } else if (!fromCache) {
-                    // If we got a fresh (i.e. not from cache) null logo, don't record any load
-                    // time even if we get another update later.
-                    mShouldRecordLoadTime = false;
-                }
-
-                mOnLogoClickUrl = logo != null ? logo.onClickUrl : null;
-                mAnimatedLogoUrl =
-                        (logo != null && mLogoView != null) ? logo.animatedLogoUrl : null;
-
-                logoObserver.onLogoAvailable(logo, fromCache);
-            }
-
-            @Override
-            public void onCachedLogoRevalidated() {
-                logoObserver.onCachedLogoRevalidated();
-            }
-        };
-
-        mLogoBridge.getCurrentLogo(wrapperCallback);
-    }
-}
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediator.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediator.java
new file mode 100644
index 0000000..fae19df
--- /dev/null
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediator.java
@@ -0,0 +1,394 @@
+// 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.
+
+package org.chromium.chrome.browser.logo;
+
+import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ObserverList;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.logo.LogoBridge.Logo;
+import org.chromium.chrome.browser.logo.LogoBridge.LogoObserver;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
+import org.chromium.components.image_fetcher.ImageFetcher;
+import org.chromium.components.image_fetcher.ImageFetcherConfig;
+import org.chromium.components.image_fetcher.ImageFetcherFactory;
+import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import jp.tomorrowkey.android.gifplayer.BaseGifImage;
+
+/** Mediator used to fetch and load logo image for Start surface and NTP.*/
+public class LogoMediator implements TemplateUrlServiceObserver {
+    // UMA enum constants. CTA means the "click-to-action" icon.
+    private static final String LOGO_SHOWN_UMA_NAME = "NewTabPage.LogoShown";
+    private static final String LOGO_SHOWN_FROM_CACHE_UMA_NAME = "NewTabPage.LogoShown.FromCache";
+    private static final String LOGO_SHOWN_FRESH_UMA_NAME = "NewTabPage.LogoShown.Fresh";
+    @IntDef({LogoShownId.STATIC_LOGO_SHOWN, LogoShownId.CTA_IMAGE_SHOWN,
+            LogoShownId.LOGO_SHOWN_COUNT})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LogoShownId {
+        int STATIC_LOGO_SHOWN = 0;
+        int CTA_IMAGE_SHOWN = 1;
+        int LOGO_SHOWN_COUNT = 2;
+    }
+
+    private static final String LOGO_SHOWN_TIME_UMA_NAME = "NewTabPage.LogoShownTime2";
+
+    private static final String LOGO_CLICK_UMA_NAME = "NewTabPage.LogoClick";
+    @IntDef({LogoClickId.STATIC_LOGO_CLICKED, LogoClickId.CTA_IMAGE_CLICKED,
+            LogoClickId.ANIMATED_LOGO_CLICKED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LogoClickId {
+        int STATIC_LOGO_CLICKED = 0;
+        int CTA_IMAGE_CLICKED = 1;
+        int ANIMATED_LOGO_CLICKED = 2;
+    }
+
+    private final PropertyModel mLogoModel;
+    private final Context mContext;
+    private Profile mProfile;
+    private LogoBridge mLogoBridge;
+    private ImageFetcher mImageFetcher;
+    private final Callback<LoadUrlParams> mLogoClickedCallback;
+    private final Callback<LogoBridge.Logo> mOnLogoAvailableRunnable;
+    private final Runnable mOnCachedLogoRevalidatedRunnable;
+    private boolean mHasLogoLoadedForCurrentSearchEngine;
+    private final boolean mShouldFetchDoodle;
+    private boolean mIsParentSurfaceShown; // This value should always be true when this class
+                                           // is used by NTP.
+    private final LogoCoordinator.VisibilityObserver mVisibilityObserver;
+    private final CachedTintedBitmap mDefaultGoogleLogo;
+    private boolean mShouldShowLogo;
+    private boolean mIsLoadPending;
+    private String mOnLogoClickUrl;
+    private String mAnimatedLogoUrl;
+    private boolean mShouldRecordLoadTime = true;
+
+    private final ObserverList<LogoCoordinator.VisibilityObserver> mVisibilityObservers =
+            new ObserverList<>();
+
+    /**
+     * Creates a LogoMediator object.
+     *
+     * @param context Used to load colors and resources.
+     * @param logoClickedCallback Supplies the StartSurface's parent tab.
+     * @param logoModel The model that is required to build the logo on start surface or ntp.
+     * @param shouldFetchDoodle Whether to fetch doodle if there is.
+     * @param onLogoAvailableCallback The callback for when logo is available.
+     * @param onCachedLogoRevalidatedRunnable The runnable for when cached logo is revalidated.
+     * @param isParentSurfaceShown Whether Start surface homepage or NTP is shown. This value
+     *                             is true when this class is used by NTP; while used by Start,
+     *                             it's only true on Start homepage.
+     * @param visibilityObserver Observer object monitoring logo visibility.
+     * @param defaultGoogleLogo The google logo shared across all NTPs when Google is the default
+     *                          search engine.
+     */
+    LogoMediator(Context context, Callback<LoadUrlParams> logoClickedCallback,
+            PropertyModel logoModel, boolean shouldFetchDoodle,
+            Callback<LogoBridge.Logo> onLogoAvailableCallback,
+            Runnable onCachedLogoRevalidatedRunnable, boolean isParentSurfaceShown,
+            LogoCoordinator.VisibilityObserver visibilityObserver,
+            CachedTintedBitmap defaultGoogleLogo) {
+        mContext = context;
+        mLogoModel = logoModel;
+        mLogoClickedCallback = logoClickedCallback;
+        mShouldFetchDoodle = shouldFetchDoodle;
+        mOnLogoAvailableRunnable = onLogoAvailableCallback;
+        mOnCachedLogoRevalidatedRunnable = onCachedLogoRevalidatedRunnable;
+        mIsParentSurfaceShown = isParentSurfaceShown;
+        mVisibilityObserver = visibilityObserver;
+        mVisibilityObservers.addObserver(mVisibilityObserver);
+        mDefaultGoogleLogo = defaultGoogleLogo;
+    }
+
+    /**
+     * Initialize the mediator with the components that had native initialization dependencies,
+     * i.e. Profile..
+     */
+    void initWithNative() {
+        if (mProfile != null) return;
+
+        mProfile = Profile.getLastUsedRegularProfile();
+        updateVisibility();
+
+        if (mShouldShowLogo) {
+            showSearchProviderInitialView();
+            if (mIsLoadPending) loadSearchProviderLogo(/*animationEnabled=*/false);
+        }
+
+        TemplateUrlServiceFactory.get().addObserver(this);
+    }
+
+    /** Update the logo based on default search engine changes.*/
+    @Override
+    public void onTemplateURLServiceChanged() {
+        mHasLogoLoadedForCurrentSearchEngine = false;
+        loadSearchProviderLogoWithAnimation();
+    }
+
+    /** Force to load the search provider logo with animation enabled.*/
+    void loadSearchProviderLogoWithAnimation() {
+        updateVisibilityAndMaybeCleanUp(mIsParentSurfaceShown, /*shouldDestroyBridge=*/false, true);
+    }
+
+    /**
+     * If it's on Start surface homepage or on NTP, load search provider logo; If it's not on Start
+     * surface homepage, destroy the part of LogoBridge which includes mImageFetcher.
+     *
+     * @param isParentSurfaceShown Whether Start surface homepage or NTP is shown. This value
+     *                             should always be true when this class is used by NTP.
+     * @param shouldDestroyBridge Whether to destroy the part of LogoBridge for saving memory. This
+     *                              value should always be false when this class is used by NTP.
+     *                              TODO(crbug.com/1315676): Remove this variable once the refactor
+     *                              is launched and StartSurfaceState is removed. Now we check this
+     *                              because there are some intermediate StartSurfaceStates,
+     *                              i.e. SHOWING_START.
+     * @param animationEnabled Whether to enable the fade in animation.
+     */
+    void updateVisibilityAndMaybeCleanUp(
+            boolean isParentSurfaceShown, boolean shouldDestroyBridge, boolean animationEnabled) {
+        assert !isParentSurfaceShown || !shouldDestroyBridge;
+
+        mIsParentSurfaceShown = isParentSurfaceShown;
+        updateVisibility();
+
+        if (mShouldShowLogo) {
+            if (mProfile != null) {
+                loadSearchProviderLogo(animationEnabled);
+            } else {
+                mIsLoadPending = true;
+            }
+        } else if (shouldDestroyBridge && mLogoBridge != null) {
+            mHasLogoLoadedForCurrentSearchEngine = false;
+            // Destroy the part of logoBridge when hiding Start surface homepage to save memory.
+            cleanUp();
+        }
+    }
+
+    /** Cleans up any code as necessary.*/
+    void destroy() {
+        cleanUp();
+
+        if (mProfile != null) {
+            TemplateUrlServiceFactory.get().removeObserver(this);
+        }
+
+        if (mVisibilityObserver != null) {
+            mVisibilityObservers.removeObserver(mVisibilityObserver);
+        }
+    }
+
+    private void cleanUp() {
+        if (mLogoBridge != null) {
+            mLogoBridge.destroy();
+            mLogoBridge = null;
+            mImageFetcher.destroy();
+            mImageFetcher = null;
+        }
+    }
+
+    /** Returns whether LogoView is visible.*/
+    boolean isLogoVisible() {
+        return mShouldShowLogo && mLogoModel.get(LogoProperties.VISIBILITY);
+    }
+
+    /**
+     * Load the search provider logo on Start surface.
+     *
+     * @param animationEnabled Whether to enable the fade in animation.
+     */
+    private void loadSearchProviderLogo(boolean animationEnabled) {
+        // If logo is already updated for the current search provider, or profile is null or off the
+        // record, don't bother loading the logo image.
+        if (mHasLogoLoadedForCurrentSearchEngine || mProfile == null || !mShouldShowLogo) return;
+
+        mHasLogoLoadedForCurrentSearchEngine = true;
+        mLogoModel.set(LogoProperties.ANIMATION_ENABLED, animationEnabled);
+        showSearchProviderInitialView();
+
+        // If default search engine is google and doodle is not supported, doesn't bother to fetch
+        // logo image.
+        if (TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle() && !mShouldFetchDoodle) {
+            return;
+        }
+
+        if (mLogoBridge == null) {
+            mLogoBridge = new LogoBridge(mProfile);
+            mImageFetcher = ImageFetcherFactory.createImageFetcher(
+                    ImageFetcherConfig.DISK_CACHE_ONLY, mProfile.getProfileKey());
+        }
+
+        getSearchProviderLogo(new LogoBridge.LogoObserver() {
+            @Override
+            public void onLogoAvailable(LogoBridge.Logo logo, boolean fromCache) {
+                if (logo == null) {
+                    if (fromCache) {
+                        // There is no cached logo. Wait until we know whether there's a fresh
+                        // one before making any further decisions.
+                        return;
+                    }
+                    mLogoModel.set(
+                            LogoProperties.DEFAULT_GOOGLE_LOGO, getDefaultGoogleLogo(mContext));
+                }
+                mLogoModel.set(LogoProperties.LOGO_CLICK_HANDLER, LogoMediator.this::onLogoClicked);
+                mLogoModel.set(LogoProperties.LOGO, logo);
+
+                if (mOnLogoAvailableRunnable != null) mOnLogoAvailableRunnable.onResult(logo);
+            }
+
+            @Override
+            public void onCachedLogoRevalidated() {
+                if (mOnCachedLogoRevalidatedRunnable != null) {
+                    mOnCachedLogoRevalidatedRunnable.run();
+                }
+            }
+        });
+    }
+
+    private void showSearchProviderInitialView() {
+        mLogoModel.set(LogoProperties.DEFAULT_GOOGLE_LOGO, getDefaultGoogleLogo(mContext));
+        mLogoModel.set(LogoProperties.SHOW_SEARCH_PROVIDER_INITIAL_VIEW, true);
+    }
+
+    private void updateVisibility() {
+        boolean doesDseHaveLogo = mProfile != null
+                ? TemplateUrlServiceFactory.get().doesDefaultSearchEngineHaveLogo()
+                : SharedPreferencesManager.getInstance().readBoolean(
+                        APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, true);
+        mShouldShowLogo = mIsParentSurfaceShown && doesDseHaveLogo;
+        mLogoModel.set(LogoProperties.VISIBILITY, mShouldShowLogo);
+        for (LogoCoordinator.VisibilityObserver observer : mVisibilityObservers) {
+            observer.onLogoVisibilityChanged();
+        }
+    }
+
+    /**
+     * Get the default Google logo if available.
+     * @param context Used to load colors and resources.
+     * @return The default Google logo.
+     */
+    @VisibleForTesting
+    Bitmap getDefaultGoogleLogo(Context context) {
+        return TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()
+                ? mDefaultGoogleLogo.getBitmap(context)
+                : null;
+    }
+
+    public void onLogoClicked(boolean isAnimatedLogoShowing) {
+        if (mLogoBridge == null) return;
+
+        if (!isAnimatedLogoShowing && mAnimatedLogoUrl != null) {
+            RecordHistogram.recordSparseHistogram(
+                    LOGO_CLICK_UMA_NAME, LogoClickId.CTA_IMAGE_CLICKED);
+            mLogoModel.set(LogoProperties.SHOW_LOADING_VIEW, true);
+            mImageFetcher.fetchGif(ImageFetcher.Params.create(mAnimatedLogoUrl,
+                                           ImageFetcher.NTP_ANIMATED_LOGO_UMA_CLIENT_NAME),
+                    (BaseGifImage animatedLogoImage) -> {
+                        if (mLogoBridge == null || animatedLogoImage == null) return;
+                        mLogoModel.set(LogoProperties.ANIMATED_LOGO, animatedLogoImage);
+                    });
+        } else if (mOnLogoClickUrl != null) {
+            RecordHistogram.recordSparseHistogram(LOGO_CLICK_UMA_NAME,
+                    isAnimatedLogoShowing ? LogoClickId.ANIMATED_LOGO_CLICKED
+                                          : LogoClickId.STATIC_LOGO_CLICKED);
+            mLogoClickedCallback.onResult(new LoadUrlParams(mOnLogoClickUrl, PageTransition.LINK));
+        }
+    }
+
+    private void getSearchProviderLogo(final LogoObserver logoObserver) {
+        assert mLogoBridge != null;
+
+        final long loadTimeStart = System.currentTimeMillis();
+
+        LogoObserver wrapperCallback = new LogoObserver() {
+            @Override
+            public void onLogoAvailable(Logo logo, boolean fromCache) {
+                if (mLogoBridge == null) return;
+
+                if (logo != null) {
+                    int logoType = logo.animatedLogoUrl == null ? LogoShownId.STATIC_LOGO_SHOWN
+                                                                : LogoShownId.CTA_IMAGE_SHOWN;
+                    RecordHistogram.recordEnumeratedHistogram(
+                            LOGO_SHOWN_UMA_NAME, logoType, LogoShownId.LOGO_SHOWN_COUNT);
+                    if (fromCache) {
+                        RecordHistogram.recordEnumeratedHistogram(LOGO_SHOWN_FROM_CACHE_UMA_NAME,
+                                logoType, LogoShownId.LOGO_SHOWN_COUNT);
+                    } else {
+                        RecordHistogram.recordEnumeratedHistogram(
+                                LOGO_SHOWN_FRESH_UMA_NAME, logoType, LogoShownId.LOGO_SHOWN_COUNT);
+                    }
+                    if (mShouldRecordLoadTime) {
+                        long loadTime = System.currentTimeMillis() - loadTimeStart;
+                        RecordHistogram.recordMediumTimesHistogram(
+                                LOGO_SHOWN_TIME_UMA_NAME, loadTime);
+                        // Only record the load time once per NTP, for the first logo we got,
+                        // whether that came from cache or not.
+                        mShouldRecordLoadTime = false;
+                    }
+                } else if (!fromCache) {
+                    // If we got a fresh (i.e. not from cache) null logo, don't record any load
+                    // time even if we get another update later.
+                    mShouldRecordLoadTime = false;
+                }
+
+                mOnLogoClickUrl = logo != null ? logo.onClickUrl : null;
+                mAnimatedLogoUrl = logo != null ? logo.animatedLogoUrl : null;
+
+                logoObserver.onLogoAvailable(logo, fromCache);
+            }
+
+            @Override
+            public void onCachedLogoRevalidated() {
+                logoObserver.onCachedLogoRevalidated();
+            }
+        };
+
+        mLogoBridge.getCurrentLogo(wrapperCallback);
+    }
+
+    // TODO(crbug.com/1394983): Remove the following ForTesting methods if possible.
+    void setHasLogoLoadedForCurrentSearchEngineForTesting(
+            boolean hasLogoLoadedForCurrentSearchEngine) {
+        mHasLogoLoadedForCurrentSearchEngine = hasLogoLoadedForCurrentSearchEngine;
+    }
+
+    void setLogoBridgeForTesting(LogoBridge logoBridge) {
+        mLogoBridge = logoBridge;
+    }
+
+    void setImageFetcherForTesting(ImageFetcher imageFetcher) {
+        mImageFetcher = imageFetcher;
+    }
+
+    void setAnimatedLogoUrlForTesting(String animatedLogoUrl) {
+        mAnimatedLogoUrl = animatedLogoUrl;
+    }
+
+    ImageFetcher getImageFetcherForTesting() {
+        return mImageFetcher;
+    }
+
+    LogoBridge getLogoBridgeForTesting() {
+        return mLogoBridge;
+    }
+
+    boolean getIsLoadPendingForTesting() {
+        return mIsLoadPending;
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediatorUnitTest.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediatorUnitTest.java
new file mode 100644
index 0000000..8da1ef0
--- /dev/null
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoMediatorUnitTest.java
@@ -0,0 +1,328 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.logo;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.homepage.HomepageManager;
+import org.chromium.chrome.browser.logo.LogoBridge.Logo;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
+import org.chromium.components.image_fetcher.ImageFetcher;
+import org.chromium.components.search_engines.TemplateUrlService;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** Unit tests for the {@link LogoMediator}.*/
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class LogoMediatorUnitTest {
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+
+    @Mock
+    private Profile mProfile;
+
+    @Mock
+    LogoBridge.Natives mLogoBridgeJniMock;
+
+    @Mock
+    LogoBridge mLogoBridge;
+
+    @Mock
+    ImageFetcher mImageFetcher;
+
+    @Mock
+    TemplateUrlService mTemplateUrlService;
+
+    @Mock
+    Callback<LoadUrlParams> mLogoClickedCallback;
+
+    @Mock
+    Callback<Logo> mOnLogoAvailableCallback;
+
+    @Mock
+    Runnable mOnCachedLogoRevalidatedRunnable;
+
+    @Captor
+    private ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver>
+            mTemplateUrlServiceObserverArgumentCaptor;
+
+    private Context mContext;
+    private PropertyModel mLogoModel;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Profile.setLastUsedProfileForTesting(mProfile);
+
+        mContext = ApplicationProvider.getApplicationContext();
+
+        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
+        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);
+        when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(true);
+
+        mJniMocker.mock(LogoBridgeJni.TEST_HOOKS, mLogoBridgeJniMock);
+
+        Assert.assertTrue(ChromeFeatureList.sStartSurfaceAndroid.isEnabled());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> HomepageManager.getInstance().setPrefHomepageEnabled(true));
+
+        mLogoModel = new PropertyModel(LogoProperties.ALL_KEYS);
+    }
+
+    @After
+    public void tearDown() {
+        TemplateUrlServiceFactory.setInstanceForTesting(null);
+    }
+
+    @Test
+    public void testDseChangedAndGoogleIsDseAndDoodleIsSupported() {
+        LogoMediator logoMediator = createMediator();
+        Assert.assertNotNull(logoMediator.getDefaultGoogleLogo(mContext));
+
+        verify(mTemplateUrlService)
+                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
+        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();
+
+        verify(mLogoBridge, times(1)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testDseChangedAndGoogleIsDseAndDoodleIsNotSupported() {
+        // If doodle isn't supported, getSearchProviderLogo() shouldn't be called by
+        // onTemplateURLServiceChanged().
+        LogoMediator logoMediator = createMediator(false);
+        Assert.assertNotNull(logoMediator.getDefaultGoogleLogo(mContext));
+
+        verify(mTemplateUrlService)
+                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
+        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();
+
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testDseChangedAndGoogleIsNotDse() {
+        LogoMediator logoMediator = createMediator();
+        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);
+        Assert.assertNull(logoMediator.getDefaultGoogleLogo(mContext));
+
+        verify(mTemplateUrlService)
+                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
+        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();
+
+        verify(mLogoBridge, times(1)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testDseChangedAndDoesNotHaveLogo() {
+        createMediator();
+        when(mTemplateUrlService.doesDefaultSearchEngineHaveLogo()).thenReturn(false);
+
+        verify(mTemplateUrlService)
+                .addObserver(mTemplateUrlServiceObserverArgumentCaptor.capture());
+        mTemplateUrlServiceObserverArgumentCaptor.getValue().onTemplateURLServiceChanged();
+
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testLoadLogoWhenLogoNotLoaded() {
+        LogoMediator logoMediator = createMediator();
+        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(false);
+        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);
+
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+
+        verify(mLogoBridge, times(1)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testLoadLogoWhenLogoHasLoaded() {
+        LogoMediator logoMediator = createMediator();
+        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(true);
+        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);
+
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+    }
+
+    @Test
+    public void testInitWithNativeWhenParentSurfaceIsNotVisible() {
+        LogoMediator logoMediator =
+                createMediatorWithoutNative(/*isParentSurfaceShown=*/false, true);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ false, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+        Assert.assertFalse(mLogoModel.get(LogoProperties.VISIBILITY));
+        // When parent surface isn't showing, calling updateVisibilityAndMaybeCleanUp() shouldn't
+        // trigger getSearchProviderLogo() nor add any pending load task.
+        Assert.assertFalse(logoMediator.getIsLoadPendingForTesting());
+        logoMediator.initWithNative();
+
+        Assert.assertFalse(mLogoModel.get(LogoProperties.VISIBILITY));
+        Assert.assertFalse(logoMediator.isLogoVisible());
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+        verify(mTemplateUrlService).addObserver(logoMediator);
+    }
+
+    @Test
+    public void testInitWithNativeWhenParentSurfaceIsVisible() {
+        LogoMediator logoMediator = createMediatorWithoutNative(true, true);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+
+        Assert.assertTrue(logoMediator.isLogoVisible());
+        // When parent surface is shown while native library isn't loaded, calling
+        // updateVisibilityAndMaybeCleanUp() will add a pending load task.
+        Assert.assertTrue(logoMediator.getIsLoadPendingForTesting());
+        logoMediator.initWithNative();
+
+        Assert.assertTrue(logoMediator.isLogoVisible());
+        verify(mLogoBridge, times(1)).getCurrentLogo(any());
+        verify(mTemplateUrlService).addObserver(logoMediator);
+    }
+
+    @Test
+    public void testInitWithoutNativeWhenDseDoesNotHaveLogo() {
+        LogoMediator logoMediator = createMediatorWithoutNative(true, true);
+        boolean origin_key_value = SharedPreferencesManager.getInstance().readBoolean(
+                APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO,
+                TemplateUrlServiceFactory.get().doesDefaultSearchEngineHaveLogo());
+        SharedPreferencesManager.getInstance().writeBoolean(
+                APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, false);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+        Assert.assertFalse(mLogoModel.get(LogoProperties.VISIBILITY));
+        Assert.assertFalse(logoMediator.getIsLoadPendingForTesting());
+        verify(mLogoBridge, times(0)).destroy();
+        SharedPreferencesManager.getInstance().writeBoolean(
+                APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, origin_key_value);
+    }
+
+    @Test
+    public void testUpdateVisibilityAndMaybeCleanUp() {
+        LogoMediator logoMediator = createMediator();
+
+        // If parent surface is not shown nor bridge shouldn't be destroyed, logo shouldn't be
+        // loaded and bridge isn't destroyed.
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ false, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+        Assert.assertFalse(logoMediator.isLogoVisible());
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+        verify(mLogoBridge, times(0)).destroy();
+
+        // If parent surface is not shown and bridge should be destroyed, logo should be
+        // loaded and bridge is destroyed.
+        logoMediator.setImageFetcherForTesting(mImageFetcher);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ false, /*shouldDestroyBridge*/ true,
+                /*animationEnabled*/ false);
+        Assert.assertFalse(logoMediator.isLogoVisible());
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+        verify(mLogoBridge, times(1)).destroy();
+        verify(mImageFetcher, times(1)).destroy();
+        Assert.assertNull(logoMediator.getLogoBridgeForTesting());
+        Assert.assertNull(logoMediator.getImageFetcherForTesting());
+
+        // If parent surface is shown, logo should be loaded and bridge shouldn't be
+        // destroyed.
+        logoMediator.setLogoBridgeForTesting(mLogoBridge);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ false);
+        Assert.assertTrue(logoMediator.isLogoVisible());
+        verify(mLogoBridge, times(1)).getCurrentLogo(any());
+        verify(mLogoBridge, times(1)).destroy();
+        Assert.assertFalse(mLogoModel.get(LogoProperties.ANIMATION_ENABLED));
+
+        // Attached the test for animationEnabled.
+        logoMediator.setHasLogoLoadedForCurrentSearchEngineForTesting(false);
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ false,
+                /*animationEnabled*/ true);
+        Assert.assertTrue(mLogoModel.get(LogoProperties.ANIMATION_ENABLED));
+    }
+
+    @Test(expected = AssertionError.class)
+    public void testUpdateVisibilityAndMaybeCleanUpAssertionError() {
+        LogoMediator logoMediator = createMediator();
+        verify(mLogoBridge, times(0)).getCurrentLogo(any());
+
+        // If parent surface is shown and bridge should be destroyed, an assertion error
+        // should be thrown.
+        logoMediator.updateVisibilityAndMaybeCleanUp(
+                /*isParentSurfaceShown*/ true, /*shouldDestroyBridge*/ true,
+                /*animationEnabled*/ false); // should throw an exception
+    }
+
+    @Test
+    public void testDestroyWhenInitWithNative() {
+        LogoMediator logoMediator = createMediator();
+        logoMediator.setLogoBridgeForTesting(null);
+        logoMediator.destroy();
+        verify(mTemplateUrlService).removeObserver(logoMediator);
+    }
+
+    private LogoMediator createMediator(boolean shouldFetchDoodle) {
+        LogoMediator logoMediator =
+                createMediatorWithoutNative(/*isParentSurfaceShown=*/true, shouldFetchDoodle);
+        logoMediator.initWithNative();
+        return logoMediator;
+    }
+
+    private LogoMediator createMediator() {
+        LogoMediator logoMediator =
+                createMediatorWithoutNative(/*isParentSurfaceShown=*/true, true);
+        logoMediator.initWithNative();
+        return logoMediator;
+    }
+
+    private LogoMediator createMediatorWithoutNative(
+            boolean isParentSurfaceShown, boolean shouldFetchDoodle) {
+        LogoMediator logoMediator = new LogoMediator(mContext, mLogoClickedCallback, mLogoModel,
+                shouldFetchDoodle, mOnLogoAvailableCallback, mOnCachedLogoRevalidatedRunnable,
+                isParentSurfaceShown, null,
+                new CachedTintedBitmap(R.drawable.google_logo, R.color.google_logo_tint_color));
+        logoMediator.setLogoBridgeForTesting(mLogoBridge);
+        return logoMediator;
+    }
+}
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoProperties.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoProperties.java
index ad1b6b4..fdd5d6d 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoProperties.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoProperties.java
@@ -4,31 +4,45 @@
 
 package org.chromium.chrome.browser.logo;
 
+import android.graphics.Bitmap;
+
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
-/**
- * The properties required to build the logo on start surface or ntp.
- */
+import jp.tomorrowkey.android.gifplayer.BaseGifImage;
+
+/** The properties required to build the logo on start surface or ntp.*/
 interface LogoProperties {
+    // TODO(crbug.com/1394983): It doesn't really make sense for those
+    //  WritableObjectPropertyKey<Boolean> with skipEquality equals to true property keys;
+    //  if we're not going to read the value out of this in the ViewBinder.
     WritableFloatPropertyKey ALPHA = new WritableFloatPropertyKey();
     WritableIntPropertyKey LOGO_TOP_MARGIN = new WritableIntPropertyKey();
     WritableIntPropertyKey LOGO_BOTTOM_MARGIN = new WritableIntPropertyKey();
-    WritableObjectPropertyKey SET_END_FADE_ANIMATION =
+    WritableObjectPropertyKey<Boolean> SET_END_FADE_ANIMATION =
             new WritableObjectPropertyKey<>(true /* skipEquality */);
-    WritableObjectPropertyKey DESTROY = new WritableObjectPropertyKey<>(true /* skipEquality */);
+    // TODO(crbug.com/1394983): Change the VISIBILITY properties to some sort of state
+    //  enum if possible.
     WritableBooleanPropertyKey VISIBILITY = new WritableBooleanPropertyKey();
     WritableBooleanPropertyKey ANIMATION_ENABLED = new WritableBooleanPropertyKey();
-    WritableObjectPropertyKey LOGO_DELEGATE = new WritableObjectPropertyKey<>();
-    WritableObjectPropertyKey SHOW_SEARCH_PROVIDER_INITIAL_VIEW =
+    WritableObjectPropertyKey<LogoView.ClickHandler> LOGO_CLICK_HANDLER =
+            new WritableObjectPropertyKey<>();
+    WritableObjectPropertyKey<Boolean> SHOW_SEARCH_PROVIDER_INITIAL_VIEW =
             new WritableObjectPropertyKey<>(true /* skipEquality */);
-    WritableObjectPropertyKey UPDATED_LOGO = new WritableObjectPropertyKey<>();
-    WritableObjectPropertyKey DEFAULT_GOOGLE_LOGO = new WritableObjectPropertyKey<>();
+    // TODO(crbug.com/1394983): Generate the LOGO, DEFAULT_GOOGLE_LOGO and ANIMATED_LOGO properties
+    //  into one property that takes an object generic/powerful enough to represent all three of
+    //  these if possible.
+    WritableObjectPropertyKey<LogoBridge.Logo> LOGO = new WritableObjectPropertyKey<>();
+    WritableObjectPropertyKey<Bitmap> DEFAULT_GOOGLE_LOGO = new WritableObjectPropertyKey<>();
+    WritableObjectPropertyKey<Boolean> SHOW_LOADING_VIEW =
+            new WritableObjectPropertyKey<>(true /* skipEquality */);
+    WritableObjectPropertyKey<BaseGifImage> ANIMATED_LOGO = new WritableObjectPropertyKey<>();
 
     PropertyKey[] ALL_KEYS = new PropertyKey[] {ALPHA, LOGO_TOP_MARGIN, LOGO_BOTTOM_MARGIN,
-            SET_END_FADE_ANIMATION, DESTROY, VISIBILITY, ANIMATION_ENABLED, LOGO_DELEGATE,
-            SHOW_SEARCH_PROVIDER_INITIAL_VIEW, UPDATED_LOGO, DEFAULT_GOOGLE_LOGO};
+            SET_END_FADE_ANIMATION, VISIBILITY, ANIMATION_ENABLED, LOGO_CLICK_HANDLER,
+            SHOW_SEARCH_PROVIDER_INITIAL_VIEW, LOGO, DEFAULT_GOOGLE_LOGO, SHOW_LOADING_VIEW,
+            ANIMATED_LOGO};
 }
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoView.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoView.java
index b274c6f..65b43e3 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoView.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoView.java
@@ -61,7 +61,7 @@
      */
     private float mTransitionAmount;
 
-    private Delegate mDelegate;
+    private ClickHandler mClickHandler;
 
     private final FloatProperty<LogoView> mTransitionProperty =
             new FloatProperty<LogoView>("") {
@@ -82,7 +82,8 @@
             };
 
     /** Handles tasks for the {@link LogoView} shown on an NTP.*/
-    public interface Delegate {
+    @FunctionalInterface
+    interface ClickHandler {
         /**
          * Called when the user clicks on the logo.
          * @param isAnimatedLogoShowing Whether the animated GIF logo is playing.
@@ -115,7 +116,7 @@
     }
 
     /** Clean up member variables when this view is no longer needed.*/
-    public void destroy() {
+    void destroy() {
         // Need to end the animation otherwise it can cause memory leaks since the AnimationHandler
         // has a reference to the animation callback which then can link back to the
         // {@code mTransitionProperty}.
@@ -123,13 +124,13 @@
         mLoadingView.destroy();
     }
 
-    /** Sets the {@link Delegate} to notify when the logo is pressed.*/
-    public void setDelegate(Delegate delegate) {
-        mDelegate = delegate;
+    /** Sets the {@link ClickHandler} to notify when the logo is pressed.*/
+    void setClickHandler(ClickHandler clickHandler) {
+        mClickHandler = clickHandler;
     }
 
     /** Jumps to the end of the logo cross-fading animation, if any.*/
-    public void endFadeAnimation() {
+    void endFadeAnimation() {
         if (mFadeAnimation != null) {
             mFadeAnimation.end();
             mFadeAnimation = null;
@@ -142,7 +143,7 @@
     }
 
     /** Starts playing the given animated GIF logo.*/
-    public void playAnimatedLogo(BaseGifImage gifImage) {
+    void playAnimatedLogo(BaseGifImage gifImage) {
         mLoadingView.hideLoadingUI();
         mAnimatedLogoDrawable = new BaseGifDrawable(gifImage, Config.ARGB_8888);
         mAnimatedLogoMatrix = new Matrix();
@@ -154,7 +155,7 @@
     }
 
     /** Show a spinning progressbar.*/
-    public void showLoadingView() {
+    void showLoadingView() {
         mLogo = null;
         invalidate();
         mLoadingView.showLoadingUI();
@@ -164,7 +165,7 @@
      * Show a loading indicator or a baked-in default search provider logo, based on what is
      * available.
      */
-    public void showSearchProviderInitialView() {
+    void showSearchProviderInitialView() {
         if (maybeShowDefaultLogo()) return;
 
         showLoadingView();
@@ -175,7 +176,7 @@
      *
      * @param logo The new logo to fade in.
      */
-    public void updateLogo(Logo logo) {
+    void updateLogo(Logo logo) {
         if (logo == null) {
             if (maybeShowDefaultLogo()) return;
 
@@ -191,7 +192,7 @@
                 logo.image, contentDescription, /* isDefaultLogo = */ false, isLogoClickable(logo));
     }
 
-    public void setAnimationEnabled(boolean animationEnabled) {
+    void setAnimationEnabled(boolean animationEnabled) {
         mAnimationEnabled = animationEnabled;
     }
 
@@ -247,7 +248,7 @@
         mFadeAnimation.start();
     }
 
-    public void setDefaultGoogleLogo(Bitmap defaultGoogleLogo) {
+    void setDefaultGoogleLogo(Bitmap defaultGoogleLogo) {
         mDefaultGoogleLogo = defaultGoogleLogo;
     }
 
@@ -361,8 +362,8 @@
 
     @Override
     public void onClick(View view) {
-        if (view == this && mDelegate != null && !isTransitioning()) {
-            mDelegate.onLogoClicked(isAnimatedLogoShowing());
+        if (view == this && mClickHandler != null && !isTransitioning()) {
+            mClickHandler.onLogoClicked(isAnimatedLogoShowing());
         }
     }
 
@@ -378,6 +379,10 @@
         return mNewLogo;
     }
 
+    Bitmap getLogoForTesting() {
+        return mLogo;
+    }
+
     boolean getAnimationEnabledForTesting() {
         return mAnimationEnabled;
     }
@@ -390,11 +395,19 @@
         mLoadingView.addObserver(listener);
     }
 
-    Delegate getDelegateForTesting() {
-        return mDelegate;
+    ClickHandler getClickHandlerForTesting() {
+        return mClickHandler;
     }
 
     Bitmap getDefaultGoogleLogoForTesting() {
         return mDefaultGoogleLogo;
     }
+
+    int getLoadingViewVisibilityForTesting() {
+        return mLoadingView.getVisibility();
+    }
+
+    void setLoadingViewVisibilityForTesting(int visibility) {
+        mLoadingView.setVisibility(visibility);
+    }
 }
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinder.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinder.java
index 402dc321..ec35e5e1 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinder.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinder.java
@@ -4,11 +4,9 @@
 
 package org.chromium.chrome.browser.logo;
 
-import android.graphics.Bitmap;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 
-import org.chromium.chrome.browser.logo.LogoBridge.Logo;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -30,20 +28,22 @@
             marginLayoutParams.bottomMargin = model.get(LogoProperties.LOGO_BOTTOM_MARGIN);
         } else if (LogoProperties.SET_END_FADE_ANIMATION == propertyKey) {
             logoView.endFadeAnimation();
-        } else if (LogoProperties.DESTROY == propertyKey) {
-            logoView.destroy();
         } else if (LogoProperties.VISIBILITY == propertyKey) {
             logoView.setVisibility(model.get(LogoProperties.VISIBILITY) ? View.VISIBLE : View.GONE);
         } else if (LogoProperties.ANIMATION_ENABLED == propertyKey) {
             logoView.setAnimationEnabled(model.get(LogoProperties.ANIMATION_ENABLED));
-        } else if (LogoProperties.LOGO_DELEGATE == propertyKey) {
-            logoView.setDelegate((LogoDelegateImpl) model.get(LogoProperties.LOGO_DELEGATE));
+        } else if (LogoProperties.LOGO_CLICK_HANDLER == propertyKey) {
+            logoView.setClickHandler(model.get(LogoProperties.LOGO_CLICK_HANDLER));
         } else if (LogoProperties.SHOW_SEARCH_PROVIDER_INITIAL_VIEW == propertyKey) {
             logoView.showSearchProviderInitialView();
-        } else if (LogoProperties.UPDATED_LOGO == propertyKey) {
-            logoView.updateLogo((Logo) model.get(LogoProperties.UPDATED_LOGO));
+        } else if (LogoProperties.LOGO == propertyKey) {
+            logoView.updateLogo(model.get(LogoProperties.LOGO));
         } else if (LogoProperties.DEFAULT_GOOGLE_LOGO == propertyKey) {
-            logoView.setDefaultGoogleLogo((Bitmap) model.get(LogoProperties.DEFAULT_GOOGLE_LOGO));
+            logoView.setDefaultGoogleLogo(model.get(LogoProperties.DEFAULT_GOOGLE_LOGO));
+        } else if (LogoProperties.SHOW_LOADING_VIEW == propertyKey) {
+            logoView.showLoadingView();
+        } else if (LogoProperties.ANIMATED_LOGO == propertyKey) {
+            logoView.playAnimatedLogo(model.get(LogoProperties.ANIMATED_LOGO));
         } else {
             assert false : "Unhandled property detected in LogoViewBinder!";
         }
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinderUnitTest.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinderUnitTest.java
index 9e02d40..bc9b903 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinderUnitTest.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewBinderUnitTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -31,17 +32,22 @@
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.logo.LogoBridge.Logo;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.profiles.ProfileJni;
+import org.chromium.components.image_fetcher.ImageFetcher;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 import org.chromium.ui.widget.LoadingView;
 
+import jp.tomorrowkey.android.gifplayer.BaseGifImage;
+
+// TODO(crbug.com/1394983): For the LogoViewTest and LogoViewBinderUnitTest, that's the nice thing
+//  about only have 1 test file, where all test cases go into the single test file.
+
 /** Unit tests for the {@link LogoViewBinder}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
@@ -50,24 +56,26 @@
     private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
     private PropertyModel mLogoModel;
     private LogoView mLogoView;
-    private LogoDelegateImpl mLogoDelegate;
+    private LogoMediator mLogoMediator;
     private static final double DELTA = 1e-5;
+    private static final String ANIMATED_LOGO_URL =
+            "https://www.gstatic.com/chrome/ntp/doodle_test/ddljson_android4.json";
 
     @Rule
-    public final JniMocker mJniMocker = new JniMocker();
-
-    @Mock
-    LogoBridge.Natives mLogoBridge;
-
-    @Mock
-    Profile.Natives mProfileJniMock;
-
-    @Mock
-    private Profile mProfile;
+    public JniMocker mJniMocker = new JniMocker();
 
     @Mock
     private LogoView mMockLogoView;
 
+    @Mock
+    LogoBridge.Natives mLogoBridgeJniMock;
+
+    @Mock
+    LogoBridge mLogoBridge;
+
+    @Mock
+    ImageFetcher mImageFetcher;
+
     static class TestObserver implements LoadingView.Observer {
         public final CallbackHelper showLoadingCallback = new CallbackHelper();
         public final CallbackHelper hideLoadingCallback = new CallbackHelper();
@@ -86,6 +94,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mJniMocker.mock(LogoBridgeJni.TEST_HOOKS, mLogoBridgeJniMock);
         mActivity = Robolectric.buildActivity(Activity.class).setup().get();
         mLogoView = new LogoView(mActivity, null);
         LayoutParams params =
@@ -94,9 +103,8 @@
         mLogoModel = new PropertyModel(LogoProperties.ALL_KEYS);
         mPropertyModelChangeProcessor =
                 PropertyModelChangeProcessor.create(mLogoModel, mLogoView, new LogoViewBinder());
-        mJniMocker.mock(LogoBridgeJni.TEST_HOOKS, mLogoBridge);
-        mJniMocker.mock(ProfileJni.TEST_HOOKS, mProfileJniMock);
-        mLogoDelegate = new LogoDelegateImpl(null, mLogoView, mProfile);
+        mLogoMediator =
+                new LogoMediator(null, null, mLogoModel, true, null, null, true, null, null);
     }
 
     @After
@@ -105,7 +113,7 @@
         mLogoModel = null;
         mLogoView = null;
         mActivity = null;
-        mLogoDelegate = null;
+        mLogoMediator = null;
     }
 
     @Test
@@ -136,13 +144,13 @@
         Logo logo = new Logo(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8), null, null,
                 "https://www.gstatic.com/chrome/ntp/doodle_test/ddljson_android4.json");
         assertNull(mLogoView.getFadeAnimationForTesting());
-        mLogoModel.set(LogoProperties.UPDATED_LOGO, logo);
+        mLogoModel.set(LogoProperties.LOGO, logo);
         assertNotNull(mLogoView.getFadeAnimationForTesting());
         mLogoModel.set(LogoProperties.SET_END_FADE_ANIMATION, true);
         assertNull(mLogoView.getFadeAnimationForTesting());
         Logo newLogo = new Logo(Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888),
                 "https://www.google.com", null, null);
-        mLogoModel.set(LogoProperties.UPDATED_LOGO, newLogo);
+        mLogoModel.set(LogoProperties.LOGO, newLogo);
         assertNotNull(mLogoView.getFadeAnimationForTesting());
         mLogoModel.set(LogoProperties.SET_END_FADE_ANIMATION, true);
         assertNull(mLogoView.getFadeAnimationForTesting());
@@ -151,27 +159,12 @@
     @Test
     @UiThreadTest
     @SmallTest
-    public void testDestroy() {
-        Logo logo = new Logo(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8), null, null,
-                "https://www.gstatic.com/chrome/ntp/doodle_test/ddljson_android4.json");
-        mLogoModel.set(LogoProperties.UPDATED_LOGO, logo);
-        mLogoView.addLoadingViewObserverForTesting(new TestObserver());
-        assertEquals(false, mLogoView.checkLoadingViewObserverEmptyForTesting());
-        assertNotNull(mLogoView.getFadeAnimationForTesting());
-        mLogoModel.set(LogoProperties.DESTROY, true);
-        assertNull(mLogoView.getFadeAnimationForTesting());
-        assertEquals(true, mLogoView.checkLoadingViewObserverEmptyForTesting());
-    }
-
-    @Test
-    @UiThreadTest
-    @SmallTest
     public void testUpdateLogo() {
         Logo logo = new Logo(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8), null, null,
                 "https://www.gstatic.com/chrome/ntp/doodle_test/ddljson_android4.json");
         assertNull(mLogoView.getFadeAnimationForTesting());
         assertNotEquals(logo.image, mLogoView.getNewLogoForTesting());
-        mLogoModel.set(LogoProperties.UPDATED_LOGO, logo);
+        mLogoModel.set(LogoProperties.LOGO, logo);
         assertNotNull(mLogoView.getFadeAnimationForTesting());
         assertEquals(logo.image, mLogoView.getNewLogoForTesting());
     }
@@ -201,10 +194,16 @@
     @Test
     @UiThreadTest
     @SmallTest
-    public void testSetLogoDelegate() {
-        assertNull(mLogoView.getDelegateForTesting());
-        mLogoModel.set(LogoProperties.LOGO_DELEGATE, mLogoDelegate);
-        assertEquals(mLogoDelegate, mLogoView.getDelegateForTesting());
+    public void testSetLogoClickHandler() {
+        assertNull(mLogoView.getClickHandlerForTesting());
+        mLogoMediator.setLogoBridgeForTesting(mLogoBridge);
+        mLogoMediator.setImageFetcherForTesting(mImageFetcher);
+        mLogoMediator.setAnimatedLogoUrlForTesting(ANIMATED_LOGO_URL);
+        mLogoModel.set(LogoProperties.LOGO_CLICK_HANDLER, mLogoMediator::onLogoClicked);
+        mLogoView.onClick(mLogoView);
+        assertEquals(
+                1, RecordHistogram.getHistogramValueCountForTesting("NewTabPage.LogoClick", 1));
+        verify(mImageFetcher, times(1)).fetchGif(any(), any());
     }
 
     @Test
@@ -218,4 +217,13 @@
         LogoModel.set(LogoProperties.SHOW_SEARCH_PROVIDER_INITIAL_VIEW, true);
         verify(mMockLogoView, times(2)).showSearchProviderInitialView();
     }
+
+    @Test
+    @UiThreadTest
+    @SmallTest
+    public void testLoadingViewWithAnimatedLogo() {
+        mLogoView.setLoadingViewVisibilityForTesting(View.INVISIBLE);
+        mLogoModel.set(LogoProperties.ANIMATED_LOGO, new BaseGifImage(new byte[] {}));
+        assertEquals(View.GONE, mLogoView.getLoadingViewVisibilityForTesting());
+    }
 }
diff --git a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewTest.java b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewTest.java
index 630f624..c979040 100644
--- a/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewTest.java
+++ b/chrome/browser/ui/android/logo/java/src/org/chromium/chrome/browser/logo/LogoViewTest.java
@@ -11,6 +11,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.text.TextUtils;
+import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
@@ -30,6 +31,8 @@
 import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.TestActivity;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 /** Instrumentation tests for {@link LogoView}. */
 @RunWith(BaseRobolectricTestRunner.class)
@@ -41,7 +44,7 @@
     @Mock
     public TemplateUrlService mTemplateUrlService;
     @Mock
-    public LogoDelegateImpl mLogoDelegate;
+    public LogoView.ClickHandler mLogoClickHandler;
 
     private static final String LOGO_URL = "https://www.google.com";
     private static final String ANIMATED_LOGO_URL =
@@ -50,6 +53,8 @@
 
     private LogoView mView;
     private Bitmap mBitmap;
+    private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
+    private PropertyModel mModel;
 
     @Before
     public void setup() {
@@ -62,6 +67,9 @@
             LayoutParams params =
                     new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
             activity.setContentView(mView, params);
+            mModel = new PropertyModel(LogoProperties.ALL_KEYS);
+            mPropertyModelChangeProcessor =
+                    PropertyModelChangeProcessor.create(mModel, mView, new LogoViewBinder());
         });
     }
 
@@ -74,7 +82,9 @@
     @Test
     public void testDefaultLogoView() {
         doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
-        mView.setDefaultGoogleLogo(LogoCoordinator.getDefaultGoogleLogo(mView.getContext()));
+        mView.setDefaultGoogleLogo(
+                new CachedTintedBitmap(R.drawable.google_logo, R.color.google_logo_tint_color)
+                        .getBitmap(mView.getContext()));
         mView.updateLogo(null);
         mView.endAnimationsForTesting();
 
@@ -110,12 +120,12 @@
 
     @Test
     public void testLogoView_WithUrl_Clicked() {
-        mView.setDelegate(mLogoDelegate);
+        mView.setClickHandler(mLogoClickHandler);
         Logo logo = new Logo(mBitmap, LOGO_URL, null, null);
         mView.updateLogo(logo);
         mView.endAnimationsForTesting();
         mView.performClick();
-        verify(mLogoDelegate, times(1)).onLogoClicked(false);
+        verify(mLogoClickHandler, times(1)).onLogoClicked(false);
     }
 
     @Test
@@ -129,4 +139,16 @@
         Assert.assertFalse("Logo should have a content description.",
                 TextUtils.isEmpty(mView.getContentDescription()));
     }
+
+    @Test
+    public void testShowLoadingView() {
+        Logo logo = new Logo(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8), null, null, null);
+        mModel.set(LogoProperties.LOGO, logo);
+        mView.endAnimationsForTesting();
+        Assert.assertNotNull(mView.getLogoForTesting());
+        mView.setLoadingViewVisibilityForTesting(View.VISIBLE);
+        mModel.set(LogoProperties.SHOW_LOADING_VIEW, true);
+        Assert.assertNull(mView.getLogoForTesting());
+        Assert.assertEquals(View.GONE, mView.getLoadingViewVisibilityForTesting());
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index d5ad88f..5b28fc7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -75,7 +75,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowLog.class, ShadowLooper.class, ShadowGURL.class})
-@Features.DisableFeatures({ChromeFeatureList.CLEAR_OMNIBOX_FOCUS_AFTER_NAVIGATION})
+@Features.EnableFeatures({ChromeFeatureList.CLEAR_OMNIBOX_FOCUS_AFTER_NAVIGATION})
 public class AutocompleteMediatorUnitTest {
     private static final int MINIMUM_NUMBER_OF_SUGGESTIONS_TO_SHOW = 5;
     private static final int SUGGESTION_MIN_HEIGHT = 20;
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 86d5b54..fcc8bac 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1974,13 +1974,13 @@
       </message>
 
         <!-- Mixed content download -->
-        <message name="IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_TITLE" desc="Title of the dialog for asking user to confirm a mixed content download">
+        <message name="IDS_INSECURE_DOWNLOAD_DIALOG_TITLE" desc="Title of the dialog for asking user to confirm an insecure download">
           File can’t be downloaded securely
         </message>
-        <message name="IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_CONFIRM_TEXT" desc="Text label for the confirm button on the mixed content download dialog for user to confirm the download.">
+        <message name="IDS_INSECURE_DOWNLOAD_DIALOG_CONFIRM_TEXT" desc="Text label for the confirm button on the insecure download dialog for user to confirm the download.">
           Keep
         </message>
-        <message name="IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_DISCARD_TEXT" desc="Text label for the cancel button on the mixed content download dialog for user to confirm the download.">
+        <message name="IDS_INSECURE_DOWNLOAD_DIALOG_DISCARD_TEXT" desc="Text label for the cancel button on the insecure download dialog for user to confirm the download.">
           Discard
         </message>
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1
similarity index 100%
rename from chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1
rename to chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_DISCARD_TEXT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_DISCARD_TEXT.png.sha1
similarity index 100%
rename from chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_DISCARD_TEXT.png.sha1
rename to chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_DISCARD_TEXT.png.sha1
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_TITLE.png.sha1
similarity index 100%
rename from chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MIXED_CONTENT_DOWNLOAD_DIALOG_TITLE.png.sha1
rename to chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSECURE_DOWNLOAD_DIALOG_TITLE.png.sha1
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
index 07de2e3..27c99fb 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
@@ -331,7 +331,7 @@
 
         mLogoCoordinator = new LogoCoordinator(mContext, mLogoClickedCallback, logoView,
                 mShouldFetchDoodle, /*onLogoAvailableCallback=*/null,
-                /*onCachedLogoRevalidatedRunnable=*/null, isOnHomepage());
+                /*onCachedLogoRevalidatedRunnable=*/null, isOnHomepage(), null);
 
         // The logo view may be ready after native is initialized, so we need to call
         // mLogoCoordinator.initWithNative() here in case that initLogoNative() skip it.
@@ -386,7 +386,7 @@
     private void updateLogoVisibility() {
         if (mLogoCoordinator == null) return;
 
-        mLogoCoordinator.maybeLoadSearchProviderLogo(isOnHomepage(),
+        mLogoCoordinator.updateVisibilityAndMaybeCleanUp(isOnHomepage(),
                 isOnATab() || isOnGridTabSwitcher()
                         || mStartSurfaceState == StartSurfaceState.DISABLED,
                 /*animationEnabled*/ false);
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index 160d57f..2931d75b1 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/ash/policy/display/display_resolution_handler.h"
 #include "chrome/browser/ash/policy/display/display_rotation_default_handler.h"
 #include "chrome/browser/ash/policy/display/display_settings_handler.h"
+#include "chrome/browser/ash/privacy_hub/privacy_hub_util.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/sync/sync_error_notifier_factory.h"
 #include "chrome/browser/ash/video_conference/video_conference_tray_controller_impl.h"
@@ -278,6 +279,9 @@
   media_client_ = std::make_unique<MediaClientImpl>();
   media_client_->Init();
 
+  // Passes (and continues passing) the current camera count to the PrivacyHub.
+  ash::privacy_hub_util::SetUpCameraCountObserver();
+
   if (ash::features::IsMicMuteNotificationsEnabled()) {
     app_access_notifier_ = std::make_unique<AppAccessNotifier>();
   }
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
index a15e4fb..03e41979 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.cc
@@ -92,11 +92,11 @@
 // in-progress downloads integration.
 bool IsEligibleForInProgressIntegration(
     const crosapi::mojom::DownloadItem* mojo_download_item) {
-  // The `has_is_mixed_content` field was the last field to be implemented in
+  // The `has_is_insecure` field was the last field to be implemented in
   // Lacros. Its presence indicates that other required metadata and APIs (e.g.
   // pause, resume, cancel, etc.) are also implemented and is therefore used to
   // gate eligibility.
-  return mojo_download_item->has_is_mixed_content;
+  return mojo_download_item->has_is_insecure;
 }
 
 // Returns whether the specified `mojo_download_item` is in progress.
@@ -181,7 +181,7 @@
       msg_id = IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_A11Y_NAME_SCANNING;
     } else if (IsDangerous() && !MightBeMalicious()) {
       msg_id = IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_A11Y_NAME_CONFIRM;
-    } else if (IsDangerous() || IsMixedContent()) {
+    } else if (IsDangerous() || IsInsecure()) {
       msg_id = IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_A11Y_NAME_DANGEROUS;
     } else if (IsPaused()) {
       msg_id = IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_A11Y_NAME_PAUSED;
@@ -212,7 +212,7 @@
   // Returns the current progress of the underlying download.
   // NOTE:
   //   * Progress is indeterminate if the download is being scanned.
-  //   * Progress is hidden if the download is dangerous or mixed content.
+  //   * Progress is hidden if the download is dangerous or insecure.
   HoldingSpaceProgress GetProgress() const {
     if (IsComplete(mojo_download_item_.get()))
       return HoldingSpaceProgress();
@@ -222,7 +222,7 @@
     }
     return HoldingSpaceProgress(GetReceivedBytes(), GetTotalBytes(),
                                 /*complete=*/false,
-                                /*hidden=*/IsDangerous() || IsMixedContent());
+                                /*hidden=*/IsDangerous() || IsInsecure());
   }
 
   // Returns the target file path associated with the underlying download.
@@ -246,8 +246,8 @@
   // Returns whether the underlying download is dangerous.
   bool IsDangerous() const { return mojo_download_item_->is_dangerous; }
 
-  // Returns whether the underlying download is mixed content.
-  bool IsMixedContent() const { return mojo_download_item_->is_mixed_content; }
+  // Returns whether the underlying download is insecure.
+  bool IsInsecure() const { return mojo_download_item_->is_insecure; }
 
   // Returns whether the underlying download is paused.
   bool IsPaused() const { return mojo_download_item_->is_paused; }
@@ -276,7 +276,7 @@
   // Returns a resolver which creates a `gfx::ImageSkia` placeholder
   // corresponding to the file type of the associated *target* file path, rather
   // than the *backing* file path, when a thumbnail cannot be generated. Note
-  // that if the download is dangerous or is mixed content, a placeholder
+  // that if the download is dangerous or is insecure, a placeholder
   // indicating error will be returned.
   HoldingSpaceImage::PlaceholderImageSkiaResolver
   GetPlaceholderImageSkiaResolver() const {
@@ -285,9 +285,8 @@
            const base::FilePath& file_path, const gfx::Size& size,
            const absl::optional<bool>& dark_background,
            const absl::optional<bool>& is_folder) {
-          if (in_progress_download &&
-              (in_progress_download->IsDangerous() ||
-               in_progress_download->IsMixedContent())) {
+          if (in_progress_download && (in_progress_download->IsDangerous() ||
+                                       in_progress_download->IsInsecure())) {
             return CreateErrorPlaceholderImageSkia(
                 size, /*color_name=*/in_progress_download->IsDangerous() &&
                               !in_progress_download->MightBeMalicious()
@@ -339,9 +338,9 @@
           IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_CONFIRM);
     }
 
-    // In-progress download items which are dangerous or mixed content have a
-    // special secondary text treatment.
-    if (IsDangerous() || IsMixedContent()) {
+    // In-progress download items which are dangerous or insecure have a special
+    // secondary text treatment.
+    if (IsDangerous() || IsInsecure()) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_HOLDING_SPACE_IN_PROGRESS_DOWNLOAD_DANGEROUS_FILE);
     }
@@ -403,9 +402,9 @@
     if (IsDangerous() && !MightBeMalicious())
       return cros_styles::ColorName::kTextColorWarning;
 
-    // In-progress download items which are dangerous or mixed content have a
-    // special secondary text treatment.
-    if (IsDangerous() || IsMixedContent())
+    // In-progress download items which are dangerous or insecure have a special
+    // secondary text treatment.
+    if (IsDangerous() || IsInsecure())
       return cros_styles::ColorName::kTextColorAlert;
 
     return absl::nullopt;
@@ -419,8 +418,7 @@
       crosapi::mojom::DownloadItemPtr mojo_download_item) {
     const bool was_dangerous_but_not_malicious =
         IsDangerous() && !MightBeMalicious();
-    const bool was_dangerous_or_mixed_content =
-        IsDangerous() || IsMixedContent();
+    const bool was_dangerous_or_insecure = IsDangerous() || IsInsecure();
 
     mojo_download_item_ = std::move(mojo_download_item);
 
@@ -431,15 +429,14 @@
 
     const bool is_dangerous_but_not_malicious =
         IsDangerous() && !MightBeMalicious();
-    const bool is_dangerous_or_mixed_content =
-        IsDangerous() || IsMixedContent();
+    const bool is_dangerous_or_insecure = IsDangerous() || IsInsecure();
 
     // Explicitly invalidate the image of the associated holding space item if
     // the download is transitioning to/from a state which required an error
     // placeholder image.
     const bool invalidate_image =
         was_dangerous_but_not_malicious != is_dangerous_but_not_malicious ||
-        was_dangerous_or_mixed_content != is_dangerous_or_mixed_content;
+        was_dangerous_or_insecure != is_dangerous_or_insecure;
 
     switch (mojo_download_item_->state) {
       case crosapi::mojom::DownloadState::kInProgress:
@@ -864,7 +861,7 @@
   std::vector<HoldingSpaceItem::InProgressCommand> in_progress_commands;
   if (!in_progress_download->GetProgress().IsComplete()) {
     if (!(in_progress_download->IsDangerous() ||
-          in_progress_download->IsMixedContent())) {
+          in_progress_download->IsInsecure())) {
       in_progress_commands.push_back(
           in_progress_download->IsPaused()
               ? HoldingSpaceItem::InProgressCommand(
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
index dfbbfebe..d0490f8 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_downloads_delegate.h
@@ -82,8 +82,7 @@
   // Invoked when the specified `in_progress_download` is updated. If
   // `invalidate_image` is `true`, the image for the associated holding space
   // item will be explicitly invalidated. This is necessary if, for example, the
-  // underlying download is transitioning to/from a dangerous or mixed content
-  // state.
+  // underlying download is transitioning to/from a dangerous or insecure state.
   void OnDownloadUpdated(InProgressDownload* in_progress_download,
                          bool invalidate_image);
 
@@ -102,7 +101,7 @@
   // specified `in_progress_download`. If `invalidate_image` is `true`, the
   // image for the holding space item will be explicitly invalidated. This is
   // necessary if, for example, the underlying download is transitioning to/from
-  // a dangerous or mixed content state.
+  // a dangerous or insecure state.
   void CreateOrUpdateHoldingSpaceItem(InProgressDownload* in_progress_download,
                                       bool invalidate_image);
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_browsertest.cc
index 82a1bae..37aea8d 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_browsertest.cc
@@ -830,12 +830,12 @@
   download->is_from_incognito_profile = FromIncognitoProfile();
 
   // Lacros clients which are eligible for in-progress downloads integration
-  // have `has_is_mixed_content` present. This field was the last field to be
+  // have `has_is_insecure` present. This field was the last field to be
   // implemented in Lacros. Its presence indicates that other required metadata
   // and APIs (e.g. pause, resume, cancel, etc.) are also implemented and is
   // therefore used to gate eligibility.
   if (InProgressDownloadsEligibleClient())
-    download->has_is_mixed_content = true;
+    download->has_is_insecure = true;
 
   // Notify observers of `download` creation.
   download->state = crosapi::mojom::DownloadState::kInProgress;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
index 25cc4a3..1052205 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
@@ -1624,12 +1624,12 @@
   }
 
   // Updates whether the specified `in_progress_download` of the appropriate
-  // type for Ash or Lacros given test parameterization is dangerous, mixed
-  // content, or might be malicious.
-  void UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  // type for Ash or Lacros given test parameterization is dangerous, insecure,
+  // or might be malicious.
+  void UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       AshOrLacrosDownload* in_progress_download,
       bool is_dangerous,
-      bool is_mixed_content,
+      bool is_insecure,
       bool might_be_malicious) {
     ASSERT_TRUE(is_dangerous || !might_be_malicious);
     switch (GetDownloadTypeToUse()) {
@@ -1647,8 +1647,8 @@
                           DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
         ON_CALL(*in_progress_ash_download, IsDangerous())
             .WillByDefault(testing::Return(is_dangerous));
-        ON_CALL(*in_progress_ash_download, IsMixedContent())
-            .WillByDefault(testing::Return(is_mixed_content));
+        ON_CALL(*in_progress_ash_download, IsInsecure())
+            .WillByDefault(testing::Return(is_insecure));
         NotifyObserversAshDownloadUpdated(in_progress_ash_download.get());
         return;
       }
@@ -1663,7 +1663,7 @@
                          : crosapi::mojom::DownloadDangerType::
                                kDownloadDangerTypeNotDangerous;
         in_progress_lacros_download->is_dangerous = is_dangerous;
-        in_progress_lacros_download->is_mixed_content = is_mixed_content;
+        in_progress_lacros_download->is_insecure = is_insecure;
         NotifyObserversLacrosDownloadUpdated(in_progress_lacros_download.get());
         return;
       }
@@ -1951,8 +1951,8 @@
     lacros_download_item->start_time = base::Time::Now();
     lacros_download_item->is_dangerous = false;
     lacros_download_item->has_is_dangerous = true;
-    lacros_download_item->is_mixed_content = false;
-    lacros_download_item->has_is_mixed_content = true;
+    lacros_download_item->is_insecure = false;
+    lacros_download_item->has_is_insecure = true;
 
     auto* const download_controller_ash = crosapi::CrosapiManager::Get()
                                               ->crosapi_ash()
@@ -2229,10 +2229,10 @@
             base::UTF16ToUTF8(u"Download paused " + target_file_name));
 
   // Mark the download as dangerous.
-  UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       in_progress_download.get(),
       /*is_dangerous=*/true,
-      /*is_mixed_content=*/false,
+      /*is_insecure=*/false,
       /*might_be_malicious=*/true);
 
   // Because the download is marked as dangerous, that should be indicated in
@@ -2265,9 +2265,9 @@
             base::UTF16ToUTF8(u"Download scanning " + target_file_name));
 
   // Stop scanning and mark that the download is *not* malicious.
-  UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       in_progress_download.get(), /*is_dangerous=*/true,
-      /*is_mixed_content=*/false, /*might_be_malicious=*/false);
+      /*is_insecure=*/false, /*might_be_malicious=*/false);
 
   // Because the download is *not* malicious, the user will be able to keep/
   // discard the download via notification. That should be indicated in the
@@ -2284,10 +2284,10 @@
             base::UTF16ToUTF8(u"Confirm download " + target_file_name));
 
   // Mark the download as safe.
-  UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       in_progress_download.get(),
       /*is_dangerous=*/false,
-      /*is_mixed_content=*/false,
+      /*is_insecure=*/false,
       /*might_be_malicious=*/false);
 
   // Because the download is no longer marked as dangerous, that should be
@@ -2304,14 +2304,14 @@
   EXPECT_EQ(GetAccessibleName(download_chips.at(0)),
             base::UTF16ToUTF8(u"Download paused " + target_file_name));
 
-  // Mark the download as mixed content.
-  UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  // Mark the download as insecure.
+  UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       in_progress_download.get(),
       /*is_dangerous=*/false,
-      /*is_mixed_content=*/true,
+      /*is_insecure=*/true,
       /*might_be_malicious=*/false);
 
-  // Because the download is marked as mixed content, that should be indicated
+  // Because the download is marked as insecure, that should be indicated
   // in the `secondary_label` of the holding space item chip view.
   EXPECT_TRUE(primary_label->GetVisible());
   EXPECT_EQ(primary_label->GetText(), target_file_name);
@@ -2324,14 +2324,14 @@
   EXPECT_EQ(GetAccessibleName(download_chips.at(0)),
             base::UTF16ToUTF8(u"Download dangerous " + target_file_name));
 
-  // Mark the download as *not* mixed content.
-  UpdateInProgressDownloadIsDangerousMixedContentOrMightBeMalicious(
+  // Mark the download as *not* insecure.
+  UpdateInProgressDownloadIsDangerousInsecureOrMightBeMalicious(
       in_progress_download.get(),
       /*is_dangerous=*/false,
-      /*is_mixed_content=*/false,
+      /*is_insecure=*/false,
       /*might_be_malicious=*/false);
 
-  // Because the download is no longer marked as mixed content, that should be
+  // Because the download is no longer marked as insecure, that should be
   // indicated in the `secondary_label` of the holding space item chip view.
   EXPECT_TRUE(primary_label->GetVisible());
   EXPECT_EQ(primary_label->GetText(), target_file_name);
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.cc b/chrome/browser/ui/ash/projector/projector_client_impl.cc
index fe1bcae..11d7722 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.cc
@@ -70,16 +70,11 @@
   if (session_manager)
     session_observation_.Observe(session_manager);
 
-  if (!base::FeatureList::IsEnabled(
-          ash::features::kOnDeviceSpeechRecognition)) {
-    controller_->OnSpeechRecognitionAvailabilityChanged(
-        ash::SpeechRecognitionAvailability::
-            kOnDeviceSpeechRecognitionNotSupported);
-    return;
+  if (base::FeatureList::IsEnabled(ash::features::kOnDeviceSpeechRecognition)) {
+    soda_installation_controller_ =
+        std::make_unique<ProjectorSodaInstallationController>(
+            ash::ProjectorAppClient::Get(), controller_);
   }
-  soda_installation_controller_ =
-      std::make_unique<ProjectorSodaInstallationController>(
-          ash::ProjectorAppClient::Get(), controller_);
 }
 
 ProjectorClientImpl::ProjectorClientImpl()
@@ -89,11 +84,41 @@
   controller_->SetClient(nullptr);
 }
 
+// Projector prioritizes on-device speech recognition over server
+// based speech recognition.
+ash::SpeechRecognitionAvailability
+ProjectorClientImpl::GetSpeechRecognitionAvailability() const {
+  const auto& locale = GetLocale();
+  if (ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev()) {
+    return SpeechRecognitionRecognizerClientImpl::
+        GetServerBasedRecognitionAvailability(locale);
+  }
+
+  const auto on_device_availability = SpeechRecognitionRecognizerClientImpl::
+      GetOnDeviceSpeechRecognitionAvailability(locale);
+  if (on_device_availability ==
+      ash::SpeechRecognitionAvailability::kSodaAvailable) {
+    return on_device_availability;
+  }
+
+  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;
+}
+
 void ProjectorClientImpl::StartSpeechRecognition() {
-  // TODO(b/245613717): Use SpeechRecognitionRecognizerClient factory to
-  // handle the creation of the recognizer.
-  bool should_use_server_based =
-      ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev();
+  const auto availability = GetSpeechRecognitionAvailability();
+  DCHECK(ash::ProjectorController::IsRecognitionAvailable(availability));
+  const bool should_use_server_based =
+      availability ==
+      ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable;
 
   DCHECK_EQ(speech_recognizer_.get(), nullptr);
   recognizer_status_ = SPEECH_RECOGNIZER_OFF;
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.h b/chrome/browser/ui/ash/projector/projector_client_impl.h
index 254f80a..4931a89 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.h
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/projector/projector_annotator_controller.h"
 #include "ash/public/cpp/projector/projector_client.h"
 #include "ash/public/cpp/projector/projector_controller.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
@@ -46,6 +47,8 @@
   ~ProjectorClientImpl() override;
 
   // ash::ProjectorClient:
+  ash::SpeechRecognitionAvailability GetSpeechRecognitionAvailability()
+      const override;
   void StartSpeechRecognition() override;
   void StopSpeechRecognition() override;
   bool GetBaseStoragePath(base::FilePath* result) const override;
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 e8df7f9..38a8846d 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
 #include "ash/webui/projector_app/test/mock_app_client.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/branding_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/speech/cros_speech_recognition_service_factory.h"
@@ -29,6 +30,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
+
 namespace ash {
 
 namespace {
@@ -36,8 +38,7 @@
 const char kFirstSpeechResult[] = "the brown fox";
 const char kSecondSpeechResult[] = "the brown fox jumped over the lazy dog";
 
-const char kEnglishLocale[] = "en-US";
-
+const char kEnglishUS[] = "en-US";
 inline void SetLocale(const std::string& locale) {
   g_browser_process->SetApplicationLocale(locale);
 }
@@ -76,14 +77,22 @@
   MOCK_METHOD1(RemoveObserver, void(ash::LocaleChangeObserver*));
 };
 
+struct ProjectorClientTestScenario {
+  const std::vector<base::test::FeatureRef> enabled_features;
+  const std::vector<base::test::FeatureRef> disabled_features;
+
+  ProjectorClientTestScenario(
+      const std::vector<base::test::FeatureRef>& enabled,
+      const std::vector<base::test::FeatureRef>& disabled)
+      : enabled_features(enabled), disabled_features(disabled) {}
+};
+
 }  // namespace
 
-class ProjectorClientImplUnitTest : public testing::Test {
+class ProjectorClientImplUnitTest
+    : public testing::TestWithParam<ProjectorClientTestScenario> {
  public:
-  ProjectorClientImplUnitTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kProjector, features::kOnDeviceSpeechRecognition}, {});
-  }
+  ProjectorClientImplUnitTest() = default;
 
   ProjectorClientImplUnitTest(const ProjectorClientImplUnitTest&) = delete;
   ProjectorClientImplUnitTest& operator=(const ProjectorClientImplUnitTest&) =
@@ -98,9 +107,11 @@
 
   ProjectorClient* client() { return projector_client_.get(); }
 
-  // testing::Test:
   void SetUp() override {
-    testing::Test::SetUp();
+    const auto& parameter = GetParam();
+    scoped_feature_list_.InitWithFeatures(parameter.enabled_features,
+                                          parameter.disabled_features);
+
     ASSERT_TRUE(testing_profile_manager_.SetUp());
     testing_profile_ = ProfileManager::GetPrimaryUserProfile();
     ASSERT_TRUE(testing_profile_);
@@ -111,8 +122,10 @@
             base::BindRepeating(&ProjectorClientImplUnitTest::
                                     CreateTestSpeechRecognitionService,
                                 base::Unretained(this)));
-    SetLocale(kEnglishLocale);
+    SetLocale(kEnglishUS);
     soda_installer_ = std::make_unique<MockSodaInstaller>();
+    ON_CALL(*soda_installer_, GetAvailableLanguages)
+        .WillByDefault(testing::Return(std::vector<std::string>({kEnglishUS})));
     soda_installer_->NotifySodaInstalledForTesting();
     soda_installer_->NotifySodaInstalledForTesting(speech::LanguageCode::kEnUs);
     mock_app_client_ = std::make_unique<MockAppClient>();
@@ -169,7 +182,7 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(ProjectorClientImplUnitTest, SpeechRecognitionResults) {
+TEST_P(ProjectorClientImplUnitTest, SpeechRecognitionResults) {
   client()->StartSpeechRecognition();
   fake_service_->WaitForRecognitionStarted();
 
@@ -187,4 +200,63 @@
   SendTranscriptionError();
 }
 
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+namespace {
+
+const char kFrench[] = "fr";
+const char kUnsupportedLanguage[] = "am";
+
+}  // namespace
+
+TEST_P(ProjectorClientImplUnitTest, SpeechRecognitionAvailability) {
+  const bool force_enable_server_based =
+      features::ShouldForceEnableServerSideSpeechRecognitionForDev();
+  const bool server_based_available =
+      features::IsInternalServerSideSpeechRecognitionEnabled();
+
+  SetLocale(kFrench);
+  if (server_based_available) {
+    EXPECT_EQ(
+        projector_client_->GetSpeechRecognitionAvailability(),
+        ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable);
+  } else {
+    EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
+              ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable);
+  }
+
+  SetLocale(kEnglishUS);
+  if (force_enable_server_based && server_based_available) {
+    EXPECT_EQ(
+        projector_client_->GetSpeechRecognitionAvailability(),
+        ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable);
+  } else {
+    EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
+              ash::SpeechRecognitionAvailability::kSodaAvailable);
+  }
+
+  SetLocale(kUnsupportedLanguage);
+  EXPECT_EQ(projector_client_->GetSpeechRecognitionAvailability(),
+            ash::SpeechRecognitionAvailability::kUserLanguageNotAvailable);
+}
+
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+INSTANTIATE_TEST_SUITE_P(
+    ProjectorClientTestScenarios,
+    ProjectorClientImplUnitTest,
+    ::testing::Values(
+        ProjectorClientTestScenario({features::kProjector,
+                                     features::kOnDeviceSpeechRecognition},
+                                    {}),
+        ProjectorClientTestScenario(
+            {features::kProjector, features::kOnDeviceSpeechRecognition,
+             features::kForceEnableServerSideSpeechRecognitionForDev},
+            {}),
+        ProjectorClientTestScenario(
+            {features::kProjector,
+             features::kInternalServerSideSpeechRecognition,
+             features::kOnDeviceSpeechRecognition},
+            {features::kForceEnableServerSideSpeechRecognitionForDev})));
+
 }  // namespace ash
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 ea1e3b3..c791583 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/projector/projector_controller.h"
+#include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "ash/webui/projector_app/projector_app_client.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
@@ -39,16 +40,6 @@
     : app_client_(client), projector_controller_(projector_controller) {
   soda_installer_observation_.Observe(speech::SodaInstaller::GetInstance());
   locale_change_observation_.Observe(ash::LocaleUpdateController::Get());
-
-  if (!SpeechRecognitionRecognizerClientImpl::
-          IsOnDeviceSpeechRecognizerAvailable(GetLocale())) {
-    projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-        ash::SpeechRecognitionAvailability::kSodaNotInstalled);
-    return;
-  }
-
-  projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-      ash::SpeechRecognitionAvailability::kAvailable);
 }
 
 ProjectorSodaInstallationController::~ProjectorSodaInstallationController() =
@@ -86,38 +77,27 @@
   // Check that language code matches the selected language for projector.
   if (language_code != speech::GetLanguageCode(GetLocale()))
     return;
-  projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-      ash::SpeechRecognitionAvailability::kAvailable);
+  projector_controller_->OnSpeechRecognitionAvailabilityChanged();
   app_client_->OnSodaInstalled();
 }
 
 void ProjectorSodaInstallationController::OnSodaInstallError(
     speech::LanguageCode language_code,
     speech::SodaInstaller::ErrorCode error_code) {
+  const auto& current_locale = GetLocale();
   // Check that language code matches the selected language for projector or is
   // LanguageCode::kNone (signifying the SODA binary failed).
-  if (language_code != speech::GetLanguageCode(GetLocale()) &&
+  if (language_code != speech::GetLanguageCode(current_locale) &&
       language_code != speech::LanguageCode::kNone) {
     return;
   }
+  projector_controller_->OnSpeechRecognitionAvailabilityChanged();
 
-  switch (error_code) {
-    case speech::SodaInstaller::ErrorCode::kUnspecifiedError:
-      projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-          ash::SpeechRecognitionAvailability::
-              kSodaInstallationErrorUnspecified);
-      break;
-    case speech::SodaInstaller::ErrorCode::kNeedsReboot:
-      projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-          ash::SpeechRecognitionAvailability::
-              kSodaInstallationErrorNeedsReboot);
-      break;
-  }
-  // TODO(b/245616124): When there is a SODA installation error,
-  // notify the client of the error only when server side recognition is
-  // not available.
-  if (!ash::features::ShouldForceEnableServerSideSpeechRecognitionForDev())
+  if (SpeechRecognitionRecognizerClientImpl::
+          GetServerBasedRecognitionAvailability(current_locale) !=
+      ash::SpeechRecognitionAvailability::kServerBasedRecognitionAvailable) {
     app_client_->OnSodaInstallError();
+  }
 }
 
 void ProjectorSodaInstallationController::OnSodaProgress(
@@ -135,7 +115,6 @@
 // This function is triggered after every sign in.
 void ProjectorSodaInstallationController::OnLocaleChanged() {
   if (!IsLanguageSupported(speech::GetLanguageCode(GetLocale()))) {
-    projector_controller_->OnSpeechRecognitionAvailabilityChanged(
-        ash::SpeechRecognitionAvailability::kUserLanguageNotSupported);
+    projector_controller_->OnSpeechRecognitionAvailabilityChanged();
   }
 }
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 31143d54..a4216a80 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
@@ -15,6 +15,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/speech/speech_recognition_recognizer_client_impl.h"
 #include "chrome/test/base/chrome_ash_test_base.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -62,7 +63,8 @@
             features::kOnDeviceSpeechRecognition,
             features::kProjector,
         },
-        {});
+        {features::kInternalServerSideSpeechRecognition,
+         features::kForceEnableServerSideSpeechRecognitionForDev});
   }
   ProjectorSodaInstallationControllerTest(
       const ProjectorSodaInstallationControllerTest&) = delete;
@@ -83,6 +85,15 @@
             testing::Return(std::vector<std::string>({kEnglishLocale})));
 
     mock_client_ = std::make_unique<MockProjectorClient>();
+
+    ON_CALL(*mock_client_, GetSpeechRecognitionAvailability)
+        .WillByDefault(
+            testing::Invoke([&]() -> ash::SpeechRecognitionAvailability {
+              return SpeechRecognitionRecognizerClientImpl::
+                  GetOnDeviceSpeechRecognitionAvailability(
+                      g_browser_process->GetApplicationLocale());
+            }));
+
     projector_controller().SetClient(mock_client_.get());
     mock_app_client_ = std::make_unique<MockAppClient>();
 
diff --git a/chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.cc b/chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.cc
index 2677aef..45171e9 100644
--- a/chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.cc
+++ b/chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.cc
@@ -104,7 +104,7 @@
   DCHECK(provider);
 
   web_app::DisplayMode display_mode =
-      provider->registrar().GetAppEffectiveDisplayMode(app_id.value());
+      provider->registrar_unsafe().GetAppEffectiveDisplayMode(app_id.value());
 
   // TODO(crbug/1113502): Plumb through better launch sources from callsites.
   apps::AppLaunchParams params = apps::CreateAppIdLaunchParamsWithEventFlags(
@@ -200,7 +200,7 @@
   auto* system_app = swa_manager->GetSystemApp(app_type);
 
 #if BUILDFLAG(IS_CHROMEOS)
-  DCHECK(url.DeprecatedGetOriginAsURL() == provider->registrar()
+  DCHECK(url.DeprecatedGetOriginAsURL() == provider->registrar_unsafe()
                                                .GetAppLaunchUrl(params.app_id)
                                                .DeprecatedGetOriginAsURL() ||
          system_app && system_app->IsUrlInSystemAppScope(url));
@@ -259,7 +259,7 @@
   auto* provider = SystemWebAppManager::GetWebAppProvider(profile);
   DCHECK(provider);
 
-  if (!provider->registrar().IsInstalled(app_id.value()))
+  if (!provider->registrar_unsafe().IsInstalled(app_id.value()))
     return nullptr;
 
   // Look through all the windows, find a browser for this app. Prefer the most
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.cc b/chrome/browser/ui/ash/test_wallpaper_controller.cc
index c8b9c1bc..5f000d58 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.cc
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.cc
@@ -132,13 +132,6 @@
   NOTIMPLEMENTED();
 }
 
-void TestWallpaperController::SetOnlineWallpaperFromData(
-    const ash::OnlineWallpaperParams& params,
-    const std::string& image_data,
-    SetWallpaperCallback callback) {
-  NOTIMPLEMENTED();
-}
-
 void TestWallpaperController::SetDefaultWallpaper(
     const AccountId& account_id,
     bool show_wallpaper,
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.h b/chrome/browser/ui/ash/test_wallpaper_controller.h
index 52341245..b372f23 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.h
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.h
@@ -98,9 +98,6 @@
                           SetWallpaperCallback callback) override;
   void SetOnlineWallpaperIfExists(const ash::OnlineWallpaperParams& params,
                                   SetWallpaperCallback callback) override;
-  void SetOnlineWallpaperFromData(const ash::OnlineWallpaperParams& params,
-                                  const std::string& image_data,
-                                  SetWallpaperCallback callback) override;
   void SetGooglePhotosWallpaper(const ash::GooglePhotosWallpaperParams& params,
                                 SetWallpaperCallback callback) override;
   void SetGooglePhotosDailyRefreshAlbumId(const AccountId& account_id,
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
index 40b1b267..0e36782 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -348,16 +348,6 @@
                                                     std::move(callback));
 }
 
-void WallpaperControllerClientImpl::SetOnlineWallpaperFromData(
-    const ash::OnlineWallpaperParams& params,
-    const std::string& image_data,
-    ash::WallpaperController::SetWallpaperCallback callback) {
-  if (!IsKnownUser(params.account_id))
-    return;
-  wallpaper_controller_->SetOnlineWallpaperFromData(params, image_data,
-                                                    std::move(callback));
-}
-
 void WallpaperControllerClientImpl::SetCustomizedDefaultWallpaperPaths(
     const base::FilePath& customized_default_small_path,
     const base::FilePath& customized_default_large_path) {
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
index fbfba40..e912620 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
@@ -116,10 +116,6 @@
   void SetOnlineWallpaperIfExists(
       const ash::OnlineWallpaperParams& params,
       ash::WallpaperController::SetWallpaperCallback callback);
-  void SetOnlineWallpaperFromData(
-      const ash::OnlineWallpaperParams& params,
-      const std::string& image_data,
-      ash::WallpaperController::SetWallpaperCallback callback);
   void SetCustomizedDefaultWallpaperPaths(
       const base::FilePath& customized_default_small_path,
       const base::FilePath& customized_default_large_path);
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index 8e9d5a7..4086a03d 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -1504,9 +1504,15 @@
   }
 }
 
+// TODO(crbug.com/1394195) This test is flaky on asan lacros and may crash ash.
+#if BUILDFLAG(IS_CHROMEOS_LACROS) && defined(ADDRESS_SANITIZER)
+#define MAYBE_StartMinimized DISABLED_StartMinimized
+#else
+#define MAYBE_StartMinimized StartMinimized
+#endif
 // Makes sure the browser doesn't crash when
 // set_show_state(ui::SHOW_STATE_MINIMIZED) has been invoked.
-IN_PROC_BROWSER_TEST_F(BrowserTest, StartMinimized) {
+IN_PROC_BROWSER_TEST_F(BrowserTest, MAYBE_StartMinimized) {
   Browser::CreateParams params[] = {
     Browser::CreateParams(Browser::TYPE_NORMAL, browser()->profile(), true),
     Browser::CreateParams(Browser::TYPE_POPUP, browser()->profile(), true),
diff --git a/chrome/browser/ui/download/download_item_mode.cc b/chrome/browser/ui/download/download_item_mode.cc
index e57e8d8eb..d833b51 100644
--- a/chrome/browser/ui/download/download_item_mode.cc
+++ b/chrome/browser/ui/download/download_item_mode.cc
@@ -9,11 +9,11 @@
 namespace download {
 
 DownloadItemMode GetDesiredDownloadItemMode(DownloadUIModel* download) {
-  if (download->IsMixedContent()) {
-    const bool warn = download->GetMixedContentStatus() ==
-                      download::DownloadItem::MixedContentStatus::WARN;
-    return warn ? DownloadItemMode::kMixedContentWarn
-                : DownloadItemMode::kMixedContentBlock;
+  if (download->IsInsecure()) {
+    const bool warn = download->GetInsecureDownloadStatus() ==
+                      download::DownloadItem::InsecureDownloadStatus::WARN;
+    return warn ? DownloadItemMode::kInsecureDownloadWarn
+                : DownloadItemMode::kInsecureDownloadBlock;
   }
 
   if (download->IsDangerous() &&
diff --git a/chrome/browser/ui/download/download_item_mode.h b/chrome/browser/ui/download/download_item_mode.h
index d1d5d4a..89848b7 100644
--- a/chrome/browser/ui/download/download_item_mode.h
+++ b/chrome/browser/ui/download/download_item_mode.h
@@ -11,12 +11,12 @@
 
 // Security UI mode of a download item.
 enum class DownloadItemMode {
-  kNormal,             // Showing download item.
-  kDangerous,          // Displaying the dangerous download warning.
-  kMalicious,          // Displaying the malicious download warning.
-  kMixedContentWarn,   // Displaying the mixed-content download warning.
-  kMixedContentBlock,  // Displaying the mixed-content download block error.
-  kDeepScanning,       // Displaying in-progress deep scanning information.
+  kNormal,                 // Showing download item.
+  kDangerous,              // Displaying the dangerous download warning.
+  kMalicious,              // Displaying the malicious download warning.
+  kInsecureDownloadWarn,   // Displaying the insecure download warning.
+  kInsecureDownloadBlock,  // Displaying the insecure download block error.
+  kDeepScanning,           // Displaying in-progress deep scanning information.
 };
 
 // Returns the mode that best reflects the current model state.
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java
index 59349929..547cf36 100644
--- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java
+++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java
@@ -120,8 +120,9 @@
     }
 
     public void dismiss() {
-        // Disable action button during animation.
-        mActionButtonView.setEnabled(false);
+        // Prevent clicks during dismissal animations. Intentionally not using setEnabled(false) to
+        // avoid unnecessary text color changes in this transitory state.
+        mActionButtonView.setOnClickListener(null);
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.setDuration(mAnimationDuration);
         animatorSet.addListener(new AnimatorListenerAdapter() {
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.cc b/chrome/browser/ui/tabs/tab_renderer_data.cc
index 5aa1d17..b6451b6 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.cc
+++ b/chrome/browser/ui/tabs/tab_renderer_data.cc
@@ -42,8 +42,10 @@
   data.favicon = tab_ui_helper->GetFavicon().AsImageSkia();
   ThumbnailTabHelper* const thumbnail_tab_helper =
       ThumbnailTabHelper::FromWebContents(contents);
-  if (thumbnail_tab_helper)
+  if (thumbnail_tab_helper) {
     data.thumbnail = thumbnail_tab_helper->thumbnail();
+    data.is_tab_discarded = thumbnail_tab_helper->is_tab_discarded();
+  }
   data.network_state = TabNetworkStateForWebContents(contents);
   data.title = tab_ui_helper->GetTitle();
   data.visible_url = contents->GetVisibleURL();
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.h b/chrome/browser/ui/tabs/tab_renderer_data.h
index 3e0e9c6..ccd8e833 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.h
+++ b/chrome/browser/ui/tabs/tab_renderer_data.h
@@ -56,6 +56,7 @@
   bool should_hide_throbber = false;
   bool should_render_empty_title = false;
   bool should_themify_favicon = false;
+  bool is_tab_discarded = false;
 };
 
 #endif  // CHROME_BROWSER_UI_TABS_TAB_RENDERER_DATA_H_
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index cd77b45c..4d63cea 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -253,7 +253,9 @@
               base::Unretained(this)))),
       thumbnail_(base::MakeRefCounted<ThumbnailImage>(
           state_.get(),
-          DiscardedTabThumbnailData::TakeThumbnailDataIfAvailable(contents))) {}
+          DiscardedTabThumbnailData::TakeThumbnailDataIfAvailable(contents))) {
+  is_tab_discarded_ = contents->WasDiscarded();
+}
 
 ThumbnailTabHelper::~ThumbnailTabHelper() {
   StopVideoCapture();
@@ -412,4 +414,9 @@
                                                   thumbnail_->data());
 }
 
+void ThumbnailTabHelper::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  is_tab_discarded_ = false;
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(ThumbnailTabHelper);
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
index 152d6ab..771fe54 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.h
@@ -31,6 +31,8 @@
 
   scoped_refptr<ThumbnailImage> thumbnail() const { return thumbnail_; }
 
+  bool is_tab_discarded() const { return is_tab_discarded_; }
+
  private:
   class TabStateTracker;
   friend class content::WebContentsUserData<ThumbnailTabHelper>;
@@ -78,6 +80,9 @@
 
   void AboutToBeDiscarded(content::WebContents* new_contents) override;
 
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
   // Copy info from the most recent frame we have captured.
   ThumbnailCaptureInfo last_frame_capture_info_;
 
@@ -95,6 +100,8 @@
   // The thumbnail maintained by this instance.
   scoped_refptr<ThumbnailImage> thumbnail_;
 
+  bool is_tab_discarded_ = false;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
   base::WeakPtrFactory<ThumbnailTabHelper>
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 1a2673a..c1a5924 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -170,7 +170,7 @@
 // Enables buttons when scrolling the tabstrip https://crbug.com/951078
 BASE_FEATURE(kTabScrollingButtonPosition,
              "TabScrollingButtonPosition",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 const char kTabScrollingButtonPositionParameterName[] = "buttonPosition";
 
 // Enables tab scrolling while dragging tabs in tabstrip
diff --git a/chrome/browser/ui/views/controls/page_switcher_view.h b/chrome/browser/ui/views/controls/page_switcher_view.h
index 29301461..38bd7a55 100644
--- a/chrome/browser/ui/views/controls/page_switcher_view.h
+++ b/chrome/browser/ui/views/controls/page_switcher_view.h
@@ -28,4 +28,9 @@
   raw_ptr<views::View, DanglingUntriaged> current_page_ = nullptr;
 };
 
+BEGIN_VIEW_BUILDER(/* no export */, PageSwitcherView, views::View)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* no export */, PageSwitcherView)
+
 #endif  // CHROME_BROWSER_UI_VIEWS_CONTROLS_PAGE_SWITCHER_VIEW_H_
diff --git a/chrome/browser/ui/views/download/download_danger_prompt_views.cc b/chrome/browser/ui/views/download/download_danger_prompt_views.cc
index 3ee5c91..97c6985 100644
--- a/chrome/browser/ui/views/download/download_danger_prompt_views.cc
+++ b/chrome/browser/ui/views/download/download_danger_prompt_views.cc
@@ -234,10 +234,10 @@
       }
     }
   } else {
-    // If we're mixed content, we show that warning first.
-    if (download_->IsMixedContent()) {
+    // If we're insecurely downloading, show a warning first.
+    if (download_->IsInsecure()) {
       return l10n_util::GetStringFUTF16(
-          IDS_PROMPT_CONFIRM_MIXED_CONTENT_DOWNLOAD,
+          IDS_PROMPT_CONFIRM_INSECURE_DOWNLOAD,
           download_->GetFileNameToReportUser().LossyDisplayName());
     }
     switch (download_->GetDangerType()) {
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 3206556b..75a714d2 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -212,15 +212,15 @@
          (mode == download::DownloadItemMode::kMalicious);
 }
 
-// Whether we are in the mixed content mode.
-bool is_mixed_content(download::DownloadItemMode mode) {
-  return (mode == download::DownloadItemMode::kMixedContentWarn) ||
-         (mode == download::DownloadItemMode::kMixedContentBlock);
+// Whether we are in the insecure download mode.
+bool is_insecure(download::DownloadItemMode mode) {
+  return (mode == download::DownloadItemMode::kInsecureDownloadWarn) ||
+         (mode == download::DownloadItemMode::kInsecureDownloadBlock);
 }
 
 // Whether a warning label is visible.
 bool has_warning_label(download::DownloadItemMode mode) {
-  return is_download_warning(mode) || is_mixed_content(mode);
+  return is_download_warning(mode) || is_insecure(mode);
 }
 
 float GetDPIScaleForView(views::View* view) {
@@ -737,10 +737,10 @@
       UpdateAccessibleAlert(model_->GetWarningText(unelided_filename, &ignore));
       accessible_alert_timer_.Stop();
     }
-  } else if (is_mixed_content(mode_)) {
+  } else if (is_insecure(mode_)) {
     announce_accessible_alert_soon_ = true;
     UpdateAccessibleAlert(l10n_util::GetStringFUTF16(
-        IDS_PROMPT_DOWNLOAD_MIXED_CONTENT_BLOCKED_ACCESSIBLE_ALERT,
+        IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT,
         unelided_filename));
   } else if (mode_ == download::DownloadItemMode::kDeepScanning) {
     UpdateAccessibleAlert(l10n_util::GetStringFUTF16(
@@ -855,11 +855,11 @@
 
   save_button_->SetVisible(
       (mode_ == download::DownloadItemMode::kDangerous) ||
-      (mode_ == download::DownloadItemMode::kMixedContentWarn));
+      (mode_ == download::DownloadItemMode::kInsecureDownloadWarn));
   save_button_->SetText(model_->GetWarningConfirmButtonText());
 
   discard_button_->SetVisible(
-      (mode_ == download::DownloadItemMode::kMixedContentBlock) ||
+      (mode_ == download::DownloadItemMode::kInsecureDownloadBlock) ||
       prompt_to_discard);
   scan_button_->SetVisible(prompt_to_scan);
   review_button_->SetVisible(prompt_to_review);
@@ -1072,20 +1072,20 @@
       break;
   }
 
-  switch (model_->GetMixedContentStatus()) {
-    case download::DownloadItem::MixedContentStatus::BLOCK:
+  switch (model_->GetInsecureDownloadStatus()) {
+    case download::DownloadItem::InsecureDownloadStatus::BLOCK:
       return kError;
-    case download::DownloadItem::MixedContentStatus::WARN:
+    case download::DownloadItem::InsecureDownloadStatus::WARN:
       return kWarning;
-    case download::DownloadItem::MixedContentStatus::UNKNOWN:
-    case download::DownloadItem::MixedContentStatus::SAFE:
-    case download::DownloadItem::MixedContentStatus::VALIDATED:
-    case download::DownloadItem::MixedContentStatus::SILENT_BLOCK:
+    case download::DownloadItem::InsecureDownloadStatus::UNKNOWN:
+    case download::DownloadItem::InsecureDownloadStatus::SAFE:
+    case download::DownloadItem::InsecureDownloadStatus::VALIDATED:
+    case download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
       break;
   }
 
   LOG(ERROR) << "Unexpected danger type " << danger_type
-             << " or mixed content status " << model_->GetMixedContentStatus();
+             << " or insecure status " << model_->GetInsecureDownloadStatus();
   return kInfo;
 }
 
@@ -1326,10 +1326,10 @@
                        {download::DownloadItemMode::kNormal, u"kNormal"},
                        {download::DownloadItemMode::kDangerous, u"kDangerous"},
                        {download::DownloadItemMode::kMalicious, u"kMalicious"},
-                       {download::DownloadItemMode::kMixedContentWarn,
-                        u"kMixedContentWarn"},
-                       {download::DownloadItemMode::kMixedContentBlock,
-                        u"kMixedContentBlock"})
+                       {download::DownloadItemMode::kInsecureDownloadWarn,
+                        u"kInsecureDownloadWarn"},
+                       {download::DownloadItemMode::kInsecureDownloadBlock,
+                        u"kInsecureDownloadBlock"})
 
 BEGIN_METADATA(DownloadItemView, views::View)
 ADD_READONLY_PROPERTY_METADATA(download::DownloadItemMode, Mode)
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_base_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_base_view.cc
new file mode 100644
index 0000000..57f14ae4
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extensions_menu_base_view.cc
@@ -0,0 +1,29 @@
+// 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/views/extensions/extensions_menu_base_view.h"
+
+#include "chrome/browser/ui/views/controls/page_switcher_view.h"
+#include "chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+ExtensionsMenuBaseView::ExtensionsMenuBaseView(Browser* browser) {
+  auto initial_page =
+      std::make_unique<ExtensionsMenuMainPageView>(browser, this);
+
+  views::Builder<ExtensionsMenuBaseView>(this)
+      .SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical))
+      .AddChildren(
+          views::Builder<PageSwitcherView>(
+              std::make_unique<PageSwitcherView>(std::move(initial_page)))
+              .CopyAddressTo(&page_container_))
+      .BuildChildren();
+}
+
+void ExtensionsMenuBaseView::CloseBubble() {
+  GetWidget()->CloseWithReason(
+      views::Widget::ClosedReason::kCloseButtonClicked);
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_base_view.h b/chrome/browser/ui/views/extensions/extensions_menu_base_view.h
new file mode 100644
index 0000000..2a374d2
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extensions_menu_base_view.h
@@ -0,0 +1,36 @@
+// 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_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_BASE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_BASE_VIEW_H_
+
+#include "chrome/browser/ui/views/controls/page_switcher_view.h"
+#include "chrome/browser/ui/views/extensions/extensions_menu_navigation_handler.h"
+#include "ui/views/view.h"
+
+class Browser;
+
+// The views implementation of the extensions menu UI.
+class ExtensionsMenuBaseView : public views::View,
+                               public ExtensionsMenuNavigationHandler {
+ public:
+  explicit ExtensionsMenuBaseView(Browser* browser);
+  ~ExtensionsMenuBaseView() override = default;
+  ExtensionsMenuBaseView(const ExtensionsMenuBaseView&) = delete;
+  const ExtensionsMenuBaseView& operator=(const ExtensionsMenuBaseView&) =
+      delete;
+
+  // ExtensionsMenuNavigationHandler:
+  void CloseBubble() override;
+
+ private:
+  raw_ptr<PageSwitcherView> page_container_;
+};
+
+BEGIN_VIEW_BUILDER(/* no export */, ExtensionsMenuBaseView, views::View)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* no export */, ExtensionsMenuBaseView)
+
+#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_BASE_VIEW_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_coordinator.cc b/chrome/browser/ui/views/extensions/extensions_menu_coordinator.cc
index ecd4a6f..650f6b8 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_coordinator.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_coordinator.cc
@@ -7,13 +7,15 @@
 #include <memory>
 
 #include "base/feature_list.h"
+#include "chrome/browser/ui/views/extensions/extensions_menu_base_view.h"
 #include "extensions/common/extension_features.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/layout/layout_provider.h"
 #include "ui/views/view_tracker.h"
 #include "ui/views/widget/widget.h"
 
-ExtensionsMenuCoordinator::ExtensionsMenuCoordinator() = default;
+ExtensionsMenuCoordinator::ExtensionsMenuCoordinator(Browser* browser)
+    : browser_(browser) {}
 
 ExtensionsMenuCoordinator::~ExtensionsMenuCoordinator() {
   Hide();
@@ -31,13 +33,11 @@
   // Let anchor view's MenuButtonController handle the highlight.
   bubble_delegate->set_highlight_button_when_shown(false);
   bubble_delegate->SetButtons(ui::DIALOG_BUTTON_NONE);
-  bubble_delegate->SetShowCloseButton(true);
   bubble_delegate->SetEnableArrowKeyTraversal(true);
 
-  // TODO(crbug.com/1390952): Use "extensions menu base view" once it's created.
-  auto* contents_view =
-      bubble_delegate->SetContentsView(std::make_unique<views::View>());
-  extensions_menu_bubble_view_tracker_.SetView(contents_view);
+  auto* contents_view = bubble_delegate->SetContentsView(
+      std::make_unique<ExtensionsMenuBaseView>(browser_));
+  bubble_tracker_.SetView(contents_view);
 
   views::BubbleDialogDelegate::CreateBubble(std::move(bubble_delegate))->Show();
 }
@@ -50,15 +50,14 @@
     menu->Close();
     // Immediately stop tracking the view. Widget will be destroyed
     // asynchronously.
-    extensions_menu_bubble_view_tracker_.SetView(nullptr);
+    bubble_tracker_.SetView(nullptr);
   }
 }
 
 bool ExtensionsMenuCoordinator::IsShowing() const {
-  return !!extensions_menu_bubble_view_tracker_.view();
+  return bubble_tracker_.view() != nullptr;
 }
 
 views::Widget* ExtensionsMenuCoordinator::GetExtensionsMenuWidget() {
-  return IsShowing() ? extensions_menu_bubble_view_tracker_.view()->GetWidget()
-                     : nullptr;
+  return IsShowing() ? bubble_tracker_.view()->GetWidget() : nullptr;
 }
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_coordinator.h b/chrome/browser/ui/views/extensions/extensions_menu_coordinator.h
index d5f46d84..e83abe3 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_coordinator.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_coordinator.h
@@ -7,9 +7,12 @@
 
 #include "ui/views/view_tracker.h"
 
+class Browser;
+
+// Handles the lifetime and showing/hidden state of the extensions menu bubble.
 class ExtensionsMenuCoordinator {
  public:
-  ExtensionsMenuCoordinator();
+  explicit ExtensionsMenuCoordinator(Browser* browser);
   ExtensionsMenuCoordinator(const ExtensionsMenuCoordinator&) = delete;
   const ExtensionsMenuCoordinator& operator=(const ExtensionsMenuCoordinator&) =
       delete;
@@ -28,7 +31,8 @@
   views::Widget* GetExtensionsMenuWidget();
 
  private:
-  views::ViewTracker extensions_menu_bubble_view_tracker_;
+  raw_ptr<Browser> browser_;
+  views::ViewTracker bubble_tracker_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_COORDINATOR_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
new file mode 100644
index 0000000..0a2256ad
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -0,0 +1,115 @@
+// 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/views/extensions/extensions_menu_main_page_view.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/extensions/extensions_menu_navigation_handler.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/url_formatter/elide_url.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/flex_layout_view.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/style/typography.h"
+#include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
+
+namespace {
+
+// Returns the current site pointed by `web_contents`. This method should only
+// be called when web contents are present.
+std::u16string GetCurrentSite(content::WebContents* web_contents) {
+  DCHECK(web_contents);
+  const GURL& url = web_contents->GetLastCommittedURL();
+  return url_formatter::FormatUrlForDisplayOmitSchemePathAndTrivialSubdomains(
+      url);
+}
+
+}  // namespace
+
+ExtensionsMenuMainPageView::ExtensionsMenuMainPageView(
+    Browser* browser,
+    ExtensionsMenuNavigationHandler* navigation_handler)
+    : browser_(browser), navigation_handler_(navigation_handler) {
+  views::FlexSpecification stretch_specification =
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded,
+                               /*adjust_height_for_width =*/true)
+          .WithWeight(1);
+
+  views::Builder<ExtensionsMenuMainPageView>(this)
+      .SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical))
+      // TODO(crbug.com/1390952): Add margins after adding the menu
+      // items, to make sure all items are aligned.
+      .AddChildren(
+          views::Builder<views::FlexLayoutView>()
+              .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
+              .SetProperty(views::kFlexBehaviorKey, stretch_specification)
+              .AddChildren(
+                  views::Builder<views::FlexLayoutView>()
+                      .SetOrientation(views::LayoutOrientation::kVertical)
+                      .SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
+                      .SetProperty(views::kFlexBehaviorKey,
+                                   stretch_specification)
+                      .AddChildren(
+                          views::Builder<views::Label>()
+                              .SetText(l10n_util::GetStringUTF16(
+                                  IDS_EXTENSIONS_MENU_TITLE))
+                              .SetHorizontalAlignment(gfx::ALIGN_LEFT)
+                              .SetTextContext(
+                                  views::style::CONTEXT_DIALOG_TITLE)
+                              .SetTextStyle(views::style::STYLE_SECONDARY),
+                          views::Builder<views::Label>()
+                              .CopyAddressTo(&subheader_subtitle_)
+                              .SetText(GetCurrentSite(GetActiveWebContents()))
+                              .SetHorizontalAlignment(gfx::ALIGN_LEFT)
+                              .SetTextContext(views::style::CONTEXT_LABEL)
+                              .SetTextStyle(views::style::STYLE_SECONDARY)
+                              .SetAllowCharacterBreak(true)
+                              .SetMultiLine(true)
+                              .SetProperty(views::kFlexBehaviorKey,
+                                           stretch_specification)),
+                  views::Builder<views::Button>(
+                      views::BubbleFrameView::CreateCloseButton(
+                          base::BindRepeating(
+                              &ExtensionsMenuNavigationHandler::CloseBubble,
+                              base::Unretained(navigation_handler_))))))
+      .BuildChildren();
+
+  browser_->tab_strip_model()->AddObserver(this);
+}
+
+void ExtensionsMenuMainPageView::Update() {
+  content::WebContents* web_contents = GetActiveWebContents();
+  if (web_contents)
+    subheader_subtitle_->SetText(GetCurrentSite(web_contents));
+}
+
+void ExtensionsMenuMainPageView::TabChangedAt(content::WebContents* contents,
+                                              int index,
+                                              TabChangeType change_type) {
+  Update();
+}
+
+void ExtensionsMenuMainPageView::OnTabStripModelChanged(
+    TabStripModel* tab_strip_model,
+    const TabStripModelChange& change,
+    const TabStripSelectionChange& selection) {
+  Update();
+}
+
+content::WebContents* ExtensionsMenuMainPageView::GetActiveWebContents() const {
+  return browser_->tab_strip_model()->GetActiveWebContents();
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
new file mode 100644
index 0000000..5e84205
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
@@ -0,0 +1,58 @@
+// 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_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_MAIN_PAGE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_MAIN_PAGE_VIEW_H_
+
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Label;
+}
+
+class Browser;
+class ExtensionsMenuNavigationHandler;
+
+// The main view of the extensions menu.
+class ExtensionsMenuMainPageView : public views::View,
+                                   public TabStripModelObserver {
+ public:
+  explicit ExtensionsMenuMainPageView(
+      Browser* browser,
+      ExtensionsMenuNavigationHandler* navigation_handler);
+  ~ExtensionsMenuMainPageView() override = default;
+  ExtensionsMenuMainPageView(const ExtensionsMenuMainPageView&) = delete;
+  const ExtensionsMenuMainPageView& operator=(
+      const ExtensionsMenuMainPageView&) = delete;
+
+  void Update();
+
+  // TabStripModelObserver:
+  // Sometimes, menu can stay open when tab changes (e.g keyboard shortcuts) or
+  // due to the extension (e.g extension switching the active tab). Thus, we
+  // listen for tab changes to properly update the menu content.
+  void TabChangedAt(content::WebContents* contents,
+                    int index,
+                    TabChangeType change_type) override;
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override;
+
+ private:
+  content::WebContents* GetActiveWebContents() const;
+
+  raw_ptr<Browser> browser_;
+  raw_ptr<ExtensionsMenuNavigationHandler> navigation_handler_;
+
+  raw_ptr<views::Label> subheader_subtitle_;
+};
+
+BEGIN_VIEW_BUILDER(/* no export */, ExtensionsMenuMainPageView, views::View)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* no export */, ExtensionsMenuMainPageView)
+
+#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_MAIN_PAGE_VIEW_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_navigation_handler.h b/chrome/browser/ui/views/extensions/extensions_menu_navigation_handler.h
new file mode 100644
index 0000000..80fa7b6
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extensions_menu_navigation_handler.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_NAVIGATION_HANDLER_H_
+#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_NAVIGATION_HANDLER_H_
+
+// An interface that provides methods to navigate between pages of the
+// extensions menu.
+class ExtensionsMenuNavigationHandler {
+ public:
+  virtual void CloseBubble() = 0;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_MENU_NAVIGATION_HANDLER_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 22c5acf2..10b90c9 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -97,7 +97,7 @@
       extensions_menu_coordinator_(
           base::FeatureList::IsEnabled(
               extensions_features::kExtensionsMenuAccessControl)
-              ? std::make_unique<ExtensionsMenuCoordinator>()
+              ? std::make_unique<ExtensionsMenuCoordinator>(browser_)
               : nullptr),
       extensions_button_(base::FeatureList::IsEnabled(
                              extensions_features::kExtensionsMenuAccessControl)
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
index 5ad270b..d5c900b 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
@@ -270,10 +270,6 @@
 }
 
 void BrowserNonClientFrameView::PaintAsActiveChanged() {
-  // The toolbar top separator color (used as the stroke around the tabs and
-  // the new tab button) needs to be recalculated.
-  browser_view_->tab_strip_region_view()->FrameColorsChanged();
-
   if (web_app_frame_toolbar_)
     web_app_frame_toolbar_->SetPaintAsActive(ShouldPaintAsActive());
 
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index aabe30a..22f6912 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -204,17 +204,6 @@
   return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
 }
 
-void TabStripRegionView::FrameColorsChanged() {
-  new_tab_button_->FrameColorsChanged();
-  if (tab_search_button_)
-    tab_search_button_->FrameColorsChanged();
-
-  tab_strip_->FrameColorsChanged();
-  if (tab_strip_scroll_container_)
-    tab_strip_scroll_container_->FrameColorsChanged();
-  SchedulePaint();
-}
-
 bool TabStripRegionView::CanDrop(const OSExchangeData& data) {
   return TabDragController::IsSystemDragAndDropSessionRunning() &&
          data.HasCustomFormat(
@@ -259,11 +248,6 @@
   return tab_strip_min_size;
 }
 
-void TabStripRegionView::OnThemeChanged() {
-  View::OnThemeChanged();
-  FrameColorsChanged();
-}
-
 views::View* TabStripRegionView::GetDefaultFocusableChild() {
   auto* focusable_child = tab_strip_->GetDefaultFocusableChild();
   return focusable_child ? focusable_child
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.h b/chrome/browser/ui/views/frame/tab_strip_region_view.h
index 6a17cd4..7c1270b 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.h
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.h
@@ -41,9 +41,6 @@
   // of |this|.
   bool IsPositionInWindowCaption(const gfx::Point& point);
 
-  // Called when the colors of the frame change.
-  void FrameColorsChanged();
-
   NewTabButton* new_tab_button() { return new_tab_button_; }
 
   TabSearchButton* tab_search_button() { return tab_search_button_; }
@@ -71,7 +68,6 @@
   // views::AccessiblePaneView:
   void ChildPreferredSizeChanged(views::View* child) override;
   gfx::Size GetMinimumSize() const override;
-  void OnThemeChanged() override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   views::View* GetDefaultFocusableChild() override;
 
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.cc b/chrome/browser/ui/views/tabs/compound_tab_container.cc
index 341a091..ddda01c 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.cc
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.cc
@@ -7,6 +7,7 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/trace_event/trace_event.h"
 #include "chrome/browser/ui/tabs/tab_types.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
@@ -56,19 +57,19 @@
   }
 
   bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override {
-    NOTREACHED();
+    NOTREACHED();  // Pinned container can't have groups.
     return false;
   }
 
   absl::optional<int> GetFirstTabInGroup(
       const tab_groups::TabGroupId& group) const override {
-    NOTREACHED();
+    NOTREACHED();  // Pinned container can't have groups.
     return absl::nullopt;
   }
 
   gfx::Range ListTabsInGroup(
       const tab_groups::TabGroupId& group) const override {
-    NOTREACHED();
+    NOTREACHED();  // Pinned container can't have groups.
     return gfx::Range();
   }
 
@@ -346,6 +347,7 @@
 }
 
 std::unique_ptr<Tab> CompoundTabContainer::TransferTabOut(int model_index) {
+  // TODO(1395526): This only needs to be implemented in TabContainerImpl.
   NOTREACHED();
   return nullptr;
 }
@@ -353,9 +355,7 @@
 Tab* CompoundTabContainer::AddTabToViewModel(Tab* tab,
                                              int model_index,
                                              TabPinned pinned) {
-  // This method is implemented by TabContainerImpl for when it's a target for a
-  // pin or unpin animation. This would need to be implemented to nest
-  // CompoundTabContainers inside one another.
+  // TODO(1395526): This only needs to be implemented in TabContainerImpl.
   NOTREACHED();
   return nullptr;
 }
@@ -514,11 +514,25 @@
           this, base::to_address(unpinned_tab_container_), gfx::RectF(rect))));
 }
 
+absl::optional<ZOrderableTabContainerElement>
+CompoundTabContainer::GetLeadingElementForZOrdering() const {
+  // TODO(1395526): This only needs to be implemented in TabContainerImpl.
+  NOTREACHED();
+  return absl::nullopt;
+}
+absl::optional<ZOrderableTabContainerElement>
+CompoundTabContainer::GetTrailingElementForZOrdering() const {
+  // TODO(1395526): This only needs to be implemented in TabContainerImpl.
+  NOTREACHED();
+  return absl::nullopt;
+}
+
 void CompoundTabContainer::OnTabSlotAnimationProgressed(TabSlotView* view) {
   GetTabContainerFor(view)->OnTabSlotAnimationProgressed(view);
 }
 
 void CompoundTabContainer::OnTabCloseAnimationCompleted(Tab* tab) {
+  // TODO(1395526): This only needs to be implemented in TabContainerImpl.
   NOTREACHED();
 }
 
@@ -637,9 +651,45 @@
 }
 
 void CompoundTabContainer::PaintChildren(const views::PaintInfo& paint_info) {
-  // TODO(crbug.com/1346023): paint in the right order depending on tab paint
-  // order.
-  views::View::PaintChildren(paint_info);
+  TRACE_EVENT1("views", "View::PaintChildren", "class", GetClassName());
+
+  // N.B. We override PaintChildren only to define paint order for our children.
+  // We do this instead of GetChildrenInZOrder for consistency with
+  // TabContainerImpl.
+
+  // Paint our containers first, ordered based on their overlapping elements.
+  // I.e., the last tab in `pinned_tab_container_` will overlap the first tab
+  // (or group header) in `unpinned_tab_container_`, and to paint them in the
+  // right order, we have to paint their containers in the same order.
+  // N.B. if either are nullopt, it doesn't matter what order we paint in
+  // because that whole container must be empty and therefore won't paint
+  // anything at all.
+  absl::optional<ZOrderableTabContainerElement> trailing_pinned_element =
+      pinned_tab_container_->GetTrailingElementForZOrdering();
+  absl::optional<ZOrderableTabContainerElement> leading_unpinned_element =
+      unpinned_tab_container_->GetLeadingElementForZOrdering();
+  if (trailing_pinned_element < leading_unpinned_element) {
+    pinned_tab_container_->Paint(paint_info);
+    unpinned_tab_container_->Paint(paint_info);
+  } else {
+    unpinned_tab_container_->Paint(paint_info);
+    pinned_tab_container_->Paint(paint_info);
+  }
+
+  // Then paint all tabs animating between pinned and unpinned, ordered based on
+  // their individual z-values.
+  std::vector<ZOrderableTabContainerElement> orderable_children;
+  for (views::View* const child : children()) {
+    if (!ZOrderableTabContainerElement::CanOrderView(child))
+      continue;
+    orderable_children.emplace_back(child);
+  }
+
+  // Sort in ascending order by z-value. Stable sort breaks ties by child index.
+  std::stable_sort(orderable_children.begin(), orderable_children.end());
+
+  for (const ZOrderableTabContainerElement& child : orderable_children)
+    child.view()->Paint(paint_info);
 }
 
 void CompoundTabContainer::ChildPreferredSizeChanged(views::View* child) {
@@ -648,6 +698,7 @@
 
 BrowserRootView::DropIndex CompoundTabContainer::GetDropIndex(
     const ui::DropTargetEvent& event) {
+  // TODO(1346023): Implement text drag and drop.
   NOTREACHED();
   return BrowserRootView::DropIndex();
 }
@@ -658,16 +709,19 @@
 }
 
 views::View* CompoundTabContainer::GetViewForDrop() {
+  // TODO(1346023): Implement text drag and drop.
   NOTREACHED();
   return nullptr;
 }
 
 void CompoundTabContainer::HandleDragUpdate(
     const absl::optional<BrowserRootView::DropIndex>& index) {
+  // TODO(1346023): Implement text drag and drop.
   NOTREACHED();
 }
 
 void CompoundTabContainer::HandleDragExited() {
+  // TODO(1346023): Implement text drag and drop.
   NOTREACHED();
 }
 
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.h b/chrome/browser/ui/views/tabs/compound_tab_container.h
index 5db4691..982c5826 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.h
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.h
@@ -72,6 +72,10 @@
       TabSlotController::HoverCardUpdateType update_type) override;
   void HandleLongTap(ui::GestureEvent* event) override;
   bool IsRectInContentArea(const gfx::Rect& rect) override;
+  absl::optional<ZOrderableTabContainerElement> GetLeadingElementForZOrdering()
+      const override;
+  absl::optional<ZOrderableTabContainerElement> GetTrailingElementForZOrdering()
+      const override;
   void OnTabSlotAnimationProgressed(TabSlotView* view) override;
   void OnTabCloseAnimationCompleted(Tab* tab) override;
   void InvalidateIdealBounds() override;
diff --git a/chrome/browser/ui/views/tabs/new_tab_button.cc b/chrome/browser/ui/views/tabs/new_tab_button.cc
index 431669e9..c205558 100644
--- a/chrome/browser/ui/views/tabs/new_tab_button.cc
+++ b/chrome/browser/ui/views/tabs/new_tab_button.cc
@@ -120,6 +120,18 @@
   ink_drop_container_->SetBoundsRect(GetLocalBounds());
 }
 
+void NewTabButton::AddedToWidget() {
+  paint_as_active_subscription_ =
+      GetWidget()->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
+          &NewTabButton::FrameColorsChanged, base::Unretained(this)));
+  // Set the initial state correctly.
+  FrameColorsChanged();
+}
+
+void NewTabButton::RemovedFromWidget() {
+  paint_as_active_subscription_ = {};
+}
+
 #if BUILDFLAG(IS_WIN)
 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
   if (!event.IsOnlyRightMouseButton()) {
diff --git a/chrome/browser/ui/views/tabs/new_tab_button.h b/chrome/browser/ui/views/tabs/new_tab_button.h
index 5a4dbdd..7c2a308 100644
--- a/chrome/browser/ui/views/tabs/new_tab_button.h
+++ b/chrome/browser/ui/views/tabs/new_tab_button.h
@@ -58,6 +58,8 @@
 
   // views::ImageButton:
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+  void AddedToWidget() override;
+  void RemovedFromWidget() override;
 
  private:
   class HighlightPathGenerator;
@@ -92,6 +94,8 @@
   // Contains our ink drop layer so it can paint above our background.
   raw_ptr<views::InkDropContainerView, DanglingUntriaged> ink_drop_container_;
 
+  base::CallbackListSubscription paint_as_active_subscription_;
+
   // For tracking whether this object has been destroyed. Must be last.
   base::WeakPtrFactory<NewTabButton> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 4e7a91b..579a5c23 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -685,9 +685,17 @@
 }
 
 void Tab::AddedToWidget() {
+  paint_as_active_subscription_ =
+      GetWidget()->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
+          &Tab::UpdateForegroundColors, base::Unretained(this)));
+  // Set the initial state correctly
   UpdateForegroundColors();
 }
 
+void Tab::RemovedFromWidget() {
+  paint_as_active_subscription_ = {};
+}
+
 void Tab::OnFocus() {
   View::OnFocus();
   controller_->UpdateHoverCard(this,
@@ -802,10 +810,6 @@
   Layout();
 }
 
-void Tab::FrameColorsChanged() {
-  UpdateForegroundColors();
-}
-
 void Tab::SelectedStateChanged() {
   UpdateForegroundColors();
 }
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index ac17154..ee39e522 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -96,6 +96,7 @@
   void PaintChildren(const views::PaintInfo& info) override;
   void OnPaint(gfx::Canvas* canvas) override;
   void AddedToWidget() override;
+  void RemovedFromWidget() override;
   void OnFocus() override;
   void OnBlur() override;
   void OnThemeChanged() override;
@@ -124,9 +125,6 @@
   // Called when the alert indicator has changed states.
   void AlertStateChanged();
 
-  // Called when the frame state color changes.
-  void FrameColorsChanged();
-
   // Called when the selected state changes.
   void SelectedStateChanged();
 
@@ -290,6 +288,8 @@
   // Freezing token held while the tab is collapsed.
   std::unique_ptr<performance_manager::freezing::FreezingVoteToken>
       freezing_token_;
+
+  base::CallbackListSubscription paint_as_active_subscription_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_H_
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h
index 0c0bfac..bd2661ab 100644
--- a/chrome/browser/ui/views/tabs/tab_container.h
+++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/tabs/tab_group_views.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_view.h"
+#include "chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "ui/views/animation/bounds_animator.h"
 #include "ui/views/view.h"
@@ -105,6 +106,11 @@
 
   virtual bool IsRectInContentArea(const gfx::Rect& rect) = 0;
 
+  virtual absl::optional<ZOrderableTabContainerElement>
+  GetLeadingElementForZOrdering() const = 0;
+  virtual absl::optional<ZOrderableTabContainerElement>
+  GetTrailingElementForZOrdering() const = 0;
+
   // Animation stuff. Will be public until fully moved down into TabContainer.
 
   // Called whenever a tab or group header animation has progressed.
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.cc b/chrome/browser/ui/views/tabs/tab_container_impl.cc
index 43485feb..608ef4b6 100644
--- a/chrome/browser/ui/views/tabs/tab_container_impl.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_impl.cc
@@ -546,6 +546,42 @@
   return true;
 }
 
+absl::optional<ZOrderableTabContainerElement>
+TabContainerImpl::GetLeadingElementForZOrdering() const {
+  // Use `tabs_view_model_` instead of `layout_helper_` to ignore closing tabs
+  // to prevent discontinuous z-order flips when tab close animations end.
+  if (GetTabCount() == 0)
+    return absl::nullopt;
+  Tab* const leading_tab = tabs_view_model_.view_at(0);
+
+  // If `leading_tab` is grouped, it's preceded by its group header.
+  if (leading_tab->group().has_value()) {
+    return ZOrderableTabContainerElement(
+        group_views_.at(leading_tab->group().value())->header());
+  }
+
+  return ZOrderableTabContainerElement(leading_tab);
+}
+
+absl::optional<ZOrderableTabContainerElement>
+TabContainerImpl::GetTrailingElementForZOrdering() const {
+  // Use `tabs_view_model_` instead of `layout_helper_` to ignore closing tabs
+  // to prevent discontinuous z-order flips when tab close animations end.
+  if (GetTabCount() == 0)
+    return absl::nullopt;
+
+  Tab* const trailing_tab =
+      tabs_view_model_.view_at(tabs_view_model_.view_size() - 1);
+
+  // Tab group headers could be the trailing element, if the group is collapsed.
+  // However, this method doesn't need to consider that case because it is
+  // currently only called on the pinned TabContainer in a CompoundTabContainer,
+  // which can't have tab groups. DCHECK that assumption:
+  DCHECK(!trailing_tab->group().has_value());
+
+  return ZOrderableTabContainerElement(trailing_tab);
+}
+
 void TabContainerImpl::OnTabSlotAnimationProgressed(TabSlotView* view) {
   if (view && view->group())
     UpdateTabGroupVisuals(view->group().value());
@@ -737,6 +773,12 @@
 }
 
 void TabContainerImpl::PaintChildren(const views::PaintInfo& paint_info) {
+  // N.B. We override PaintChildren only to define paint order for our children.
+  // We do this instead of GetChildrenInZOrder because GetChildrenInZOrder is
+  // called in many more contexts for many more reasons, e.g. whenever views are
+  // added or removed, and in particular can be called while we are partway
+  // through creating a tab group and are not in a self-consistent state.
+
   std::vector<ZOrderableTabContainerElement> orderable_children;
   for (views::View* child : children()) {
     if (!ZOrderableTabContainerElement::CanOrderView(child))
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.h b/chrome/browser/ui/views/tabs/tab_container_impl.h
index 6b65bf3..5fe38e0 100644
--- a/chrome/browser/ui/views/tabs/tab_container_impl.h
+++ b/chrome/browser/ui/views/tabs/tab_container_impl.h
@@ -98,6 +98,11 @@
 
   bool IsRectInContentArea(const gfx::Rect& rect) override;
 
+  absl::optional<ZOrderableTabContainerElement> GetLeadingElementForZOrdering()
+      const override;
+  absl::optional<ZOrderableTabContainerElement> GetTrailingElementForZOrdering()
+      const override;
+
   void OnTabSlotAnimationProgressed(TabSlotView* view) override;
 
   void OnTabCloseAnimationCompleted(Tab* tab) override;
@@ -291,6 +296,10 @@
   // to clip).
   bool ShouldTabBeVisible(const Tab* tab) const;
 
+  // Returns true iff `tab` is a member of a collapsed group and the collapse
+  // animation is finished.
+  bool IsTabCollapsed(const Tab* tab) const;
+
   // -- Link Drag & Drop ------------------------------------------------------
 
   // Returns the bounds to render the drop at, in screen coordinates. Sets
diff --git a/chrome/browser/ui/views/tabs/tab_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
index 0429793c..5eece85 100644
--- a/chrome/browser/ui/views/tabs/tab_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
@@ -1024,3 +1024,24 @@
   // Validate that `tab_container_` did not actually take the tab view back.
   EXPECT_EQ(tab->parent(), nullptr);
 }
+
+TEST_F(TabContainerTest, GetLeadingTrailingElementsForZOrdering) {
+  // An empty TabContainer has no leading/trailing views.
+  EXPECT_EQ(tab_container_->GetLeadingElementForZOrdering(), absl::nullopt);
+  EXPECT_EQ(tab_container_->GetTrailingElementForZOrdering(), absl::nullopt);
+
+  // Leading/trailing views could be tabs.
+  Tab* const first_tab = AddTab(0);
+  Tab* const last_tab = AddTab(1);
+  EXPECT_EQ(tab_container_->GetLeadingElementForZOrdering()->view(), first_tab);
+  EXPECT_EQ(tab_container_->GetTrailingElementForZOrdering()->view(), last_tab);
+
+  // Leading view could be a group header.
+  tab_groups::TabGroupId group = tab_groups::TabGroupId::GenerateNew();
+  AddTabToGroup(0, group);
+  AddTabToGroup(1, group);
+  TabGroupHeader* const group_header =
+      tab_container_->GetGroupViews(group)->header();
+  EXPECT_EQ(tab_container_->GetLeadingElementForZOrdering()->view(),
+            group_header);
+}
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index 3cd0b6fc..a9d3f225 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -344,6 +344,12 @@
   if (update_type == TabSlotController::HoverCardUpdateType::kTabDataChanged) {
     DCHECK(IsHoverCardShowingForTab(tab));
     UpdateCardContent(tab);
+
+    // When a tab has been discarded, the thumbnail is moved to a new
+    // ThumbnailTabHelper so it must be observed again.
+    if (tab->data().is_tab_discarded)
+      MaybeStartThumbnailObservation(tab, /* is_initial_show */ false);
+
     slide_animator_->UpdateTargetBounds();
     return;
   }
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 87215215..c6390ac 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -976,13 +976,6 @@
   observers_.RemoveObserver(observer);
 }
 
-void TabStrip::FrameColorsChanged() {
-  for (int i = 0; i < GetTabCount(); ++i)
-    tab_at(i)->FrameColorsChanged();
-  UpdateContrastRatioValues();
-  SchedulePaint();
-}
-
 void TabStrip::SetBackgroundOffset(int background_offset) {
   if (background_offset == background_offset_)
     return;
@@ -1218,20 +1211,25 @@
     if (old_active_tab)
       old_active_tab->ActiveStateChanged();
 
-    if (new_active_tab->group().has_value()) {
-      const tab_groups::TabGroupId new_group = new_active_tab->group().value();
-      // If the tab that is about to be activated is in a collapsed group,
-      // automatically expand the group.
-      if (IsGroupCollapsed(new_group))
-        ToggleTabGroupCollapsedState(
-            new_group, ToggleTabGroupCollapsedStateOrigin::kImplicitAction);
-    }
     new_active_tab->ActiveStateChanged();
 
     tab_container_->SetActiveTab(selected_tabs_.active(),
                                  new_selection.active());
   }
 
+  for (int selection : new_selection.selected_indices()) {
+    Tab* const selected_tab = tab_at(selection);
+    if (selected_tab->group().has_value()) {
+      const tab_groups::TabGroupId new_group = selected_tab->group().value();
+      // If the tab that is about to be selected is in a collapsed group,
+      // automatically expand the group.
+      if (IsGroupCollapsed(new_group)) {
+        ToggleTabGroupCollapsedState(
+            new_group, ToggleTabGroupCollapsedStateOrigin::kImplicitAction);
+      }
+    }
+  }
+
   // Use STLSetDifference to get the indices of elements newly selected
   // and no longer selected, since selected_indices() is always sorted.
   ui::ListSelectionModel::SelectedIndices no_longer_selected =
@@ -2169,10 +2167,16 @@
 
 void TabStrip::AddedToWidget() {
   GetWidget()->AddObserver(this);
+  paint_as_active_subscription_ =
+      GetWidget()->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
+          &TabStrip::UpdateContrastRatioValues, base::Unretained(this)));
+  // Set the initial state correctly.
+  UpdateContrastRatioValues();
 }
 
 void TabStrip::RemovedFromWidget() {
   GetWidget()->RemoveObserver(this);
+  paint_as_active_subscription_ = {};
 }
 
 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 216a0de4..1a7d9169 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -314,9 +314,6 @@
   void ShiftGroupRight(const tab_groups::TabGroupId& group) override;
   const Browser* GetBrowser() const override;
 
-  // Update the background colors when frame active state changes.
-  void FrameColorsChanged();
-
   // views::View:
   views::SizeBounds GetAvailableSize(const View* child) const override;
   gfx::Size GetMinimumSize() const override;
@@ -469,6 +466,8 @@
 
   SkColor separator_color_ = gfx::kPlaceholderColor;
 
+  base::CallbackListSubscription paint_as_active_subscription_;
+
   const base::CallbackListSubscription subscription_ =
       ui::TouchUiController::Get()->RegisterCallback(
           base::BindRepeating(&TabStrip::OnTouchUiChanged,
diff --git a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
index 381d8ea..feaf999 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
@@ -308,6 +308,19 @@
   FrameColorsChanged();
 }
 
+void TabStripScrollContainer::AddedToWidget() {
+  paint_as_active_subscription_ =
+      GetWidget()->RegisterPaintAsActiveChangedCallback(
+          base::BindRepeating(&TabStripScrollContainer::FrameColorsChanged,
+                              base::Unretained(this)));
+  // Set the initial state correctly.
+  FrameColorsChanged();
+}
+
+void TabStripScrollContainer::RemovedFromWidget() {
+  paint_as_active_subscription_ = {};
+}
+
 BEGIN_METADATA(TabStripScrollContainer, views::View)
 ADD_READONLY_PROPERTY_METADATA(int, TabStripAvailableWidth)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.h b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.h
index 7a55510..9f165dd7 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.h
@@ -66,6 +66,8 @@
 
   // views::View
   void OnThemeChanged() override;
+  void AddedToWidget() override;
+  void RemovedFromWidget() override;
 
   // Manages the visibility of the scroll buttons based on whether |tab_strip_|
   // is currently overflowing.
@@ -82,6 +84,8 @@
   // The class handling the overflow indiciators for the scroll view.
   std::unique_ptr<TabStripScrollingOverflowIndicatorStrategy>
       overflow_indicator_strategy_;
+
+  base::CallbackListSubscription paint_as_active_subscription_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_SCROLL_CONTAINER_H_
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index 9ae245b2..953b0af 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -33,6 +33,10 @@
 #include "ui/gfx/text_utils.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace {
 
 // Possibly returns a resident key warning if the model indicates that it's
@@ -657,7 +661,9 @@
 
 std::u16string AuthenticatorBlePermissionMacSheetModel::GetAcceptButtonLabel()
     const {
-  return l10n_util::GetStringUTF16(IDS_OPEN_PREFERENCES_LINK);
+  return l10n_util::GetStringUTF16(base::mac::IsAtMostOS12()
+                                       ? IDS_OPEN_PREFERENCES_LINK
+                                       : IDS_OPEN_SETTINGS_LINK);
 }
 
 void AuthenticatorBlePermissionMacSheetModel::OnAccept() {
diff --git a/chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.cc b/chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.cc
index 2d7fd45..e19cb94 100644
--- a/chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.cc
+++ b/chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_phone_hub_handler.cc
@@ -21,6 +21,7 @@
 #include "chromeos/ash/components/phonehub/fake_phone_hub_manager.h"
 #include "chromeos/ash/components/phonehub/notification.h"
 #include "chromeos/ash/components/phonehub/pref_names.h"
+#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h"
 #include "components/prefs/pref_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -92,7 +93,8 @@
 
   return phonehub::Notification::AppMetadata(
       visible_app_name, *package_name, icon, /*icon_color=*/absl::nullopt,
-      /*icon_is_monochrome=*/false, user_id);
+      /*icon_is_monochrome=*/false, user_id,
+      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
 }
 
 void TryAddingMetadata(
diff --git a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
index 56781500..fd93de3 100644
--- a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
+++ b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
@@ -447,10 +447,12 @@
     // "chrome://signin-reauth",
 #endif
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    "chrome://chrome-signin",
+// TODO(crbug.com/1399912): Uncomment when TrustedTypes are enabled.
+// "chrome://chrome-signin",
 #endif
 #if BUILDFLAG(ENABLE_DICE_SUPPORT) && !BUILDFLAG(IS_CHROMEOS_ASH)
-    "chrome://chrome-signin/?reason=5",
+// TODO(crbug.com/1399912): Uncomment when TrustedTypes are enabled.
+// "chrome://chrome-signin/?reason=5",
 #endif
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
     "chrome://webuijserror",
diff --git a/chrome/browser/ui/webui/downloads/downloads.mojom b/chrome/browser/ui/webui/downloads/downloads.mojom
index 60ac0447..a5aa1fee 100644
--- a/chrome/browser/ui/webui/downloads/downloads.mojom
+++ b/chrome/browser/ui/webui/downloads/downloads.mojom
@@ -9,7 +9,8 @@
 struct Data {
   bool file_externally_removed;
   bool is_dangerous;
-  bool is_mixed_content;
+  [RenamedFrom=is_mixed_content]
+  bool is_insecure;
   bool is_reviewable;
   // |otr| stands for off-the-record and is true when a download entry is
   // created during an incognito or guest profile session.
@@ -32,7 +33,7 @@
   string show_in_folder_text;
   string since_string;
   // |state| indicates the current state of the download. A download data entry
-  // can be dangerous or mixed content which can be expressed as a state to
+  // can be dangerous or insecure which can be expressed as a state to
   // indicate the download needs confirmation before initiating. After
   // confirmation, the state will indicate the download is in progress, complete
   // or cancelled.
diff --git a/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
index 149364f..7cd951a 100644
--- a/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
@@ -360,7 +360,7 @@
   IdSet ids;
 
   for (auto* download : to_remove) {
-    if (download->IsDangerous() || download->IsMixedContent()) {
+    if (download->IsDangerous() || download->IsInsecure()) {
       // Don't allow users to revive dangerous downloads; just nuke 'em.
       download->Remove();
       continue;
@@ -480,14 +480,14 @@
     return;
   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
 
-  // If a download is mixed content, validate that first. Is most cases, mixed
-  // content warnings will occur first, but in the worst case scenario, we show
-  // a dangerous warning twice. That's better than showing a mixed content
-  // warning, then dismissing the dangerous download warning. Since mixed
-  // content downloads triggering the UI are temporary and rare to begin with,
-  // this should very rarely occur.
-  if (item->IsMixedContent()) {
-    item->ValidateMixedContentDownload();
+  // If a download is insecure, validate that first. Is most cases, insecure
+  // download warnings will occur first, but in the worst case scenario, we show
+  // a dangerous warning twice. That's better than showing an insecure download
+  // warning, then dismissing the dangerous download warning. Since insecure
+  // downloads triggering the UI are temporary and rare to begin with, this
+  // should very rarely occur.
+  if (item->IsInsecure()) {
+    item->ValidateInsecureDownload();
     return;
   }
 
diff --git a/chrome/browser/ui/webui/downloads/downloads_dom_handler_unittest.cc b/chrome/browser/ui/webui/downloads/downloads_dom_handler_unittest.cc
index f88b596..0d4a40b6 100644
--- a/chrome/browser/ui/webui/downloads/downloads_dom_handler_unittest.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_dom_handler_unittest.cc
@@ -98,7 +98,7 @@
   // Safe, in-progress items should be passed over.
   testing::StrictMock<download::MockDownloadItem> in_progress;
   EXPECT_CALL(in_progress, IsDangerous()).WillOnce(testing::Return(false));
-  EXPECT_CALL(in_progress, IsMixedContent()).WillOnce(testing::Return(false));
+  EXPECT_CALL(in_progress, IsInsecure()).WillOnce(testing::Return(false));
   EXPECT_CALL(in_progress, IsTransient()).WillOnce(testing::Return(false));
   EXPECT_CALL(in_progress, GetState())
       .WillOnce(testing::Return(download::DownloadItem::IN_PROGRESS));
@@ -113,7 +113,7 @@
   // Completed items should be marked as hidden from the shelf.
   testing::StrictMock<download::MockDownloadItem> completed;
   EXPECT_CALL(completed, IsDangerous()).WillOnce(testing::Return(false));
-  EXPECT_CALL(completed, IsMixedContent()).WillOnce(testing::Return(false));
+  EXPECT_CALL(completed, IsInsecure()).WillOnce(testing::Return(false));
   EXPECT_CALL(completed, IsTransient()).WillRepeatedly(testing::Return(false));
   EXPECT_CALL(completed, GetState())
       .WillOnce(testing::Return(download::DownloadItem::COMPLETE));
diff --git a/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc b/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
index 7862fd7..d680134 100644
--- a/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
@@ -284,8 +284,8 @@
     case download::DownloadItem::IN_PROGRESS: {
       if (download_item->IsDangerous()) {
         state = "DANGEROUS";
-      } else if (download_item->IsMixedContent()) {
-        state = "MIXED_CONTENT";
+      } else if (download_item->IsInsecure()) {
+        state = "INSECURE";
       } else if (download_item->GetDangerType() ==
                  download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING) {
         state = "ASYNC_SCANNING";
@@ -336,7 +336,7 @@
 
   file_value->danger_type = danger_type;
   file_value->is_dangerous = download_item->IsDangerous();
-  file_value->is_mixed_content = download_item->IsMixedContent();
+  file_value->is_insecure = download_item->IsInsecure();
   file_value->is_reviewable =
       enterprise_connectors::ShouldPromptReviewForDownload(
           Profile::FromBrowserContext(
diff --git a/chrome/browser/ui/webui/downloads/downloads_ui.cc b/chrome/browser/ui/webui/downloads/downloads_ui.cc
index ff71b606..e888b31 100644
--- a/chrome/browser/ui/webui/downloads/downloads_ui.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_ui.cc
@@ -132,8 +132,8 @@
           : IDS_BLOCK_REASON_UNCOMMON_DOWNLOAD);
   source->AddLocalizedString("dangerSettingsDesc",
                              IDS_BLOCK_REASON_UNWANTED_DOWNLOAD);
-  source->AddLocalizedString("mixedContentDownloadDesc",
-                             IDS_BLOCK_REASON_MIXED_CONTENT);
+  source->AddLocalizedString("insecureDownloadDesc",
+                             IDS_BLOCK_REASON_INSECURE_DOWNLOAD);
   source->AddLocalizedString("asyncScanningDownloadDesc",
                              IDS_BLOCK_REASON_DEEP_SCANNING);
   source->AddLocalizedString("accountCompromiseDownloadDesc",
diff --git a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
index 870773d..2c45f3b 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
@@ -1186,7 +1186,7 @@
       ash::ShillServiceClient::Get()->GetTestInterface();
   for (const auto* const network : networks) {
     service->SetServiceProperty(network->path(), shill::kStateProperty,
-                                base::Value(shill::kStateOffline));
+                                base::Value(shill::kStateIdle));
   }
   base::RunLoop().RunUntilIdle();
 
diff --git a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
index b25201f5..5f5126e1 100644
--- a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
@@ -762,6 +762,8 @@
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NAVIGATION_CONTROLS_DESCRIPTION},
       {"selectToSpeakOptionsNavigationControlsSubtitle",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NAVIGATION_CONTROLS_SUBTITLE},
+      {"selectToSpeakTextToSpeechSettingsLink",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TEXT_TO_SPEECH_SETTINGS_LINK},
       {"selectToSpeakOptionsHighlight",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_HIGHLIGHT},
       {"selectToSpeakOptionsSpeech",
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
index 3ada2ed0..96d4b78 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
@@ -76,6 +76,7 @@
       {"searchBookmarks", IDS_BOOKMARK_MANAGER_SEARCH_BUTTON},
       {"clearSearch", IDS_BOOKMARK_MANAGER_CLEAR_SEARCH},
       {"selectedBookmarkCount", IDS_BOOKMARK_MANAGER_ITEMS_SELECTED},
+      {"menuOpenNewTab", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB},
   };
   for (const auto& str : kLocalizedStrings)
     webui::AddLocalizedString(source, str.name, str.id);
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index 6682694..f28a707 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -21,7 +21,9 @@
   BackgroundImage? background_image;
   // Whether the OS is in dark mode.
   bool system_dark_mode;
-  // The current theme color. If not set, we use the default theme.
+  // The current theme background color.
+  skia.mojom.SkColor background_color;
+  // The current theme foreground color. If not set, we use the default theme.
   skia.mojom.SkColor? foreground_color;
 };
 
@@ -74,6 +76,9 @@
 
   // Sets Chrome's theme according to |foreground_color|.
   SetForegroundColor(skia.mojom.SkColor foreground_color);
+
+  // Sets theme to default classic chrome.
+  SetClassicChromeDefaultTheme();
 };
 
 // WebUI-side handler for requests from the browser.
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index 3d092a3..ef6789f 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -112,6 +112,8 @@
     background_image = nullptr;
   }
   theme->background_image = std::move(background_image);
+  theme->background_color =
+      web_contents_->GetColorProvider().GetColor(kColorNewTabPageBackground);
   if (!theme_service_->UsingDefaultTheme() &&
       !theme_service_->UsingSystemTheme()) {
     theme->foreground_color =
@@ -140,6 +142,11 @@
   chrome_colors_service->ConfirmThemeChanges();
 }
 
+void CustomizeChromePageHandler::SetClassicChromeDefaultTheme() {
+  ntp_custom_background_service_->ResetCustomBackgroundInfo();
+  theme_service_->UseDefaultTheme();
+}
+
 void CustomizeChromePageHandler::OnNativeThemeUpdated(
     ui::NativeTheme* observed_theme) {
   UpdateTheme();
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
index 21bb4ee..9598531d 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
@@ -57,6 +57,7 @@
   void UpdateTheme() override;
   void SetDefaultColor() override;
   void SetForegroundColor(SkColor foreground_color) override;
+  void SetClassicChromeDefaultTheme() override;
 
  private:
   // ui::NativeThemeObserver:
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index 545f401..50cf382 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -70,6 +70,7 @@
   explicit MockNtpCustomBackgroundService(Profile* profile)
       : NtpCustomBackgroundService(profile) {}
   MOCK_METHOD0(GetCustomBackground, absl::optional<CustomBackground>());
+  MOCK_METHOD0(ResetCustomBackgroundInfo, void());
 };
 
 class MockNtpBackgroundService : public NtpBackgroundService {
@@ -88,6 +89,7 @@
   using ThemeService::NotifyThemeChanged;
   MOCK_CONST_METHOD0(UsingDefaultTheme, bool());
   MOCK_CONST_METHOD0(UsingSystemTheme, bool());
+  MOCK_METHOD0(UseDefaultTheme, void());
 
  private:
   ThemeHelper theme_helper_;
@@ -306,6 +308,9 @@
   EXPECT_EQ("https://foo.com/img.png", theme->background_image->url);
   EXPECT_EQ("foo line", theme->background_image->title);
   EXPECT_TRUE(theme->system_dark_mode);
+  EXPECT_EQ(
+      web_contents().GetColorProvider().GetColor(kColorNewTabPageBackground),
+      theme->background_color);
   EXPECT_EQ(web_contents().GetColorProvider().GetColor(ui::kColorFrameActive),
             theme->foreground_color);
 }
@@ -366,3 +371,11 @@
 
   EXPECT_EQ(SK_ColorBLUE, color);
 }
+
+TEST_F(CustomizeChromePageHandlerTest, SetClassicChromeDefaultTheme) {
+  EXPECT_CALL(mock_ntp_custom_background_service_, ResetCustomBackgroundInfo)
+      .Times(1);
+  EXPECT_CALL(mock_theme_service(), UseDefaultTheme).Times(1);
+
+  handler().SetClassicChromeDefaultTheme();
+}
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
index 066a3c5..9d2b08b5 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -29,6 +29,7 @@
       chrome::kChromeUICustomizeChromeSidePanelHost);
 
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
+      {"classicChrome", IDS_NTP_CUSTOMIZE_NO_BACKGROUND_LABEL},
       {"customizeThisPage", IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL},
       {"appearanceHeader", IDS_NTP_CUSTOMIZE_APPEARANCE_LABEL},
       {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
index 94795721..20d9cec1 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
@@ -94,6 +94,7 @@
       {"searchBookmarks", IDS_BOOKMARK_MANAGER_SEARCH_BUTTON},
       {"clearSearch", IDS_BOOKMARK_MANAGER_CLEAR_SEARCH},
       {"selectedBookmarkCount", IDS_BOOKMARK_MANAGER_ITEMS_SELECTED},
+      {"menuOpenNewTab", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB},
   };
   for (const auto& str : kLocalizedStrings)
     webui::AddLocalizedString(source, str.name, str.id);
diff --git a/chrome/browser/ui/webui/signin/inline_login_ui.cc b/chrome/browser/ui/webui/signin/inline_login_ui.cc
index 3656528..69dcc6f 100644
--- a/chrome/browser/ui/webui/signin/inline_login_ui.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_ui.cc
@@ -117,6 +117,9 @@
       source,
       base::make_span(kGaiaAuthHostResources, kGaiaAuthHostResourcesSize),
       IDR_INLINE_LOGIN_INLINE_LOGIN_HTML);
+  // TODO(crbug.com/1399912): Remove this when saml_password_attributes.js is
+  // made TrustedTypes compliant.
+  source->DisableTrustedTypesCSP();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   source->AddResourcePaths(base::make_span(kArcAccountPickerResources,
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
index dd4b228..69108fb 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
@@ -960,7 +960,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void WebAppPublisherHelper::GetCompressedIconData(
     const std::string& app_id,
-    apps::IconType icon_type,
     int32_t size_in_dip,
     ui::ResourceScaleFactor scale_factor,
     apps::LoadIconCallback callback) {
@@ -969,8 +968,8 @@
     return;
   }
 
-  apps::GetWebAppCompressedIconData(profile_, app_id, icon_type, size_in_dip,
-                                    scale_factor, std::move(callback));
+  apps::GetWebAppCompressedIconData(profile_, app_id, size_in_dip, scale_factor,
+                                    std::move(callback));
 }
 #endif
 
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
index 4b651f5..9038b216 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.h
@@ -218,7 +218,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void GetCompressedIconData(const std::string& app_id,
-                             apps::IconType icon_type,
                              int32_t size_in_dip,
                              ui::ResourceScaleFactor scale_factor,
                              apps::LoadIconCallback callback);
diff --git a/chrome/browser/web_applications/app_service/web_apps.cc b/chrome/browser/web_applications/app_service/web_apps.cc
index 6ff66aa..4461a34b 100644
--- a/chrome/browser/web_applications/app_service/web_apps.cc
+++ b/chrome/browser/web_applications/app_service/web_apps.cc
@@ -102,12 +102,11 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void WebApps::GetCompressedIconData(const std::string& app_id,
-                                    apps::IconType icon_type,
                                     int32_t size_in_dip,
                                     ui::ResourceScaleFactor scale_factor,
                                     apps::LoadIconCallback callback) {
-  publisher_helper().GetCompressedIconData(app_id, icon_type, size_in_dip,
-                                           scale_factor, std::move(callback));
+  publisher_helper().GetCompressedIconData(app_id, size_in_dip, scale_factor,
+                                           std::move(callback));
 }
 #endif
 
diff --git a/chrome/browser/web_applications/app_service/web_apps.h b/chrome/browser/web_applications/app_service/web_apps.h
index 2c41a3d..2a6f7be 100644
--- a/chrome/browser/web_applications/app_service/web_apps.h
+++ b/chrome/browser/web_applications/app_service/web_apps.h
@@ -97,7 +97,6 @@
                 apps::LoadIconCallback callback) override;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void GetCompressedIconData(const std::string& app_id,
-                             apps::IconType icon_type,
                              int32_t size_in_dip,
                              ui::ResourceScaleFactor scale_factor,
                              apps::LoadIconCallback callback) override;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 519df24b4..651b7ab 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -299,12 +299,7 @@
          current_step() == Step::kNotStarted);
 
 #if BUILDFLAG(IS_MAC)
-  // The BLE permission screen is only shown on macOS <= 12 because:
-  //    * System Preferences has been renamed to System Settings, so the
-  //      string on the button would need to be changed.
-  //    * Opening Preferences/Settings at the BLE permissions page is broken.
-  if (transport_availability()->ble_access_denied &&
-      base::mac::IsAtMostOS12()) {
+  if (transport_availability()->ble_access_denied) {
     // |step| is not saved because macOS asks the user to restart Chrome
     // after permission has been granted. So the user will end up retrying
     // the whole WebAuthn request in the new process.
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 136fd89..aa93fd1 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1670817917-66caf48bab90ea9aded986b6fe5b85175c326688.profdata
+chrome-mac-main-1670867915-974611ceb0aae82f69b3414c56ce0ab9167bb70c.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index bd3b2ce..b3d190df 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1670608554-b0222b89dfa192f0ebf51cd045b0c78460a84918.profdata
+chrome-win32-main-1670817917-2ff239d7f455bf002c1f3af4e1638d37e5e1d2b5.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index e5efb15..b6cfc47e 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1670817917-da14d29ef9f05cb1a496ffa329a350e0ca08ed9b.profdata
+chrome-win64-main-1670867915-0780691e740e6cd0d08a7857afa68ce8aa0a250d.profdata
diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc
index 9dbcea7..2a81b170 100644
--- a/chrome/common/extensions/extension_unittest.cc
+++ b/chrome/common/extensions/extension_unittest.cc
@@ -104,8 +104,8 @@
       .Set("description", "some description");
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(manifest.Build())
-          .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+          .SetManifest(manifest.BuildDict())
+          .MergeManifest(DictionaryBuilder().Set("version", "0.1").BuildDict())
           .Build();
   ASSERT_TRUE(extension.get());
   EXPECT_EQ("TestNew lines", extension->name());
@@ -121,8 +121,8 @@
       .Set("description", "some description");
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(manifest.Build())
-          .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+          .SetManifest(manifest.BuildDict())
+          .MergeManifest(DictionaryBuilder().Set("version", "0.1").BuildDict())
           .Build();
   ASSERT_TRUE(extension.get());
   EXPECT_EQ("Test Whitespace", extension->name());
@@ -139,8 +139,8 @@
       .Set("description", "some description");
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(manifest1.Build())
-          .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+          .SetManifest(manifest1.BuildDict())
+          .MergeManifest(DictionaryBuilder().Set("version", "0.1").BuildDict())
           .Build();
   ASSERT_TRUE(extension.get());
   EXPECT_EQ("", extension->name());
@@ -151,8 +151,8 @@
       .Set("description", "some description");
   extension =
       ExtensionBuilder()
-          .SetManifest(manifest2.Build())
-          .MergeManifest(DictionaryBuilder().Set("version", "0.1").Build())
+          .SetManifest(manifest2.BuildDict())
+          .MergeManifest(DictionaryBuilder().Set("version", "0.1").BuildDict())
           .Build();
   ASSERT_TRUE(extension.get());
   EXPECT_EQ("", extension->name());
@@ -170,7 +170,7 @@
         .Set("version",
              "0.1");  // <NOTE> Moved this here to avoid the MergeManifest call.
     scoped_refptr<const Extension> extension =
-        ExtensionBuilder().SetManifest(manifest.Build()).Build();
+        ExtensionBuilder().SetManifest(manifest.BuildDict()).Build();
     ASSERT_TRUE(extension);
     const int kResourceId = IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE;
     const std::u16string expected_utf16 = base::WideToUTF16(expected);
diff --git a/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc b/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc
index 04d6856..aa52b516 100644
--- a/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/settings_overrides_handler_unittest.cc
@@ -111,7 +111,7 @@
 using extensions::api::manifest_types::ChromeSettingsOverrides;
 namespace manifest_keys = extensions::manifest_keys;
 
-scoped_refptr<Extension> CreateExtension(const base::DictionaryValue& manifest,
+scoped_refptr<Extension> CreateExtension(const base::Value::Dict& manifest,
                                          std::string* error) {
   scoped_refptr<Extension> extension =
       Extension::Create(base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
@@ -132,12 +132,11 @@
     ADD_FAILURE() << "Manifest isn't a Dictionary";
     return nullptr;
   }
-  return CreateExtension(*static_cast<base::DictionaryValue*>(root.get()),
-                         error);
+  return CreateExtension(root->GetDict(), error);
 }
 
 scoped_refptr<Extension> CreateExtensionWithSearchProvider(
-    std::unique_ptr<base::DictionaryValue> search_provider,
+    base::Value::Dict search_provider,
     std::string* error) {
   DictionaryBuilder manifest;
   manifest.Set("name", "name")
@@ -147,8 +146,8 @@
       .Set("chrome_settings_overrides",
            DictionaryBuilder()
                .Set("search_provider", std::move(search_provider))
-               .Build());
-  return CreateExtension(*manifest.Build(), error);
+               .BuildDict());
+  return CreateExtension(manifest.BuildDict(), error);
 }
 
 TEST(OverrideSettingsTest, ParseManifest) {
@@ -315,18 +314,17 @@
       .Set("is_default", true);
   for (const KeyValue& kv : kMandatorySearchProviderKeyValues)
     search_provider.Set(kv.key, kv.value);
-  std::unique_ptr<base::DictionaryValue> search_provider_with_all_keys_dict =
-      search_provider.Build();
+  base::Value::Dict search_provider_with_all_keys_dict =
+      search_provider.BuildDict();
 
   // Missing all keys from |kMandatorySearchProviderValues|.
   for (const KeyValue& kv : kMandatorySearchProviderKeyValues) {
     SCOPED_TRACE(testing::Message()
                  << "key = " << kv.key << " value = " << kv.value);
     // Build a search provider entry with |kv.key| missing:
-    std::unique_ptr<base::DictionaryValue> provider_with_missing_key =
-        base::DictionaryValue::From(base::Value::ToUniquePtrValue(
-            search_provider_with_all_keys_dict->Clone()));
-    ASSERT_TRUE(provider_with_missing_key->RemovePath(kv.key));
+    base::Value::Dict provider_with_missing_key =
+        search_provider_with_all_keys_dict.Clone();
+    ASSERT_TRUE(provider_with_missing_key.Remove(kv.key));
 
     std::string error;
     scoped_refptr<Extension> extension = CreateExtensionWithSearchProvider(
diff --git a/chrome/common/extensions/manifest_unittest.cc b/chrome/common/extensions/manifest_unittest.cc
index 777a828..82a453d 100644
--- a/chrome/common/extensions/manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_unittest.cc
@@ -57,7 +57,7 @@
   void MutateManifest(std::unique_ptr<Manifest>* manifest,
                       const std::string& key,
                       std::unique_ptr<base::Value> value) {
-    base::Value::Dict manifest_value = (*manifest)->value()->GetDict().Clone();
+    base::Value::Dict manifest_value = (*manifest)->value()->Clone();
     if (value)
       manifest_value.SetByDottedPath(key, std::move(*value));
     else
@@ -71,7 +71,7 @@
   // and uses the |for_login_screen| during creation to determine its type.
   void MutateManifestForLoginScreen(std::unique_ptr<Manifest>* manifest,
                                     bool for_login_screen) {
-    auto manifest_value = (*manifest)->value()->GetDict().Clone();
+    auto manifest_value = (*manifest)->value()->Clone();
     ExtensionId extension_id = manifest->get()->extension_id();
     if (for_login_screen) {
       *manifest = Manifest::CreateManifestForLoginScreen(
@@ -119,7 +119,7 @@
 
   // Test EqualsForTesting.
   auto manifest2 = std::make_unique<Manifest>(
-      ManifestLocation::kInternal, manifest->value()->GetDict().Clone(),
+      ManifestLocation::kInternal, manifest->value()->Clone(),
       crx_file::id_util::GenerateId("extid"));
   EXPECT_TRUE(manifest->EqualsForTesting(*manifest2));
   EXPECT_TRUE(manifest2->EqualsForTesting(*manifest));
diff --git a/chrome/common/extensions/permissions/permissions_data_unittest.cc b/chrome/common/extensions/permissions/permissions_data_unittest.cc
index a84d0b6..a01f0352 100644
--- a/chrome/common/extensions/permissions/permissions_data_unittest.cc
+++ b/chrome/common/extensions/permissions/permissions_data_unittest.cc
@@ -86,8 +86,8 @@
                        .Set("description", "an extension")
                        .Set("manifest_version", 2)
                        .Set("version", "1.0.0")
-                       .Set("permissions", permissions.Build())
-                       .Build())
+                       .Set("permissions", permissions.BuildList())
+                       .BuildDict())
       .SetLocation(location)
       .SetID(id)
       .Build();
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 118563e5..6cfe931 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -184,7 +184,7 @@
     "https://support.google.com/chrome/?p=is_chrome_managed";
 #endif
 
-const char kMixedContentDownloadBlockingLearnMoreUrl[] =
+const char kInsecureDownloadBlockingLearnMoreUrl[] =
     "https://support.google.com/chrome/?p=mixed_content_downloads";
 
 const char kMyActivityUrlInClearBrowsingData[] =
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index fdc1c71f..3d1d8eb8 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -194,8 +194,8 @@
 // The URL for the Learn More page about policies and enterprise enrollment.
 extern const char kManagedUiLearnMoreUrl[];
 
-// The URL for the "Learn more" page for mixed content download blocking.
-extern const char kMixedContentDownloadBlockingLearnMoreUrl[];
+// The URL for the "Learn more" page for insecure download blocking.
+extern const char kInsecureDownloadBlockingLearnMoreUrl[];
 
 // "myactivity.google.com" URL for the history checkbox in ClearBrowsingData.
 extern const char kMyActivityUrlInClearBrowsingData[];
diff --git a/chrome/renderer/extensions/api/app_hooks_delegate.cc b/chrome/renderer/extensions/api/app_hooks_delegate.cc
index 0a01c2d..fe4ea59 100644
--- a/chrome/renderer/extensions/api/app_hooks_delegate.cc
+++ b/chrome/renderer/extensions/api/app_hooks_delegate.cc
@@ -151,11 +151,10 @@
   if (!extension)
     return v8::Null(isolate);
 
-  auto manifest_copy = base::DictionaryValue::From(
-      base::Value::ToUniquePtrValue(extension->manifest()->value()->Clone()));
-  manifest_copy->SetStringKey("id", extension->id());
+  base::Value::Dict manifest_copy = extension->manifest()->value()->Clone();
+  manifest_copy.Set("id", extension->id());
   return content::V8ValueConverter::Create()->ToV8Value(
-      *manifest_copy, script_context->v8_context());
+      manifest_copy, script_context->v8_context());
 }
 
 void AppHooksDelegate::GetInstallState(ScriptContext* script_context,
diff --git a/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc b/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc
index 4563c1e..11a467cf 100644
--- a/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc
+++ b/chrome/renderer/extensions/renderer_permissions_policy_delegate_unittest.cc
@@ -49,13 +49,13 @@
 
 scoped_refptr<const Extension> CreateTestExtension(const std::string& id) {
   return ExtensionBuilder()
-      .SetManifest(
-          DictionaryBuilder()
-              .Set("name", "Extension with ID " + id)
-              .Set("version", "1.0")
-              .Set("manifest_version", 2)
-              .Set("permissions", ListBuilder().Append("<all_urls>").Build())
-              .Build())
+      .SetManifest(DictionaryBuilder()
+                       .Set("name", "Extension with ID " + id)
+                       .Set("version", "1.0")
+                       .Set("manifest_version", 2)
+                       .Set("permissions",
+                            ListBuilder().Append("<all_urls>").BuildList())
+                       .BuildDict())
       .SetID(id)
       .Build();
 }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 1b4dbbb4f..34406a6a 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2565,6 +2565,7 @@
       sources += [
         "../browser/chrome_browser_main_win_browsertest.cc",
         "../browser/chrome_main_process_singleton_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",
         "../browser/importer/edge_importer_browsertest_win.cc",
@@ -2594,6 +2595,7 @@
         "//chrome:other_version",
         "//chrome/app:chrome_dll_resources",
         "//chrome/app:command_ids",
+        "//chrome/browser/enterprise/platform_auth:test_utils",
         "//chrome/browser/safe_browsing/chrome_cleaner",
         "//chrome/browser/safe_browsing/chrome_cleaner:public",
         "//chrome/browser/ui/startup:buildflags",
@@ -5721,6 +5723,11 @@
         [ "../browser/policy/browser_dm_token_storage_linux_unittest.cc" ]
   }
 
+  if (is_chromeos_ash) {
+    sources +=
+        [ "../browser/metrics/chrome_metrics_service_client_ash_unittest.cc" ]
+  }
+
   if (enable_downgrade_processing) {
     sources += [
       "../browser/downgrade/snapshot_file_collector_unittest.cc",
@@ -6152,8 +6159,8 @@
     "//v8",
   ]
 
-  if (enable_server_based_recognition) {
-    deps += [ "//chrome/services/speech/internal:unit_tests" ]
+  if (is_chromeos) {
+    deps += [ "//chromeos/dbus/tpm_manager:tpm_manager" ]
   }
 
   public_deps = []
@@ -6166,6 +6173,10 @@
       "../browser/chrome_process_singleton_win_unittest.cc",
       "../browser/component_updater/sw_reporter_installer_win_unittest.cc",
       "../browser/device_reauth/win/biometric_authenticator_win_unittest.cc",
+      "../browser/enterprise/platform_auth/cloud_ap_provider_win_unittest.cc",
+      "../browser/enterprise/platform_auth/cloud_ap_utils_win_unittest.cc",
+      "../browser/enterprise/platform_auth/platform_auth_navigation_throttle_unittest.cc",
+      "../browser/enterprise/platform_auth/platform_auth_provider_manager_unittest.cc",
       "../browser/install_verification/win/module_info_unittest.cc",
       "../browser/install_verification/win/module_list_unittest.cc",
       "../browser/install_verification/win/module_verification_test.cc",
@@ -6233,6 +6244,7 @@
       "//chrome/app:chrome_dll_resources",
       "//chrome/app:crash_reporter_client_win_unit_tests",
       "//chrome/browser:chrome_process_finder",
+      "//chrome/browser/enterprise/platform_auth:test_utils",
       "//chrome/browser/safe_browsing/chrome_cleaner",
       "//chrome/browser/safe_browsing/chrome_cleaner:public",
       "//chrome/browser/win/conflicts:unit_tests",
diff --git a/chrome/test/data/extensions/api_test/isolated_world_csp/mv3/eval.js b/chrome/test/data/extensions/api_test/isolated_world_csp/mv3/eval.js
index dcd2443..ca5fd16 100644
--- a/chrome/test/data/extensions/api_test/isolated_world_csp/mv3/eval.js
+++ b/chrome/test/data/extensions/api_test/isolated_world_csp/mv3/eval.js
@@ -6,8 +6,7 @@
   window.foo = 2;
   var exceptedExceptionMessage = 'Refused to evaluate a string as JavaScript ' +
       'because \'unsafe-eval\' is not an allowed source of script in the ' +
-      'following Content Security Policy directive: "script-src \'self\' ' +
-      '\'wasm-unsafe-eval\'"';
+      'following Content Security Policy directive';
   chrome.test.assertThrows(
       eval, ['window.foo = 3;'], new RegExp(exceptedExceptionMessage));
   chrome.test.assertEq(2, window.foo);
diff --git a/chrome/test/data/extensions/api_test/socket/api/multicast.js b/chrome/test/data/extensions/api_test/socket/api/multicast.js
index 616b8dd..fb5e070 100644
--- a/chrome/test/data/extensions/api_test/socket/api/multicast.js
+++ b/chrome/test/data/extensions/api_test/socket/api/multicast.js
@@ -163,8 +163,7 @@
       var canceller = waitForMessage(serverSocketId, function (cancelled) {
         clearTimeout(recvTimeout);
         if (cancelled) {
-          socket.destroy(serverSocketId);
-          chrome.test.succeed();
+          leaveGroupAndDisconnect(serverSocketId);
         } else {
           chrome.test.fail("Received message after leaving the group");
           socket.destroy(serverSocketId);
@@ -173,9 +172,20 @@
       testSendMessage(request);
       recvTimeout = setTimeout(function () {
         canceller();
+      }, 2000);
+    });
+  }
+
+  function leaveGroupAndDisconnect(serverSocketId) {
+    socket.joinGroup(serverSocketId, kMulticastAddress, function (result) {
+      chrome.test.assertNoLastError();
+      chrome.test.assertEq(0, result, "Join group failed.");
+      socket.leaveGroup(serverSocketId, kMulticastAddress, () => {
+        chrome.test.assertEq(0, result, "Leave group failed.");
         socket.destroy(serverSocketId);
         chrome.test.succeed();
-      }, 2000);
+      });
+      socket.disconnect(serverSocketId);
     });
   }
 
@@ -195,4 +205,4 @@
   }
 
   testMulticastSettings();
-}
\ No newline at end of file
+}
diff --git a/chrome/test/data/extensions/local_includes/module_pass1.js b/chrome/test/data/extensions/local_includes/module_pass1.js
new file mode 100644
index 0000000..bfab455
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/module_pass1.js
@@ -0,0 +1,7 @@
+// 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.
+
+export function jsPass1() {
+  chrome.test.succeed();
+}
diff --git a/chrome/test/data/extensions/local_includes/module_pass1.js.mock-http-headers b/chrome/test/data/extensions/local_includes/module_pass1.js.mock-http-headers
new file mode 100644
index 0000000..1e5e2552
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/module_pass1.js.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: application/javascript
+Access-Control-Allow-Origin: *
diff --git a/chrome/test/data/extensions/local_includes/module_pass2.js b/chrome/test/data/extensions/local_includes/module_pass2.js
new file mode 100644
index 0000000..3a29a2d
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/module_pass2.js
@@ -0,0 +1,7 @@
+// 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.
+
+export function jsPass2() {
+  chrome.test.succeed();
+}
diff --git a/chrome/test/data/extensions/local_includes/module_pass2.js.mock-http-headers b/chrome/test/data/extensions/local_includes/module_pass2.js.mock-http-headers
new file mode 100644
index 0000000..1e5e2552
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/module_pass2.js.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: application/javascript
+Access-Control-Allow-Origin: *
diff --git a/chrome/test/data/extensions/local_includes/pass1.js b/chrome/test/data/extensions/local_includes/pass1.js
new file mode 100644
index 0000000..f2d7a49
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/pass1.js
@@ -0,0 +1,7 @@
+// 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.
+
+function jsPass1() {
+  chrome.test.succeed();
+}
diff --git a/chrome/test/data/extensions/local_includes/pass2.js b/chrome/test/data/extensions/local_includes/pass2.js
new file mode 100644
index 0000000..261f972
--- /dev/null
+++ b/chrome/test/data/extensions/local_includes/pass2.js
@@ -0,0 +1,7 @@
+// 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.
+
+function jsPass2() {
+  chrome.test.succeed();
+}
diff --git a/chrome/test/data/webui/downloads/item_tests.ts b/chrome/test/data/webui/downloads/item_tests.ts
index b15c74a3..fc72b21 100644
--- a/chrome/test/data/webui/downloads/item_tests.ts
+++ b/chrome/test/data/webui/downloads/item_tests.ts
@@ -130,8 +130,8 @@
     assertTrue(toastManager.slottedHidden);
   });
 
-  test('undo is not shown in toast when item is mixed content', () => {
-    item.data = createDownload({hideDate: false, isMixedContent: true});
+  test('undo is not shown in toast when item is insecure', () => {
+    item.data = createDownload({hideDate: false, isInsecure: true});
     toastManager.show('', /* hideSlotted= */ false);
     assertFalse(toastManager.slottedHidden);
     item.$.remove.click();
diff --git a/chrome/test/data/webui/downloads/manager_tests.ts b/chrome/test/data/webui/downloads/manager_tests.ts
index 4206846..a6c28fa 100644
--- a/chrome/test/data/webui/downloads/manager_tests.ts
+++ b/chrome/test/data/webui/downloads/manager_tests.ts
@@ -201,7 +201,7 @@
   test('undo is not shown when removing only dangerous items', async () => {
     callbackRouterRemote.insertItems(0, [
       createDownload({isDangerous: true}),
-      createDownload({isMixedContent: true}),
+      createDownload({isInsecure: true}),
     ]);
     await callbackRouterRemote.$.flushForTesting();
     toastManager.show('', /* hideSlotted= */ false);
@@ -214,7 +214,7 @@
     callbackRouterRemote.insertItems(0, [
       createDownload(),
       createDownload({isDangerous: true}),
-      createDownload({isMixedContent: true}),
+      createDownload({isInsecure: true}),
     ]);
     await callbackRouterRemote.$.flushForTesting();
     toastManager.show('', /* hideSlotted= */ true);
diff --git a/chrome/test/data/webui/downloads/test_support.ts b/chrome/test/data/webui/downloads/test_support.ts
index efe7437..3ed49fb 100644
--- a/chrome/test/data/webui/downloads/test_support.ts
+++ b/chrome/test/data/webui/downloads/test_support.ts
@@ -88,7 +88,7 @@
         hideDate: false,
         id: '123',
         isDangerous: false,
-        isMixedContent: false,
+        isInsecure: false,
         isReviewable: false,
         lastReasonText: '',
         otr: false,
diff --git a/chrome/test/data/webui/downloads/toolbar_tests.ts b/chrome/test/data/webui/downloads/toolbar_tests.ts
index 76fc58a7..5ad0d1e 100644
--- a/chrome/test/data/webui/downloads/toolbar_tests.ts
+++ b/chrome/test/data/webui/downloads/toolbar_tests.ts
@@ -72,7 +72,7 @@
   test('undo is not shown when removing only dangerous items', () => {
     toolbar.items = [
       createDownload({isDangerous: true}),
-      createDownload({isMixedContent: true}),
+      createDownload({isInsecure: true}),
     ];
     toastManager.show('', /* hideSlotted= */ false);
     assertFalse(toastManager.slottedHidden);
@@ -87,7 +87,7 @@
     toolbar.items = [
       createDownload(),
       createDownload({isDangerous: true}),
-      createDownload({isMixedContent: true}),
+      createDownload({isInsecure: true}),
     ];
     toastManager.show('', /* hideSlotted= */ true);
     assertTrue(toastManager.slottedHidden);
diff --git a/chrome/test/data/webui/gaia_auth_host/gaia_auth_host_browsertest.js b/chrome/test/data/webui/gaia_auth_host/gaia_auth_host_browsertest.js
index 718d4d8..bc36111 100644
--- a/chrome/test/data/webui/gaia_auth_host/gaia_auth_host_browsertest.js
+++ b/chrome/test/data/webui/gaia_auth_host/gaia_auth_host_browsertest.js
@@ -18,11 +18,6 @@
   get isAsync() {
     return true;
   }
-
-  /** @override */
-  get webuiHost() {
-    return 'chrome-signin';
-  }
 };
 
 [['PasswordChangeAuthenticator', 'password_change_authenticator_test.js'],
@@ -37,7 +32,7 @@
   this[className] = class extends GaiaAuthHostBrowserTest {
     /** @override */
     get browsePreload() {
-      return `chrome://webui-test/test_loader.html?module=gaia_auth_host/${
+      return `chrome://chrome-signin/test_loader.html?module=gaia_auth_host/${
           module}`;
     }
   };
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index 76c2ae7..3bdbb32 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -34,6 +34,7 @@
   "test_about_page_browser_proxy.ts",
   "test_lifetime_browser_proxy.ts",
   "test_privacy_page_browser_proxy.ts",
+  "test_security_keys_browser_proxy.ts",
   "test_sync_browser_proxy.ts",
 ]
 
@@ -110,7 +111,11 @@
   "search_page_test.ts",
   "search_settings_test.ts",
   "secure_dns_interactive_test.ts",
-  "security_keys_subpage_test.ts",
+  "security_keys_bio_enrollment_test.ts",
+  "security_keys_credential_management_test.ts",
+  "security_keys_reset_dialog_test.ts",
+  "security_keys_set_pin_dialog_test.ts",
+  "security_keys_test_util.ts",
   "security_keys_phones_subpage_test.ts",
   "settings_animated_pages_test.ts",
   "settings_category_default_radio_group_tests.ts",
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index ca8729b..8e051636 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -901,6 +901,13 @@
  ['SearchEngines', 'search_engines_page_test.js'],
  ['SearchPage', 'search_page_test.js'],
  ['Search', 'search_settings_test.js'],
+ ['SecurityKeysBioEnrollment', 'security_keys_bio_enrollment_test.js'],
+ [
+   'SecurityKeysCredentialManagement',
+   'security_keys_credential_management_test.js'
+ ],
+ ['SecurityKeysResetDialog', 'security_keys_reset_dialog_test.js'],
+ ['SecurityKeysSetPinDialog', 'security_keys_set_pin_dialog_test.js'],
  ['SecurityKeysPhonesSubpage', 'security_keys_phones_subpage_test.js'],
  ['SecureDns', 'secure_dns_test.js'],
  ['SiteDetailsPermission', 'site_details_permission_tests.js'],
@@ -982,11 +989,6 @@
 registerTest('MetricsReporting', 'metrics_reporting_tests.js');
 GEN('#endif');
 
-// TODO(crbug.com/1395417): Flaky on Linux
-GEN('#if !BUILDFLAG(IS_LINUX)');
-registerTest('SecurityKeysSubpage', 'security_keys_subpage_test.js');
-GEN('#endif');
-
 function registerTest(testName, module, caseName) {
   const className = `CrSettings${testName}Test`;
   this[className] = class extends CrSettingsBrowserTest {
diff --git a/chrome/test/data/webui/settings/security_keys_bio_enrollment_test.ts b/chrome/test/data/webui/settings/security_keys_bio_enrollment_test.ts
new file mode 100644
index 0000000..57691c9
--- /dev/null
+++ b/chrome/test/data/webui/settings/security_keys_bio_enrollment_test.ts
@@ -0,0 +1,376 @@
+// 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 {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {BioEnrollDialogPage, Ctap2Status, SampleStatus, SecurityKeysBioEnrollProxy, SecurityKeysBioEnrollProxyImpl, SettingsSecurityKeysBioEnrollDialogElement} from 'chrome://settings/lazy_load.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+import {assertShown} from './security_keys_test_util.js';
+import {TestSecurityKeysBrowserProxy} from './test_security_keys_browser_proxy.js';
+
+const currentMinPinLength = 6;
+
+class TestSecurityKeysBioEnrollProxy extends TestSecurityKeysBrowserProxy
+    implements SecurityKeysBioEnrollProxy {
+  constructor() {
+    super([
+      'startBioEnroll',
+      'providePin',
+      'getSensorInfo',
+      'enumerateEnrollments',
+      'startEnrolling',
+      'cancelEnrollment',
+      'deleteEnrollment',
+      'renameEnrollment',
+      'close',
+    ]);
+  }
+
+  startBioEnroll() {
+    return this.handleMethod('startBioEnroll');
+  }
+
+  providePin(pin: string) {
+    return this.handleMethod('providePin', pin);
+  }
+
+  getSensorInfo() {
+    return this.handleMethod('getSensorInfo');
+  }
+
+  enumerateEnrollments() {
+    return this.handleMethod('enumerateEnrollments');
+  }
+
+  startEnrolling() {
+    return this.handleMethod('startEnrolling');
+  }
+
+  cancelEnrollment() {
+    return this.methodCalled('cancelEnrollment');
+  }
+
+  deleteEnrollment(id: string) {
+    return this.handleMethod('deleteEnrollment', id);
+  }
+
+  renameEnrollment(id: string, name: string) {
+    return this.handleMethod('renameEnrollment', [id, name]);
+  }
+
+  close() {
+    this.methodCalled('close');
+  }
+}
+
+suite('SecurityKeysBioEnrollment', function() {
+  let dialog: SettingsSecurityKeysBioEnrollDialogElement;
+  let allDivs: string[];
+  let browserProxy: TestSecurityKeysBioEnrollProxy;
+
+  setup(function() {
+    browserProxy = new TestSecurityKeysBioEnrollProxy();
+    SecurityKeysBioEnrollProxyImpl.setInstance(browserProxy);
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    dialog = document.createElement('settings-security-keys-bio-enroll-dialog');
+    allDivs = Object.values(BioEnrollDialogPage);
+  });
+
+  test('Initialization', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+    assertFalse(dialog.$.cancelButton.hidden);
+  });
+
+  test('Cancel', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+    dialog.$.cancelButton.click();
+    await browserProxy.whenCalled('close');
+    assertFalse(dialog.$.dialog.open);
+  });
+
+  test('Finished', async function() {
+    const resolver = new PromiseResolver();
+    browserProxy.setResponseFor('startBioEnroll', resolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+    resolver.resolve([currentMinPinLength]);
+
+    const error = 'foo bar baz';
+    webUIListenerCallback('security-keys-bio-enroll-error', error);
+    assertShown(allDivs, dialog, 'error');
+    assertTrue(dialog.$.confirmButton.hidden);
+    assertTrue(dialog.$.error.textContent!.trim().includes(error));
+  });
+
+  test('PINChangeError', async function() {
+    const resolver = new PromiseResolver();
+    browserProxy.setResponseFor('startBioEnroll', resolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+    resolver.resolve([currentMinPinLength]);
+
+    const error = 'something about setting a new PIN';
+    webUIListenerCallback(
+        'security-keys-bio-enroll-error', error, true /* requiresPINChange */);
+    assertShown(allDivs, dialog, 'error');
+    assertFalse(dialog.$.confirmButton.hidden);
+    assertFalse(dialog.$.confirmButton.disabled);
+    assertTrue(dialog.$.error.textContent!.trim().includes(error));
+
+    const setPinEvent = eventToPromise('bio-enroll-set-pin', dialog);
+    dialog.$.confirmButton.click();
+    await setPinEvent;
+  });
+
+  test('Enrollments', async function() {
+    const startResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startBioEnroll', startResolver.promise);
+    const pinResolver = new PromiseResolver();
+    browserProxy.setResponseFor('providePin', pinResolver.promise);
+    const getSensorInfoResolver = new PromiseResolver();
+    browserProxy.setResponseFor('getSensorInfo', getSensorInfoResolver.promise);
+    const enumerateResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'enumerateEnrollments', enumerateResolver.promise);
+    const deleteResolver = new PromiseResolver();
+    browserProxy.setResponseFor('deleteEnrollment', deleteResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+
+    // Simulate PIN entry.
+    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    startResolver.resolve([currentMinPinLength]);
+    await uiReady;
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
+    dialog.$.pin.$.pin.value = '000000';
+    dialog.$.confirmButton.click();
+    const pin = await browserProxy.whenCalled('providePin');
+    assertEquals(pin, '000000');
+    pinResolver.resolve(null);
+
+    await browserProxy.whenCalled('getSensorInfo');
+    getSensorInfoResolver.resolve({
+      maxTemplateFriendlyName: 10,
+    });
+
+    // Show a list of three enrollments.
+    await browserProxy.whenCalled('enumerateEnrollments');
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+
+    const fingerprintA = {
+      name: 'FingerprintA',
+      id: '1234',
+    };
+    const fingerprintB = {
+      name: 'FingerprintB',
+      id: '4321',
+    };
+    const fingerprintC = {
+      name: 'FingerprintC',
+      id: '000000',
+    };
+    const enrollments = [fingerprintC, fingerprintB, fingerprintA];
+    const sortedEnrollments = [fingerprintA, fingerprintB, fingerprintC];
+    enumerateResolver.resolve(enrollments);
+    await uiReady;
+    assertShown(allDivs, dialog, 'enrollments');
+    assertDeepEquals(dialog.$.enrollmentList.items, sortedEnrollments);
+
+    // Delete the second enrollments and refresh the list.
+    flush();
+    dialog.$.enrollmentList.querySelectorAll('cr-icon-button')[1]!.click();
+    const id = await browserProxy.whenCalled('deleteEnrollment');
+    assertEquals(sortedEnrollments[1]!.id, id);
+    sortedEnrollments.splice(1, 1);
+    enrollments.splice(1, 1);
+    deleteResolver.resolve(enrollments);
+    await uiReady;
+    assertShown(allDivs, dialog, 'enrollments');
+    assertDeepEquals(dialog.$.enrollmentList.items, sortedEnrollments);
+  });
+
+  test('AddEnrollment', async function() {
+    const startResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startBioEnroll', startResolver.promise);
+    const pinResolver = new PromiseResolver();
+    browserProxy.setResponseFor('providePin', pinResolver.promise);
+    const getSensorInfoResolver = new PromiseResolver();
+    browserProxy.setResponseFor('getSensorInfo', getSensorInfoResolver.promise);
+    const enumerateResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'enumerateEnrollments', enumerateResolver.promise);
+    const enrollingResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startEnrolling', enrollingResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+    assertShown(allDivs, dialog, 'initial');
+
+    // Simulate PIN entry.
+    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    startResolver.resolve([currentMinPinLength]);
+    await uiReady;
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
+    dialog.$.pin.$.pin.value = '000000';
+    dialog.$.confirmButton.click();
+    const pin = await browserProxy.whenCalled('providePin');
+    assertEquals(pin, '000000');
+    pinResolver.resolve(null);
+
+    await browserProxy.whenCalled('getSensorInfo');
+    getSensorInfoResolver.resolve({
+      maxTemplateFriendlyName: 20,
+    });
+
+    // Ensure no enrollments exist.
+    await browserProxy.whenCalled('enumerateEnrollments');
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    enumerateResolver.resolve([]);
+    await uiReady;
+    assertShown(allDivs, dialog, 'enrollments');
+    assertEquals(dialog.$.enrollmentList.items!.length, 0);
+
+    // Simulate add enrollment.
+    assertFalse(dialog.$.addButton.hidden);
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    dialog.$.addButton.click();
+    await browserProxy.whenCalled('startEnrolling');
+    await uiReady;
+
+    assertShown(allDivs, dialog, 'enroll');
+    webUIListenerCallback(
+        'security-keys-bio-enroll-status',
+        {status: SampleStatus.OK, remaining: 1});
+    flush();
+    assertFalse(dialog.$.arc.isComplete());
+    assertFalse(dialog.$.cancelButton.hidden);
+    assertTrue(dialog.$.confirmButton.hidden);
+
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    const enrollmentId = 'someId';
+    const enrollmentName = 'New Fingerprint';
+    enrollingResolver.resolve({
+      code: 0,
+      enrollment: {
+        id: enrollmentId,
+        name: enrollmentName,
+      },
+    });
+    await uiReady;
+    assertTrue(dialog.$.arc.isComplete());
+    assertTrue(dialog.$.cancelButton.hidden);
+    assertFalse(dialog.$.confirmButton.hidden);
+
+    // Proceeding brings up rename dialog page.
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    dialog.$.confirmButton.click();
+    await uiReady;
+
+    // Try renaming with a name that's longer than |maxTemplateFriendlyName|.
+    assertShown(allDivs, dialog, 'chooseName');
+    assertEquals(dialog.$.enrollmentName.value, enrollmentName);
+    const invalidNewEnrollmentName = '21 bytes long string!';
+    dialog.$.enrollmentName.value = invalidNewEnrollmentName;
+    assertFalse(dialog.$.confirmButton.hidden);
+    assertFalse(dialog.$.confirmButton.disabled);
+    assertFalse(dialog.$.enrollmentName.invalid);
+    dialog.$.confirmButton.click();
+    assertTrue(dialog.$.enrollmentName.invalid);
+    assertEquals(browserProxy.getCallCount('renameEnrollment'), 0);
+
+    // Try renaming to a valid name.
+    assertShown(allDivs, dialog, 'chooseName');
+    const newEnrollmentName = '20 bytes long string';
+    dialog.$.enrollmentName.value = newEnrollmentName;
+    assertFalse(dialog.$.confirmButton.hidden);
+    assertFalse(dialog.$.confirmButton.disabled);
+
+    // Proceeding renames the enrollment and returns to the enrollment overview.
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    const renameEnrollmentResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'renameEnrollment', renameEnrollmentResolver.promise);
+    dialog.$.confirmButton.click();
+    assertFalse(dialog.$.enrollmentName.invalid);
+
+    const renameArgs = await browserProxy.whenCalled('renameEnrollment');
+    assertDeepEquals(renameArgs, [enrollmentId, newEnrollmentName]);
+    renameEnrollmentResolver.resolve([]);
+    await uiReady;
+    assertShown(allDivs, dialog, 'enrollments');
+  });
+
+  test('EnrollCancel', async function() {
+    // Simulate starting an enrollment and then cancelling it.
+    browserProxy.setResponseFor('enumerateEnrollments', Promise.resolve([]));
+    const enrollResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startEnrolling', enrollResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+
+    dialog.setDialogPageForTesting(BioEnrollDialogPage.ENROLLMENTS);
+
+    // Forcibly disable the cancel button to ensure showing the dialog page
+    // re-enables it.
+    dialog.setCancelButtonDisabledForTesting(true);
+
+    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    dialog.$.addButton.click();
+    await browserProxy.whenCalled('startEnrolling');
+    await uiReady;
+
+    assertShown(allDivs, dialog, 'enroll');
+    assertFalse(dialog.$.cancelButton.disabled);
+    assertFalse(dialog.$.cancelButton.hidden);
+
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    dialog.$.cancelButton.click();
+    await browserProxy.whenCalled('cancelEnrollment');
+    enrollResolver.resolve({code: Ctap2Status.ERR_KEEPALIVE_CANCEL});
+    await browserProxy.whenCalled('enumerateEnrollments');
+
+    await uiReady;
+    assertShown(allDivs, dialog, 'enrollments');
+  });
+
+  test('EnrollError', async function() {
+    // Test that resolving the startEnrolling promise with a CTAP error brings
+    // up the error page.
+    const enrollResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startEnrolling', enrollResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startBioEnroll');
+
+    dialog.setDialogPageForTesting(BioEnrollDialogPage.ENROLLMENTS);
+
+    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+    dialog.$.addButton.click();
+    await browserProxy.whenCalled('startEnrolling');
+    await uiReady;
+
+    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
+
+    enrollResolver.resolve({code: Ctap2Status.ERR_INVALID_OPTION});
+
+    await uiReady;
+    assertShown(allDivs, dialog, 'error');
+  });
+});
diff --git a/chrome/test/data/webui/settings/security_keys_credential_management_test.ts b/chrome/test/data/webui/settings/security_keys_credential_management_test.ts
new file mode 100644
index 0000000..fa8ff63
--- /dev/null
+++ b/chrome/test/data/webui/settings/security_keys_credential_management_test.ts
@@ -0,0 +1,292 @@
+// 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 {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CredentialManagementDialogPage, CrIconButtonElement, SecurityKeysCredentialBrowserProxy, SecurityKeysCredentialBrowserProxyImpl, SettingsSecurityKeysCredentialManagementDialogElement} from 'chrome://settings/lazy_load.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+import {assertShown} from './security_keys_test_util.js';
+import {TestSecurityKeysBrowserProxy} from './test_security_keys_browser_proxy.js';
+
+const currentMinPinLength = 6;
+
+class TestSecurityKeysCredentialBrowserProxy extends
+    TestSecurityKeysBrowserProxy implements SecurityKeysCredentialBrowserProxy {
+  constructor() {
+    super([
+      'startCredentialManagement',
+      'providePin',
+      'enumerateCredentials',
+      'deleteCredentials',
+      'updateUserInformation',
+      'close',
+    ]);
+  }
+
+  startCredentialManagement() {
+    return this.handleMethod('startCredentialManagement');
+  }
+
+  providePin(pin: string) {
+    return this.handleMethod('providePin', pin);
+  }
+
+  enumerateCredentials() {
+    return this.handleMethod('enumerateCredentials');
+  }
+
+  deleteCredentials(ids: string[]) {
+    return this.handleMethod('deleteCredentials', ids);
+  }
+
+  updateUserInformation(
+      credentialId: string, userHandle: string, newUsername: string,
+      newDisplayname: string) {
+    return this.handleMethod(
+        'updateUserInformation',
+        {credentialId, userHandle, newUsername, newDisplayname});
+  }
+
+  close() {
+    this.methodCalled('close');
+  }
+}
+
+suite('SecurityKeysCredentialManagement', function() {
+  let dialog: SettingsSecurityKeysCredentialManagementDialogElement;
+  let allDivs: string[];
+  let browserProxy: TestSecurityKeysCredentialBrowserProxy;
+
+  setup(function() {
+    browserProxy = new TestSecurityKeysCredentialBrowserProxy();
+    SecurityKeysCredentialBrowserProxyImpl.setInstance(browserProxy);
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    dialog = document.createElement(
+        'settings-security-keys-credential-management-dialog');
+    allDivs = Object.values(CredentialManagementDialogPage);
+  });
+
+  test('Initialization', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+  });
+
+  test('Cancel', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+    dialog.$.cancelButton.click();
+    await browserProxy.whenCalled('close');
+    assertFalse(dialog.$.dialog.open);
+  });
+
+  test('Finished', async function() {
+    const startResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'startCredentialManagement', startResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+    startResolver.resolve({
+      minPinLength: currentMinPinLength,
+      supportsUpdateUserInformation: true,
+    });
+
+    const error = 'foo bar baz';
+    webUIListenerCallback(
+        'security-keys-credential-management-finished', error);
+    assertShown(allDivs, dialog, 'pinError');
+    assertTrue(dialog.$.error.textContent!.trim().includes(error));
+  });
+
+  test('PINChangeError', async function() {
+    const startResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'startCredentialManagement', startResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+    startResolver.resolve({
+      minPinLength: currentMinPinLength,
+      supportsUpdateUserInformation: true,
+    });
+
+    const error = 'foo bar baz';
+    webUIListenerCallback(
+        'security-keys-credential-management-finished', error,
+        true /* requiresPINChange */);
+    assertShown(allDivs, dialog, 'pinError');
+    assertFalse(dialog.$.confirmButton.hidden);
+    assertFalse(dialog.$.confirmButton.disabled);
+    assertTrue(dialog.$.pinError.textContent!.trim().includes(error));
+
+    const setPinEvent = eventToPromise('credential-management-set-pin', dialog);
+    dialog.$.confirmButton.click();
+    await setPinEvent;
+  });
+
+  test('UpdateNotSupported', async function() {
+    const startCredentialManagementResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'startCredentialManagement', startCredentialManagementResolver.promise);
+    const pinResolver = new PromiseResolver();
+    browserProxy.setResponseFor('providePin', pinResolver.promise);
+    const enumerateResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'enumerateCredentials', enumerateResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+
+    // Simulate PIN entry.
+    let uiReady = eventToPromise(
+        'credential-management-dialog-ready-for-testing', dialog);
+    startCredentialManagementResolver.resolve({
+      minPinLength: currentMinPinLength,
+      supportsUpdateUserInformation: false,
+    });
+
+    await uiReady;
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
+    dialog.$.pin.$.pin.value = '000000';
+    dialog.$.confirmButton.click();
+    const pin = await browserProxy.whenCalled('providePin');
+    assertEquals(pin, '000000');
+
+    // Show a credential.
+    pinResolver.resolve(null);
+    await browserProxy.whenCalled('enumerateCredentials');
+    uiReady = eventToPromise(
+        'credential-management-dialog-ready-for-testing', dialog);
+    const credentials = [
+      {
+        credentialId: 'aaaaaa',
+        relyingPartyId: 'acme.com',
+        userHandle: 'userausera',
+        userName: 'userA@example.com',
+        userDisplayName: 'User Aaa',
+      },
+    ];
+    enumerateResolver.resolve(credentials);
+    await uiReady;
+    assertShown(allDivs, dialog, 'credentials');
+    assertEquals(dialog.$.credentialList.items, credentials);
+
+    // Check that the edit button is disabled.
+    flush();
+    const editButtons: CrIconButtonElement[] =
+        Array.from(dialog.$.credentialList.querySelectorAll('.edit-button'));
+    assertEquals(editButtons.length, 1);
+    assertTrue(editButtons[0]!.hidden);
+  });
+
+  test('Credentials', async function() {
+    const startCredentialManagementResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'startCredentialManagement', startCredentialManagementResolver.promise);
+    const pinResolver = new PromiseResolver();
+    browserProxy.setResponseFor('providePin', pinResolver.promise);
+    const enumerateResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'enumerateCredentials', enumerateResolver.promise);
+    const deleteResolver = new PromiseResolver();
+    browserProxy.setResponseFor('deleteCredentials', deleteResolver.promise);
+    const updateUserInformationResolver = new PromiseResolver();
+    browserProxy.setResponseFor(
+        'updateUserInformation', updateUserInformationResolver.promise);
+
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startCredentialManagement');
+    assertShown(allDivs, dialog, 'initial');
+
+    // Simulate PIN entry.
+    let uiReady = eventToPromise(
+        'credential-management-dialog-ready-for-testing', dialog);
+    startCredentialManagementResolver.resolve({
+      minPinLength: currentMinPinLength,
+      supportsUpdateUserInformation: true,
+    });
+    await uiReady;
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
+    dialog.$.pin.$.pin.value = '000000';
+    dialog.$.confirmButton.click();
+    const pin = await browserProxy.whenCalled('providePin');
+    assertEquals(pin, '000000');
+
+    // Show a list of three credentials.
+    pinResolver.resolve(null);
+    await browserProxy.whenCalled('enumerateCredentials');
+    uiReady = eventToPromise(
+        'credential-management-dialog-ready-for-testing', dialog);
+    const credentials = [
+      {
+        credentialId: 'aaaaaa',
+        relyingPartyId: 'acme.com',
+        userHandle: 'userausera',
+        userName: 'userA@example.com',
+        userDisplayName: 'User Aaa',
+      },
+      {
+        credentialId: 'bbbbbb',
+        relyingPartyId: 'acme.com',
+        userHandle: 'userbuserb',
+        userName: 'userB@example.com',
+        userDisplayName: 'User Bbb',
+      },
+      {
+        credentialId: 'cccccc',
+        relyingPartyId: 'acme.com',
+        userHandle: 'usercuserc',
+        userName: 'userC@example.com',
+        userDisplayName: 'User Ccc',
+      },
+    ];
+    enumerateResolver.resolve(credentials);
+    await uiReady;
+    assertShown(allDivs, dialog, 'credentials');
+    assertEquals(dialog.$.credentialList.items, credentials);
+
+    // Update a credential
+    flush();
+    const editButtons: CrIconButtonElement[] =
+        Array.from(dialog.$.credentialList.querySelectorAll('.edit-button'));
+    assertEquals(editButtons.length, 3);
+    editButtons.forEach(button => assertFalse(button.hidden));
+    editButtons[0]!.click();
+    assertShown(allDivs, dialog, 'edit');
+    dialog.$.displayNameInput.value = 'Bobby Example';
+    dialog.$.userNameInput.value = 'bobby@example.com';
+    dialog.$.confirmButton.click();
+    credentials[0]!.userDisplayName = 'Bobby Example';
+    credentials[0]!.userName = 'bobby@example.com';
+    updateUserInformationResolver.resolve({success: true, message: 'updated'});
+    assertShown(allDivs, dialog, 'credentials');
+    assertDeepEquals(dialog.$.credentialList.items, credentials);
+
+    // Delete a credential.
+    flush();
+    const deleteButtons: CrIconButtonElement[] =
+        Array.from(dialog.$.credentialList.querySelectorAll('.delete-button'));
+    assertEquals(deleteButtons.length, 3);
+    deleteButtons[0]!.click();
+    assertShown(allDivs, dialog, 'confirm');
+    dialog.$.confirmButton.click();
+    const credentialIds = await browserProxy.whenCalled('deleteCredentials');
+    assertDeepEquals(credentialIds, ['aaaaaa']);
+    uiReady = eventToPromise(
+        'credential-management-dialog-ready-for-testing', dialog);
+    deleteResolver.resolve({success: true, message: 'foobar'});
+    await uiReady;
+    assertShown(allDivs, dialog, 'credentials');
+  });
+});
diff --git a/chrome/test/data/webui/settings/security_keys_reset_dialog_test.ts b/chrome/test/data/webui/settings/security_keys_reset_dialog_test.ts
new file mode 100644
index 0000000..d43c86e
--- /dev/null
+++ b/chrome/test/data/webui/settings/security_keys_reset_dialog_test.ts
@@ -0,0 +1,142 @@
+// 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 {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
+import {ResetDialogPage, SecurityKeysResetBrowserProxy, SecurityKeysResetBrowserProxyImpl, SettingsSecurityKeysResetDialogElement} from 'chrome://settings/lazy_load.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+import {assertShown} from './security_keys_test_util.js';
+import {TestSecurityKeysBrowserProxy} from './test_security_keys_browser_proxy.js';
+
+class TestSecurityKeysResetBrowserProxy extends TestSecurityKeysBrowserProxy
+    implements SecurityKeysResetBrowserProxy {
+  constructor() {
+    super([
+      'reset',
+      'completeReset',
+      'close',
+    ]);
+  }
+
+  override reset() {
+    return this.handleMethod('reset');
+  }
+
+  completeReset() {
+    return this.handleMethod('completeReset');
+  }
+
+  close() {
+    this.methodCalled('close');
+  }
+}
+
+suite('SecurityKeysResetDialog', function() {
+  let dialog: SettingsSecurityKeysResetDialogElement;
+  let allDivs: string[];
+  let browserProxy: TestSecurityKeysResetBrowserProxy;
+
+  setup(function() {
+    browserProxy = new TestSecurityKeysResetBrowserProxy();
+    SecurityKeysResetBrowserProxyImpl.setInstance(browserProxy);
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    dialog = document.createElement('settings-security-keys-reset-dialog');
+    allDivs = Object.values(ResetDialogPage);
+  });
+
+  function assertComplete() {
+    assertEquals(dialog.$.button.textContent!.trim(), 'OK');
+    assertEquals(dialog.$.button.className, 'action-button');
+  }
+
+  function assertNotComplete() {
+    assertEquals(dialog.$.button.textContent!.trim(), 'Cancel');
+    assertEquals(dialog.$.button.className, 'cancel-button');
+  }
+
+  test('Initialization', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('reset');
+    assertShown(allDivs, dialog, 'initial');
+    assertNotComplete();
+  });
+
+  test('Cancel', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('reset');
+    assertShown(allDivs, dialog, 'initial');
+    assertNotComplete();
+    dialog.$.button.click();
+    await browserProxy.whenCalled('close');
+    assertFalse(dialog.$.dialog.open);
+  });
+
+  test('NotSupported', async function() {
+    browserProxy.setResponseFor(
+        'reset', Promise.resolve(1 /* INVALID_COMMAND */));
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('reset');
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'noReset');
+  });
+
+  test('ImmediateUnknownError', async function() {
+    const error = 1000 /* undefined error code */;
+    browserProxy.setResponseFor('reset', Promise.resolve(error));
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('reset');
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'resetFailed');
+    assertTrue(
+        dialog.$.resetFailed.textContent!.trim().includes(error.toString()));
+  });
+
+  test('ImmediateUnknownError', async function() {
+    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
+    const promiseResolver = new PromiseResolver();
+    browserProxy.setResponseFor('completeReset', promiseResolver.promise);
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('reset');
+    await browserProxy.whenCalled('completeReset');
+    assertNotComplete();
+    assertShown(allDivs, dialog, 'resetConfirm');
+    promiseResolver.resolve(0 /* success */);
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'resetSuccess');
+  });
+
+  test('UnknownError', async function() {
+    const error = 1000 /* undefined error code */;
+    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
+    browserProxy.setResponseFor('completeReset', Promise.resolve(error));
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('reset');
+    await browserProxy.whenCalled('completeReset');
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'resetFailed');
+    assertTrue(
+        dialog.$.resetFailed.textContent!.trim().includes(error.toString()));
+  });
+
+  test('ResetRejected', async function() {
+    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
+    browserProxy.setResponseFor(
+        'completeReset', Promise.resolve(48 /* NOT_ALLOWED */));
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('reset');
+    await browserProxy.whenCalled('completeReset');
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'resetNotAllowed');
+  });
+});
diff --git a/chrome/test/data/webui/settings/security_keys_set_pin_dialog_test.ts b/chrome/test/data/webui/settings/security_keys_set_pin_dialog_test.ts
new file mode 100644
index 0000000..9f9a225e
--- /dev/null
+++ b/chrome/test/data/webui/settings/security_keys_set_pin_dialog_test.ts
@@ -0,0 +1,309 @@
+// 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 {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
+import {CrInputElement, SecurityKeysPinBrowserProxy, SecurityKeysPinBrowserProxyImpl, SetPinDialogPage, SettingsSecurityKeysSetPinDialogElement} from 'chrome://settings/lazy_load.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+import {assertShown} from './security_keys_test_util.js';
+import {TestSecurityKeysBrowserProxy} from './test_security_keys_browser_proxy.js';
+
+const currentMinPinLength = 6;
+const newMinPinLength = 8;
+
+class TestSecurityKeysPinBrowserProxy extends TestSecurityKeysBrowserProxy
+    implements SecurityKeysPinBrowserProxy {
+  constructor() {
+    super([
+      'startSetPin',
+      'setPin',
+      'close',
+    ]);
+  }
+
+  startSetPin() {
+    return this.handleMethod('startSetPin');
+  }
+
+  setPin(oldPIN: string, newPIN: string) {
+    return this.handleMethod('setPin', {oldPIN, newPIN});
+  }
+
+  close() {
+    this.methodCalled('close');
+  }
+}
+
+suite('SecurityKeysSetPINDialog', function() {
+  const tooShortCurrentPIN = 'abcd';
+  const validCurrentPIN = 'abcdef';
+  const tooShortNewPIN = '123456';
+  const validNewPIN = '12345678';
+  const anotherValidNewPIN = '87654321';
+
+  let dialog: SettingsSecurityKeysSetPinDialogElement;
+  let allDivs: string[];
+  let browserProxy: TestSecurityKeysPinBrowserProxy;
+
+  setup(function() {
+    browserProxy = new TestSecurityKeysPinBrowserProxy();
+    SecurityKeysPinBrowserProxyImpl.setInstance(browserProxy);
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    dialog = document.createElement('settings-security-keys-set-pin-dialog');
+    allDivs = Object.values(SetPinDialogPage);
+  });
+
+  function assertComplete() {
+    assertEquals(dialog.$.closeButton.textContent!.trim(), 'OK');
+    assertEquals(dialog.$.closeButton.className, 'action-button');
+    assertEquals(dialog.$.pinSubmit.hidden, true);
+  }
+
+  function assertNotComplete() {
+    assertEquals(dialog.$.closeButton.textContent!.trim(), 'Cancel');
+    assertEquals(dialog.$.closeButton.className, 'cancel-button');
+    assertEquals(dialog.$.pinSubmit.hidden, false);
+  }
+
+  test('Initialization', async function() {
+    document.body.appendChild(dialog);
+    await browserProxy.whenCalled('startSetPin');
+    assertShown(allDivs, dialog, 'initial');
+    assertNotComplete();
+  });
+
+  // Test error codes that are returned immediately.
+  for (const testCase of [
+           [1 /* INVALID_COMMAND */, 'noPINSupport'],
+           [52 /* temporary lock */, 'reinsert'], [50 /* locked */, 'locked'],
+           [1000 /* invalid error */, 'error']]) {
+    test('ImmediateError' + testCase[0]!.toString(), async function() {
+      browserProxy.setResponseFor(
+          'startSetPin', Promise.resolve({done: true, error: testCase[0]}));
+      document.body.appendChild(dialog);
+
+      await browserProxy.whenCalled('startSetPin');
+      await browserProxy.whenCalled('close');
+      assertComplete();
+      assertShown(allDivs, dialog, (testCase[1] as string));
+      if (testCase[1] === 'error') {
+        // Unhandled error codes display the numeric code.
+        assertTrue(dialog.$.error.textContent!.trim().includes(
+            testCase[0]!.toString()));
+      }
+    });
+  }
+
+  test('ZeroRetries', async function() {
+    // Authenticators can also signal that they are locked by indicating zero
+    // attempts remaining.
+    browserProxy.setResponseFor('startSetPin', Promise.resolve({
+      done: false,
+      error: null,
+      currentMinPinLength,
+      newMinPinLength,
+      retries: 0,
+    }));
+    document.body.appendChild(dialog);
+
+    await browserProxy.whenCalled('startSetPin');
+    await browserProxy.whenCalled('close');
+    assertComplete();
+    assertShown(allDivs, dialog, 'locked');
+  });
+
+  function setPINEntry(inputElement: CrInputElement, pinValue: string) {
+    inputElement.value = pinValue;
+    // Dispatch input events to trigger validation and UI updates.
+    inputElement.dispatchEvent(
+        new CustomEvent('input', {bubbles: true, cancelable: true}));
+  }
+
+  function setNewPINEntries(
+      pinValue: string, confirmPINValue: string): Promise<void> {
+    setPINEntry(dialog.$.newPIN, pinValue);
+    setPINEntry(dialog.$.confirmPIN, confirmPINValue);
+    const ret = eventToPromise('ui-ready', dialog);
+    dialog.$.pinSubmit.click();
+    return ret;
+  }
+
+  function setChangePINEntries(
+      currentPINValue: string, pinValue: string,
+      confirmPINValue: string): Promise<void> {
+    setPINEntry(dialog.$.newPIN, pinValue);
+    setPINEntry(dialog.$.confirmPIN, confirmPINValue);
+    setPINEntry(dialog.$.currentPIN, currentPINValue);
+    const ret = eventToPromise('ui-ready', dialog);
+    dialog.$.pinSubmit.click();
+    return ret;
+  }
+
+  test('SetPIN', async function() {
+    const startSetPINResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
+    document.body.appendChild(dialog);
+    const uiReady = eventToPromise('ui-ready', dialog);
+
+    await browserProxy.whenCalled('startSetPin');
+    startSetPINResolver.resolve({
+      done: false,
+      error: null,
+      currentMinPinLength,
+      newMinPinLength,
+      retries: null,
+    });
+    await uiReady;
+    assertNotComplete();
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertTrue(dialog.$.currentPINEntry.hidden);
+
+    await setNewPINEntries(tooShortNewPIN, '');
+    assertTrue(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    await setNewPINEntries(tooShortNewPIN, tooShortNewPIN);
+    assertTrue(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    await setNewPINEntries(validNewPIN, anotherValidNewPIN);
+    assertFalse(dialog.$.newPIN.invalid);
+    assertTrue(dialog.$.confirmPIN.invalid);
+
+    const setPINResolver = new PromiseResolver();
+    browserProxy.setResponseFor('setPin', setPINResolver.promise);
+    setNewPINEntries(validNewPIN, validNewPIN);
+    const {oldPIN, newPIN} = await browserProxy.whenCalled('setPin');
+    assertTrue(dialog.$.pinSubmit.disabled);
+    assertEquals(oldPIN, '');
+    assertEquals(newPIN, validNewPIN);
+
+    setPINResolver.resolve({done: true, error: 0});
+    await browserProxy.whenCalled('close');
+    assertShown(allDivs, dialog, 'success');
+    assertComplete();
+  });
+
+  // Test error codes that are only returned after attempting to set a PIN.
+  for (const testCase of [
+           [52 /* temporary lock */, 'reinsert'], [50 /* locked */, 'locked'],
+           [1000 /* invalid error */, 'error']]) {
+    test('Error' + testCase[0]!.toString(), async function() {
+      const startSetPINResolver = new PromiseResolver();
+      browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
+      document.body.appendChild(dialog);
+      const uiReady = eventToPromise('ui-ready', dialog);
+
+      await browserProxy.whenCalled('startSetPin');
+      startSetPINResolver.resolve({
+        done: false,
+        error: null,
+        currentMinPinLength,
+        newMinPinLength,
+        retries: null,
+      });
+      await uiReady;
+
+      browserProxy.setResponseFor(
+          'setPin', Promise.resolve({done: true, error: testCase[0]}));
+      setNewPINEntries(validNewPIN, validNewPIN);
+      await browserProxy.whenCalled('setPin');
+      await browserProxy.whenCalled('close');
+      assertComplete();
+      assertShown(allDivs, dialog, (testCase[1] as string));
+      if (testCase[1] === 'error') {
+        // Unhandled error codes display the numeric code.
+        assertTrue(dialog.$.error.textContent!.trim().includes(
+            testCase[0]!.toString()));
+      }
+    });
+  }
+
+  test('ChangePIN', async function() {
+    const startSetPINResolver = new PromiseResolver();
+    browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
+    document.body.appendChild(dialog);
+    let uiReady = eventToPromise('ui-ready', dialog);
+
+    await browserProxy.whenCalled('startSetPin');
+    startSetPINResolver.resolve({
+      done: false,
+      error: null,
+      currentMinPinLength,
+      newMinPinLength,
+      retries: 2,
+    });
+    await uiReady;
+    assertNotComplete();
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertFalse(dialog.$.currentPINEntry.hidden);
+
+    setChangePINEntries(tooShortCurrentPIN, '', '');
+    assertTrue(dialog.$.currentPIN.invalid);
+    assertFalse(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    setChangePINEntries(tooShortCurrentPIN, tooShortNewPIN, '');
+    assertTrue(dialog.$.currentPIN.invalid);
+    assertFalse(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    setChangePINEntries(validCurrentPIN, tooShortNewPIN, validNewPIN);
+    assertFalse(dialog.$.currentPIN.invalid);
+    assertTrue(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    setChangePINEntries(tooShortCurrentPIN, validNewPIN, validNewPIN);
+    assertTrue(dialog.$.currentPIN.invalid);
+    assertFalse(dialog.$.newPIN.invalid);
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    setChangePINEntries(validNewPIN, validNewPIN, validNewPIN);
+    assertFalse(dialog.$.currentPIN.invalid);
+    assertTrue(dialog.$.newPIN.invalid);
+    assertEquals(
+        dialog.$.newPIN.errorMessage,
+        loadTimeData.getString('securityKeysSamePINAsCurrent'));
+    assertFalse(dialog.$.confirmPIN.invalid);
+
+    let setPINResolver = new PromiseResolver();
+    browserProxy.setResponseFor('setPin', setPINResolver.promise);
+    setPINEntry(dialog.$.currentPIN, validCurrentPIN);
+    setPINEntry(dialog.$.newPIN, validNewPIN);
+    setPINEntry(dialog.$.confirmPIN, validNewPIN);
+    dialog.$.pinSubmit.click();
+    let {oldPIN, newPIN} = await browserProxy.whenCalled('setPin');
+    assertShown(allDivs, dialog, 'pinPrompt');
+    assertNotComplete();
+    assertTrue(dialog.$.pinSubmit.disabled);
+    assertEquals(oldPIN, validCurrentPIN);
+    assertEquals(newPIN, validNewPIN);
+
+    // Simulate an incorrect PIN.
+    uiReady = eventToPromise('ui-ready', dialog);
+    setPINResolver.resolve({done: true, error: 49});
+    await uiReady;
+    assertTrue(dialog.$.currentPIN.invalid);
+    // Text box for current PIN should not be cleared.
+    assertEquals(dialog.$.currentPIN.value, validCurrentPIN);
+
+    setPINEntry(dialog.$.currentPIN, anotherValidNewPIN);
+
+    browserProxy.resetResolver('setPin');
+    setPINResolver = new PromiseResolver();
+    browserProxy.setResponseFor('setPin', setPINResolver.promise);
+    dialog.$.pinSubmit.click();
+    ({oldPIN, newPIN} = await browserProxy.whenCalled('setPin'));
+    assertTrue(dialog.$.pinSubmit.disabled);
+    assertEquals(oldPIN, anotherValidNewPIN);
+    assertEquals(newPIN, validNewPIN);
+
+    setPINResolver.resolve({done: true, error: 0});
+    await browserProxy.whenCalled('close');
+    assertShown(allDivs, dialog, 'success');
+    assertComplete();
+  });
+});
diff --git a/chrome/test/data/webui/settings/security_keys_subpage_test.ts b/chrome/test/data/webui/settings/security_keys_subpage_test.ts
deleted file mode 100644
index 258e46f..0000000
--- a/chrome/test/data/webui/settings/security_keys_subpage_test.ts
+++ /dev/null
@@ -1,1131 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// clang-format off
-import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {BioEnrollDialogPage, CredentialManagementDialogPage, CrIconButtonElement, CrInputElement, Ctap2Status, ResetDialogPage, SampleStatus, SecurityKeysBioEnrollProxy, SecurityKeysBioEnrollProxyImpl, SecurityKeysCredentialBrowserProxy, SecurityKeysCredentialBrowserProxyImpl, SecurityKeysPinBrowserProxy, SecurityKeysPinBrowserProxyImpl, SecurityKeysResetBrowserProxy, SecurityKeysResetBrowserProxyImpl, SetPinDialogPage, SettingsSecurityKeysBioEnrollDialogElement, SettingsSecurityKeysCredentialManagementDialogElement, SettingsSecurityKeysResetDialogElement, SettingsSecurityKeysSetPinDialogElement} from 'chrome://settings/lazy_load.js';
-import {assertDeepEquals, assertEquals, assertFalse, 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';
-
-// clang-format on
-
-const currentMinPinLength = 6;
-const newMinPinLength = 8;
-
-/**
- * A base class for all security key subpage test browser proxies to
- * inherit from. Provides a |promiseMap_| that proxies can be used to
- * simulation Promise resolution via |setResponseFor| and |handleMethod|.
- */
-class TestSecurityKeysBrowserProxy extends TestBrowserProxy {
-  private promiseMap_: Map<string, Promise<any>>;
-
-  constructor(methodNames: string[]) {
-    super(methodNames);
-
-    /**
-     * A map from method names to a promise to return when that method is
-     * called. (If no promise is installed, a never-resolved promise is
-     * returned.)
-     */
-    this.promiseMap_ = new Map();
-  }
-
-  setResponseFor(methodName: string, promise: Promise<any>) {
-    this.promiseMap_.set(methodName, promise);
-  }
-
-  protected handleMethod(methodName: string, arg?: any): Promise<any> {
-    this.methodCalled(methodName, arg);
-    const promise = this.promiseMap_.get(methodName);
-    if (promise !== undefined) {
-      this.promiseMap_.delete(methodName);
-      return promise;
-    }
-
-    // Return a Promise that never resolves.
-    return new Promise(() => {});
-  }
-}
-
-class TestSecurityKeysPinBrowserProxy extends TestSecurityKeysBrowserProxy
-    implements SecurityKeysPinBrowserProxy {
-  constructor() {
-    super([
-      'startSetPin',
-      'setPin',
-      'close',
-    ]);
-  }
-
-  startSetPin() {
-    return this.handleMethod('startSetPin');
-  }
-
-  setPin(oldPIN: string, newPIN: string) {
-    return this.handleMethod('setPin', {oldPIN, newPIN});
-  }
-
-  close() {
-    this.methodCalled('close');
-  }
-}
-
-class TestSecurityKeysResetBrowserProxy extends TestSecurityKeysBrowserProxy
-    implements SecurityKeysResetBrowserProxy {
-  constructor() {
-    super([
-      'reset',
-      'completeReset',
-      'close',
-    ]);
-  }
-
-  override reset() {
-    return this.handleMethod('reset');
-  }
-
-  completeReset() {
-    return this.handleMethod('completeReset');
-  }
-
-  close() {
-    this.methodCalled('close');
-  }
-}
-
-class TestSecurityKeysCredentialBrowserProxy extends
-    TestSecurityKeysBrowserProxy implements SecurityKeysCredentialBrowserProxy {
-  constructor() {
-    super([
-      'startCredentialManagement',
-      'providePin',
-      'enumerateCredentials',
-      'deleteCredentials',
-      'updateUserInformation',
-      'close',
-    ]);
-  }
-
-  startCredentialManagement() {
-    return this.handleMethod('startCredentialManagement');
-  }
-
-  providePin(pin: string) {
-    return this.handleMethod('providePin', pin);
-  }
-
-  enumerateCredentials() {
-    return this.handleMethod('enumerateCredentials');
-  }
-
-  deleteCredentials(ids: string[]) {
-    return this.handleMethod('deleteCredentials', ids);
-  }
-
-  updateUserInformation(
-      credentialId: string, userHandle: string, newUsername: string,
-      newDisplayname: string) {
-    return this.handleMethod(
-        'updateUserInformation',
-        {credentialId, userHandle, newUsername, newDisplayname});
-  }
-
-  close() {
-    this.methodCalled('close');
-  }
-}
-
-class TestSecurityKeysBioEnrollProxy extends TestSecurityKeysBrowserProxy
-    implements SecurityKeysBioEnrollProxy {
-  constructor() {
-    super([
-      'startBioEnroll',
-      'providePin',
-      'getSensorInfo',
-      'enumerateEnrollments',
-      'startEnrolling',
-      'cancelEnrollment',
-      'deleteEnrollment',
-      'renameEnrollment',
-      'close',
-    ]);
-  }
-
-  startBioEnroll() {
-    return this.handleMethod('startBioEnroll');
-  }
-
-  providePin(pin: string) {
-    return this.handleMethod('providePin', pin);
-  }
-
-  getSensorInfo() {
-    return this.handleMethod('getSensorInfo');
-  }
-
-  enumerateEnrollments() {
-    return this.handleMethod('enumerateEnrollments');
-  }
-
-  startEnrolling() {
-    return this.handleMethod('startEnrolling');
-  }
-
-  cancelEnrollment() {
-    return this.methodCalled('cancelEnrollment');
-  }
-
-  deleteEnrollment(id: string) {
-    return this.handleMethod('deleteEnrollment', id);
-  }
-
-  renameEnrollment(id: string, name: string) {
-    return this.handleMethod('renameEnrollment', [id, name]);
-  }
-
-  close() {
-    this.methodCalled('close');
-  }
-}
-
-function assertShown(
-    allDivs: string[], dialog: HTMLElement, expectedID: string) {
-  assertTrue(allDivs.includes(expectedID));
-
-  const allShown = allDivs.filter(id => {
-    return dialog.shadowRoot!.querySelector(`#${id}`)!.className ===
-        'iron-selected';
-  });
-  assertEquals(1, allShown.length);
-  assertEquals(expectedID, allShown[0]);
-}
-
-
-suite('SecurityKeysResetDialog', function() {
-  let dialog: SettingsSecurityKeysResetDialogElement;
-  let allDivs: string[];
-  let browserProxy: TestSecurityKeysResetBrowserProxy;
-
-  setup(function() {
-    browserProxy = new TestSecurityKeysResetBrowserProxy();
-    SecurityKeysResetBrowserProxyImpl.setInstance(browserProxy);
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    dialog = document.createElement('settings-security-keys-reset-dialog');
-    allDivs = Object.values(ResetDialogPage);
-  });
-
-  function assertComplete() {
-    assertEquals(dialog.$.button.textContent!.trim(), 'OK');
-    assertEquals(dialog.$.button.className, 'action-button');
-  }
-
-  function assertNotComplete() {
-    assertEquals(dialog.$.button.textContent!.trim(), 'Cancel');
-    assertEquals(dialog.$.button.className, 'cancel-button');
-  }
-
-  test('Initialization', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('reset');
-    assertShown(allDivs, dialog, 'initial');
-    assertNotComplete();
-  });
-
-  test('Cancel', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('reset');
-    assertShown(allDivs, dialog, 'initial');
-    assertNotComplete();
-    dialog.$.button.click();
-    await browserProxy.whenCalled('close');
-    assertFalse(dialog.$.dialog.open);
-  });
-
-  test('NotSupported', async function() {
-    browserProxy.setResponseFor(
-        'reset', Promise.resolve(1 /* INVALID_COMMAND */));
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('reset');
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'noReset');
-  });
-
-  test('ImmediateUnknownError', async function() {
-    const error = 1000 /* undefined error code */;
-    browserProxy.setResponseFor('reset', Promise.resolve(error));
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('reset');
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'resetFailed');
-    assertTrue(
-        dialog.$.resetFailed.textContent!.trim().includes(error.toString()));
-  });
-
-  test('ImmediateUnknownError', async function() {
-    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
-    const promiseResolver = new PromiseResolver();
-    browserProxy.setResponseFor('completeReset', promiseResolver.promise);
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('reset');
-    await browserProxy.whenCalled('completeReset');
-    assertNotComplete();
-    assertShown(allDivs, dialog, 'resetConfirm');
-    promiseResolver.resolve(0 /* success */);
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'resetSuccess');
-  });
-
-  test('UnknownError', async function() {
-    const error = 1000 /* undefined error code */;
-    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
-    browserProxy.setResponseFor('completeReset', Promise.resolve(error));
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('reset');
-    await browserProxy.whenCalled('completeReset');
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'resetFailed');
-    assertTrue(
-        dialog.$.resetFailed.textContent!.trim().includes(error.toString()));
-  });
-
-  test('ResetRejected', async function() {
-    browserProxy.setResponseFor('reset', Promise.resolve(0 /* success */));
-    browserProxy.setResponseFor(
-        'completeReset', Promise.resolve(48 /* NOT_ALLOWED */));
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('reset');
-    await browserProxy.whenCalled('completeReset');
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'resetNotAllowed');
-  });
-});
-
-suite('SecurityKeysSetPINDialog', function() {
-  const tooShortCurrentPIN = 'abcd';
-  const validCurrentPIN = 'abcdef';
-  const tooShortNewPIN = '123456';
-  const validNewPIN = '12345678';
-  const anotherValidNewPIN = '87654321';
-
-  let dialog: SettingsSecurityKeysSetPinDialogElement;
-  let allDivs: string[];
-  let browserProxy: TestSecurityKeysPinBrowserProxy;
-
-  setup(function() {
-    browserProxy = new TestSecurityKeysPinBrowserProxy();
-    SecurityKeysPinBrowserProxyImpl.setInstance(browserProxy);
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    dialog = document.createElement('settings-security-keys-set-pin-dialog');
-    allDivs = Object.values(SetPinDialogPage);
-  });
-
-  function assertComplete() {
-    assertEquals(dialog.$.closeButton.textContent!.trim(), 'OK');
-    assertEquals(dialog.$.closeButton.className, 'action-button');
-    assertEquals(dialog.$.pinSubmit.hidden, true);
-  }
-
-  function assertNotComplete() {
-    assertEquals(dialog.$.closeButton.textContent!.trim(), 'Cancel');
-    assertEquals(dialog.$.closeButton.className, 'cancel-button');
-    assertEquals(dialog.$.pinSubmit.hidden, false);
-  }
-
-  test('Initialization', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startSetPin');
-    assertShown(allDivs, dialog, 'initial');
-    assertNotComplete();
-  });
-
-  // Test error codes that are returned immediately.
-  for (const testCase of [
-           [1 /* INVALID_COMMAND */, 'noPINSupport'],
-           [52 /* temporary lock */, 'reinsert'], [50 /* locked */, 'locked'],
-           [1000 /* invalid error */, 'error']]) {
-    test('ImmediateError' + testCase[0]!.toString(), async function() {
-      browserProxy.setResponseFor(
-          'startSetPin', Promise.resolve({done: true, error: testCase[0]}));
-      document.body.appendChild(dialog);
-
-      await browserProxy.whenCalled('startSetPin');
-      await browserProxy.whenCalled('close');
-      assertComplete();
-      assertShown(allDivs, dialog, (testCase[1] as string));
-      if (testCase[1] === 'error') {
-        // Unhandled error codes display the numeric code.
-        assertTrue(dialog.$.error.textContent!.trim().includes(
-            testCase[0]!.toString()));
-      }
-    });
-  }
-
-  test('ZeroRetries', async function() {
-    // Authenticators can also signal that they are locked by indicating zero
-    // attempts remaining.
-    browserProxy.setResponseFor('startSetPin', Promise.resolve({
-      done: false,
-      error: null,
-      currentMinPinLength,
-      newMinPinLength,
-      retries: 0,
-    }));
-    document.body.appendChild(dialog);
-
-    await browserProxy.whenCalled('startSetPin');
-    await browserProxy.whenCalled('close');
-    assertComplete();
-    assertShown(allDivs, dialog, 'locked');
-  });
-
-  function setPINEntry(inputElement: CrInputElement, pinValue: string) {
-    inputElement.value = pinValue;
-    // Dispatch input events to trigger validation and UI updates.
-    inputElement.dispatchEvent(
-        new CustomEvent('input', {bubbles: true, cancelable: true}));
-  }
-
-  function setNewPINEntries(
-      pinValue: string, confirmPINValue: string): Promise<void> {
-    setPINEntry(dialog.$.newPIN, pinValue);
-    setPINEntry(dialog.$.confirmPIN, confirmPINValue);
-    const ret = eventToPromise('ui-ready', dialog);
-    dialog.$.pinSubmit.click();
-    return ret;
-  }
-
-  function setChangePINEntries(
-      currentPINValue: string, pinValue: string,
-      confirmPINValue: string): Promise<void> {
-    setPINEntry(dialog.$.newPIN, pinValue);
-    setPINEntry(dialog.$.confirmPIN, confirmPINValue);
-    setPINEntry(dialog.$.currentPIN, currentPINValue);
-    const ret = eventToPromise('ui-ready', dialog);
-    dialog.$.pinSubmit.click();
-    return ret;
-  }
-
-  test('SetPIN', async function() {
-    const startSetPINResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
-    document.body.appendChild(dialog);
-    const uiReady = eventToPromise('ui-ready', dialog);
-
-    await browserProxy.whenCalled('startSetPin');
-    startSetPINResolver.resolve({
-      done: false,
-      error: null,
-      currentMinPinLength,
-      newMinPinLength,
-      retries: null,
-    });
-    await uiReady;
-    assertNotComplete();
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertTrue(dialog.$.currentPINEntry.hidden);
-
-    await setNewPINEntries(tooShortNewPIN, '');
-    assertTrue(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    await setNewPINEntries(tooShortNewPIN, tooShortNewPIN);
-    assertTrue(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    await setNewPINEntries(validNewPIN, anotherValidNewPIN);
-    assertFalse(dialog.$.newPIN.invalid);
-    assertTrue(dialog.$.confirmPIN.invalid);
-
-    const setPINResolver = new PromiseResolver();
-    browserProxy.setResponseFor('setPin', setPINResolver.promise);
-    setNewPINEntries(validNewPIN, validNewPIN);
-    const {oldPIN, newPIN} = await browserProxy.whenCalled('setPin');
-    assertTrue(dialog.$.pinSubmit.disabled);
-    assertEquals(oldPIN, '');
-    assertEquals(newPIN, validNewPIN);
-
-    setPINResolver.resolve({done: true, error: 0});
-    await browserProxy.whenCalled('close');
-    assertShown(allDivs, dialog, 'success');
-    assertComplete();
-  });
-
-  // Test error codes that are only returned after attempting to set a PIN.
-  for (const testCase of [
-           [52 /* temporary lock */, 'reinsert'], [50 /* locked */, 'locked'],
-           [1000 /* invalid error */, 'error']]) {
-    test('Error' + testCase[0]!.toString(), async function() {
-      const startSetPINResolver = new PromiseResolver();
-      browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
-      document.body.appendChild(dialog);
-      const uiReady = eventToPromise('ui-ready', dialog);
-
-      await browserProxy.whenCalled('startSetPin');
-      startSetPINResolver.resolve({
-        done: false,
-        error: null,
-        currentMinPinLength,
-        newMinPinLength,
-        retries: null,
-      });
-      await uiReady;
-
-      browserProxy.setResponseFor(
-          'setPin', Promise.resolve({done: true, error: testCase[0]}));
-      setNewPINEntries(validNewPIN, validNewPIN);
-      await browserProxy.whenCalled('setPin');
-      await browserProxy.whenCalled('close');
-      assertComplete();
-      assertShown(allDivs, dialog, (testCase[1] as string));
-      if (testCase[1] === 'error') {
-        // Unhandled error codes display the numeric code.
-        assertTrue(dialog.$.error.textContent!.trim().includes(
-            testCase[0]!.toString()));
-      }
-    });
-  }
-
-  test('ChangePIN', async function() {
-    const startSetPINResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startSetPin', startSetPINResolver.promise);
-    document.body.appendChild(dialog);
-    let uiReady = eventToPromise('ui-ready', dialog);
-
-    await browserProxy.whenCalled('startSetPin');
-    startSetPINResolver.resolve({
-      done: false,
-      error: null,
-      currentMinPinLength,
-      newMinPinLength,
-      retries: 2,
-    });
-    await uiReady;
-    assertNotComplete();
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertFalse(dialog.$.currentPINEntry.hidden);
-
-    setChangePINEntries(tooShortCurrentPIN, '', '');
-    assertTrue(dialog.$.currentPIN.invalid);
-    assertFalse(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    setChangePINEntries(tooShortCurrentPIN, tooShortNewPIN, '');
-    assertTrue(dialog.$.currentPIN.invalid);
-    assertFalse(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    setChangePINEntries(validCurrentPIN, tooShortNewPIN, validNewPIN);
-    assertFalse(dialog.$.currentPIN.invalid);
-    assertTrue(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    setChangePINEntries(tooShortCurrentPIN, validNewPIN, validNewPIN);
-    assertTrue(dialog.$.currentPIN.invalid);
-    assertFalse(dialog.$.newPIN.invalid);
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    setChangePINEntries(validNewPIN, validNewPIN, validNewPIN);
-    assertFalse(dialog.$.currentPIN.invalid);
-    assertTrue(dialog.$.newPIN.invalid);
-    assertEquals(
-        dialog.$.newPIN.errorMessage,
-        loadTimeData.getString('securityKeysSamePINAsCurrent'));
-    assertFalse(dialog.$.confirmPIN.invalid);
-
-    let setPINResolver = new PromiseResolver();
-    browserProxy.setResponseFor('setPin', setPINResolver.promise);
-    setPINEntry(dialog.$.currentPIN, validCurrentPIN);
-    setPINEntry(dialog.$.newPIN, validNewPIN);
-    setPINEntry(dialog.$.confirmPIN, validNewPIN);
-    dialog.$.pinSubmit.click();
-    let {oldPIN, newPIN} = await browserProxy.whenCalled('setPin');
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertNotComplete();
-    assertTrue(dialog.$.pinSubmit.disabled);
-    assertEquals(oldPIN, validCurrentPIN);
-    assertEquals(newPIN, validNewPIN);
-
-    // Simulate an incorrect PIN.
-    uiReady = eventToPromise('ui-ready', dialog);
-    setPINResolver.resolve({done: true, error: 49});
-    await uiReady;
-    assertTrue(dialog.$.currentPIN.invalid);
-    // Text box for current PIN should not be cleared.
-    assertEquals(dialog.$.currentPIN.value, validCurrentPIN);
-
-    setPINEntry(dialog.$.currentPIN, anotherValidNewPIN);
-
-    browserProxy.resetResolver('setPin');
-    setPINResolver = new PromiseResolver();
-    browserProxy.setResponseFor('setPin', setPINResolver.promise);
-    dialog.$.pinSubmit.click();
-    ({oldPIN, newPIN} = await browserProxy.whenCalled('setPin'));
-    assertTrue(dialog.$.pinSubmit.disabled);
-    assertEquals(oldPIN, anotherValidNewPIN);
-    assertEquals(newPIN, validNewPIN);
-
-    setPINResolver.resolve({done: true, error: 0});
-    await browserProxy.whenCalled('close');
-    assertShown(allDivs, dialog, 'success');
-    assertComplete();
-  });
-});
-
-suite('SecurityKeysCredentialManagement', function() {
-  let dialog: SettingsSecurityKeysCredentialManagementDialogElement;
-  let allDivs: string[];
-  let browserProxy: TestSecurityKeysCredentialBrowserProxy;
-
-  setup(function() {
-    browserProxy = new TestSecurityKeysCredentialBrowserProxy();
-    SecurityKeysCredentialBrowserProxyImpl.setInstance(browserProxy);
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    dialog = document.createElement(
-        'settings-security-keys-credential-management-dialog');
-    allDivs = Object.values(CredentialManagementDialogPage);
-  });
-
-  test('Initialization', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-  });
-
-  test('Cancel', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-    dialog.$.cancelButton.click();
-    await browserProxy.whenCalled('close');
-    assertFalse(dialog.$.dialog.open);
-  });
-
-  test('Finished', async function() {
-    const startResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'startCredentialManagement', startResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-    startResolver.resolve({
-      minPinLength: currentMinPinLength,
-      supportsUpdateUserInformation: true,
-    });
-
-    const error = 'foo bar baz';
-    webUIListenerCallback(
-        'security-keys-credential-management-finished', error);
-    assertShown(allDivs, dialog, 'pinError');
-    assertTrue(dialog.$.error.textContent!.trim().includes(error));
-  });
-
-  test('PINChangeError', async function() {
-    const startResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'startCredentialManagement', startResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-    startResolver.resolve({
-      minPinLength: currentMinPinLength,
-      supportsUpdateUserInformation: true,
-    });
-
-    const error = 'foo bar baz';
-    webUIListenerCallback(
-        'security-keys-credential-management-finished', error,
-        true /* requiresPINChange */);
-    assertShown(allDivs, dialog, 'pinError');
-    assertFalse(dialog.$.confirmButton.hidden);
-    assertFalse(dialog.$.confirmButton.disabled);
-    assertTrue(dialog.$.pinError.textContent!.trim().includes(error));
-
-    const setPinEvent = eventToPromise('credential-management-set-pin', dialog);
-    dialog.$.confirmButton.click();
-    await setPinEvent;
-  });
-
-  test('UpdateNotSupported', async function() {
-    const startCredentialManagementResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'startCredentialManagement', startCredentialManagementResolver.promise);
-    const pinResolver = new PromiseResolver();
-    browserProxy.setResponseFor('providePin', pinResolver.promise);
-    const enumerateResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'enumerateCredentials', enumerateResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-
-    // Simulate PIN entry.
-    let uiReady = eventToPromise(
-        'credential-management-dialog-ready-for-testing', dialog);
-    startCredentialManagementResolver.resolve({
-      minPinLength: currentMinPinLength,
-      supportsUpdateUserInformation: false,
-    });
-
-    await uiReady;
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
-    dialog.$.pin.$.pin.value = '000000';
-    dialog.$.confirmButton.click();
-    const pin = await browserProxy.whenCalled('providePin');
-    assertEquals(pin, '000000');
-
-    // Show a credential.
-    pinResolver.resolve(null);
-    await browserProxy.whenCalled('enumerateCredentials');
-    uiReady = eventToPromise(
-        'credential-management-dialog-ready-for-testing', dialog);
-    const credentials = [
-      {
-        credentialId: 'aaaaaa',
-        relyingPartyId: 'acme.com',
-        userHandle: 'userausera',
-        userName: 'userA@example.com',
-        userDisplayName: 'User Aaa',
-      },
-    ];
-    enumerateResolver.resolve(credentials);
-    await uiReady;
-    assertShown(allDivs, dialog, 'credentials');
-    assertEquals(dialog.$.credentialList.items, credentials);
-
-    // Check that the edit button is disabled.
-    flush();
-    const editButtons: CrIconButtonElement[] =
-        Array.from(dialog.$.credentialList.querySelectorAll('.edit-button'));
-    assertEquals(editButtons.length, 1);
-    assertTrue(editButtons[0]!.hidden);
-  });
-
-  test('Credentials', async function() {
-    const startCredentialManagementResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'startCredentialManagement', startCredentialManagementResolver.promise);
-    const pinResolver = new PromiseResolver();
-    browserProxy.setResponseFor('providePin', pinResolver.promise);
-    const enumerateResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'enumerateCredentials', enumerateResolver.promise);
-    const deleteResolver = new PromiseResolver();
-    browserProxy.setResponseFor('deleteCredentials', deleteResolver.promise);
-    const updateUserInformationResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'updateUserInformation', updateUserInformationResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startCredentialManagement');
-    assertShown(allDivs, dialog, 'initial');
-
-    // Simulate PIN entry.
-    let uiReady = eventToPromise(
-        'credential-management-dialog-ready-for-testing', dialog);
-    startCredentialManagementResolver.resolve({
-      minPinLength: currentMinPinLength,
-      supportsUpdateUserInformation: true,
-    });
-    await uiReady;
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
-    dialog.$.pin.$.pin.value = '000000';
-    dialog.$.confirmButton.click();
-    const pin = await browserProxy.whenCalled('providePin');
-    assertEquals(pin, '000000');
-
-    // Show a list of three credentials.
-    pinResolver.resolve(null);
-    await browserProxy.whenCalled('enumerateCredentials');
-    uiReady = eventToPromise(
-        'credential-management-dialog-ready-for-testing', dialog);
-    const credentials = [
-      {
-        credentialId: 'aaaaaa',
-        relyingPartyId: 'acme.com',
-        userHandle: 'userausera',
-        userName: 'userA@example.com',
-        userDisplayName: 'User Aaa',
-      },
-      {
-        credentialId: 'bbbbbb',
-        relyingPartyId: 'acme.com',
-        userHandle: 'userbuserb',
-        userName: 'userB@example.com',
-        userDisplayName: 'User Bbb',
-      },
-      {
-        credentialId: 'cccccc',
-        relyingPartyId: 'acme.com',
-        userHandle: 'usercuserc',
-        userName: 'userC@example.com',
-        userDisplayName: 'User Ccc',
-      },
-    ];
-    enumerateResolver.resolve(credentials);
-    await uiReady;
-    assertShown(allDivs, dialog, 'credentials');
-    assertEquals(dialog.$.credentialList.items, credentials);
-
-    // Update a credential
-    flush();
-    const editButtons: CrIconButtonElement[] =
-        Array.from(dialog.$.credentialList.querySelectorAll('.edit-button'));
-    assertEquals(editButtons.length, 3);
-    editButtons.forEach(button => assertFalse(button.hidden));
-    editButtons[0]!.click();
-    assertShown(allDivs, dialog, 'edit');
-    dialog.$.displayNameInput.value = 'Bobby Example';
-    dialog.$.userNameInput.value = 'bobby@example.com';
-    dialog.$.confirmButton.click();
-    credentials[0]!.userDisplayName = 'Bobby Example';
-    credentials[0]!.userName = 'bobby@example.com';
-    updateUserInformationResolver.resolve({success: true, message: 'updated'});
-    assertShown(allDivs, dialog, 'credentials');
-    assertDeepEquals(dialog.$.credentialList.items, credentials);
-
-    // Delete a credential.
-    flush();
-    const deleteButtons: CrIconButtonElement[] =
-        Array.from(dialog.$.credentialList.querySelectorAll('.delete-button'));
-    assertEquals(deleteButtons.length, 3);
-    deleteButtons[0]!.click();
-    assertShown(allDivs, dialog, 'confirm');
-    dialog.$.confirmButton.click();
-    const credentialIds = await browserProxy.whenCalled('deleteCredentials');
-    assertDeepEquals(credentialIds, ['aaaaaa']);
-    uiReady = eventToPromise(
-        'credential-management-dialog-ready-for-testing', dialog);
-    deleteResolver.resolve({success: true, message: 'foobar'});
-    await uiReady;
-    assertShown(allDivs, dialog, 'credentials');
-  });
-});
-
-suite('SecurityKeysBioEnrollment', function() {
-  let dialog: SettingsSecurityKeysBioEnrollDialogElement;
-  let allDivs: string[];
-  let browserProxy: TestSecurityKeysBioEnrollProxy;
-
-  setup(function() {
-    browserProxy = new TestSecurityKeysBioEnrollProxy();
-    SecurityKeysBioEnrollProxyImpl.setInstance(browserProxy);
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    dialog = document.createElement('settings-security-keys-bio-enroll-dialog');
-    allDivs = Object.values(BioEnrollDialogPage);
-  });
-
-  test('Initialization', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-    assertFalse(dialog.$.cancelButton.hidden);
-  });
-
-  test('Cancel', async function() {
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-    dialog.$.cancelButton.click();
-    await browserProxy.whenCalled('close');
-    assertFalse(dialog.$.dialog.open);
-  });
-
-  test('Finished', async function() {
-    const resolver = new PromiseResolver();
-    browserProxy.setResponseFor('startBioEnroll', resolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-    resolver.resolve([currentMinPinLength]);
-
-    const error = 'foo bar baz';
-    webUIListenerCallback('security-keys-bio-enroll-error', error);
-    assertShown(allDivs, dialog, 'error');
-    assertTrue(dialog.$.confirmButton.hidden);
-    assertTrue(dialog.$.error.textContent!.trim().includes(error));
-  });
-
-  test('PINChangeError', async function() {
-    const resolver = new PromiseResolver();
-    browserProxy.setResponseFor('startBioEnroll', resolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-    resolver.resolve([currentMinPinLength]);
-
-    const error = 'something about setting a new PIN';
-    webUIListenerCallback(
-        'security-keys-bio-enroll-error', error, true /* requiresPINChange */);
-    assertShown(allDivs, dialog, 'error');
-    assertFalse(dialog.$.confirmButton.hidden);
-    assertFalse(dialog.$.confirmButton.disabled);
-    assertTrue(dialog.$.error.textContent!.trim().includes(error));
-
-    const setPinEvent = eventToPromise('bio-enroll-set-pin', dialog);
-    dialog.$.confirmButton.click();
-    await setPinEvent;
-  });
-
-  test('Enrollments', async function() {
-    const startResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startBioEnroll', startResolver.promise);
-    const pinResolver = new PromiseResolver();
-    browserProxy.setResponseFor('providePin', pinResolver.promise);
-    const getSensorInfoResolver = new PromiseResolver();
-    browserProxy.setResponseFor('getSensorInfo', getSensorInfoResolver.promise);
-    const enumerateResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'enumerateEnrollments', enumerateResolver.promise);
-    const deleteResolver = new PromiseResolver();
-    browserProxy.setResponseFor('deleteEnrollment', deleteResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-
-    // Simulate PIN entry.
-    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    startResolver.resolve([currentMinPinLength]);
-    await uiReady;
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
-    dialog.$.pin.$.pin.value = '000000';
-    dialog.$.confirmButton.click();
-    const pin = await browserProxy.whenCalled('providePin');
-    assertEquals(pin, '000000');
-    pinResolver.resolve(null);
-
-    await browserProxy.whenCalled('getSensorInfo');
-    getSensorInfoResolver.resolve({
-      maxTemplateFriendlyName: 10,
-    });
-
-    // Show a list of three enrollments.
-    await browserProxy.whenCalled('enumerateEnrollments');
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-
-    const fingerprintA = {
-      name: 'FingerprintA',
-      id: '1234',
-    };
-    const fingerprintB = {
-      name: 'FingerprintB',
-      id: '4321',
-    };
-    const fingerprintC = {
-      name: 'FingerprintC',
-      id: '000000',
-    };
-    const enrollments = [fingerprintC, fingerprintB, fingerprintA];
-    const sortedEnrollments = [fingerprintA, fingerprintB, fingerprintC];
-    enumerateResolver.resolve(enrollments);
-    await uiReady;
-    assertShown(allDivs, dialog, 'enrollments');
-    assertDeepEquals(dialog.$.enrollmentList.items, sortedEnrollments);
-
-    // Delete the second enrollments and refresh the list.
-    flush();
-    dialog.$.enrollmentList.querySelectorAll('cr-icon-button')[1]!.click();
-    const id = await browserProxy.whenCalled('deleteEnrollment');
-    assertEquals(sortedEnrollments[1]!.id, id);
-    sortedEnrollments.splice(1, 1);
-    enrollments.splice(1, 1);
-    deleteResolver.resolve(enrollments);
-    await uiReady;
-    assertShown(allDivs, dialog, 'enrollments');
-    assertDeepEquals(dialog.$.enrollmentList.items, sortedEnrollments);
-  });
-
-  test('AddEnrollment', async function() {
-    const startResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startBioEnroll', startResolver.promise);
-    const pinResolver = new PromiseResolver();
-    browserProxy.setResponseFor('providePin', pinResolver.promise);
-    const getSensorInfoResolver = new PromiseResolver();
-    browserProxy.setResponseFor('getSensorInfo', getSensorInfoResolver.promise);
-    const enumerateResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'enumerateEnrollments', enumerateResolver.promise);
-    const enrollingResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startEnrolling', enrollingResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-    assertShown(allDivs, dialog, 'initial');
-
-    // Simulate PIN entry.
-    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    startResolver.resolve([currentMinPinLength]);
-    await uiReady;
-    assertShown(allDivs, dialog, 'pinPrompt');
-    assertEquals(currentMinPinLength, dialog.$.pin.minPinLength);
-    dialog.$.pin.$.pin.value = '000000';
-    dialog.$.confirmButton.click();
-    const pin = await browserProxy.whenCalled('providePin');
-    assertEquals(pin, '000000');
-    pinResolver.resolve(null);
-
-    await browserProxy.whenCalled('getSensorInfo');
-    getSensorInfoResolver.resolve({
-      maxTemplateFriendlyName: 20,
-    });
-
-    // Ensure no enrollments exist.
-    await browserProxy.whenCalled('enumerateEnrollments');
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    enumerateResolver.resolve([]);
-    await uiReady;
-    assertShown(allDivs, dialog, 'enrollments');
-    assertEquals(dialog.$.enrollmentList.items!.length, 0);
-
-    // Simulate add enrollment.
-    assertFalse(dialog.$.addButton.hidden);
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    dialog.$.addButton.click();
-    await browserProxy.whenCalled('startEnrolling');
-    await uiReady;
-
-    assertShown(allDivs, dialog, 'enroll');
-    webUIListenerCallback(
-        'security-keys-bio-enroll-status',
-        {status: SampleStatus.OK, remaining: 1});
-    flush();
-    assertFalse(dialog.$.arc.isComplete());
-    assertFalse(dialog.$.cancelButton.hidden);
-    assertTrue(dialog.$.confirmButton.hidden);
-
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    const enrollmentId = 'someId';
-    const enrollmentName = 'New Fingerprint';
-    enrollingResolver.resolve({
-      code: 0,
-      enrollment: {
-        id: enrollmentId,
-        name: enrollmentName,
-      },
-    });
-    await uiReady;
-    assertTrue(dialog.$.arc.isComplete());
-    assertTrue(dialog.$.cancelButton.hidden);
-    assertFalse(dialog.$.confirmButton.hidden);
-
-    // Proceeding brings up rename dialog page.
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    dialog.$.confirmButton.click();
-    await uiReady;
-
-    // Try renaming with a name that's longer than |maxTemplateFriendlyName|.
-    assertShown(allDivs, dialog, 'chooseName');
-    assertEquals(dialog.$.enrollmentName.value, enrollmentName);
-    const invalidNewEnrollmentName = '21 bytes long string!';
-    dialog.$.enrollmentName.value = invalidNewEnrollmentName;
-    assertFalse(dialog.$.confirmButton.hidden);
-    assertFalse(dialog.$.confirmButton.disabled);
-    assertFalse(dialog.$.enrollmentName.invalid);
-    dialog.$.confirmButton.click();
-    assertTrue(dialog.$.enrollmentName.invalid);
-    assertEquals(browserProxy.getCallCount('renameEnrollment'), 0);
-
-    // Try renaming to a valid name.
-    assertShown(allDivs, dialog, 'chooseName');
-    const newEnrollmentName = '20 bytes long string';
-    dialog.$.enrollmentName.value = newEnrollmentName;
-    assertFalse(dialog.$.confirmButton.hidden);
-    assertFalse(dialog.$.confirmButton.disabled);
-
-    // Proceeding renames the enrollment and returns to the enrollment overview.
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    const renameEnrollmentResolver = new PromiseResolver();
-    browserProxy.setResponseFor(
-        'renameEnrollment', renameEnrollmentResolver.promise);
-    dialog.$.confirmButton.click();
-    assertFalse(dialog.$.enrollmentName.invalid);
-
-    const renameArgs = await browserProxy.whenCalled('renameEnrollment');
-    assertDeepEquals(renameArgs, [enrollmentId, newEnrollmentName]);
-    renameEnrollmentResolver.resolve([]);
-    await uiReady;
-    assertShown(allDivs, dialog, 'enrollments');
-  });
-
-  test('EnrollCancel', async function() {
-    // Simulate starting an enrollment and then cancelling it.
-    browserProxy.setResponseFor('enumerateEnrollments', Promise.resolve([]));
-    const enrollResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startEnrolling', enrollResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-
-    dialog.setDialogPageForTesting(BioEnrollDialogPage.ENROLLMENTS);
-
-    // Forcibly disable the cancel button to ensure showing the dialog page
-    // re-enables it.
-    dialog.setCancelButtonDisabledForTesting(true);
-
-    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    dialog.$.addButton.click();
-    await browserProxy.whenCalled('startEnrolling');
-    await uiReady;
-
-    assertShown(allDivs, dialog, 'enroll');
-    assertFalse(dialog.$.cancelButton.disabled);
-    assertFalse(dialog.$.cancelButton.hidden);
-
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    dialog.$.cancelButton.click();
-    await browserProxy.whenCalled('cancelEnrollment');
-    enrollResolver.resolve({code: Ctap2Status.ERR_KEEPALIVE_CANCEL});
-    await browserProxy.whenCalled('enumerateEnrollments');
-
-    await uiReady;
-    assertShown(allDivs, dialog, 'enrollments');
-  });
-
-  test('EnrollError', async function() {
-    // Test that resolving the startEnrolling promise with a CTAP error brings
-    // up the error page.
-    const enrollResolver = new PromiseResolver();
-    browserProxy.setResponseFor('startEnrolling', enrollResolver.promise);
-
-    document.body.appendChild(dialog);
-    await browserProxy.whenCalled('startBioEnroll');
-
-    dialog.setDialogPageForTesting(BioEnrollDialogPage.ENROLLMENTS);
-
-    let uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-    dialog.$.addButton.click();
-    await browserProxy.whenCalled('startEnrolling');
-    await uiReady;
-
-    uiReady = eventToPromise('bio-enroll-dialog-ready-for-testing', dialog);
-
-    enrollResolver.resolve({code: Ctap2Status.ERR_INVALID_OPTION});
-
-    await uiReady;
-    assertShown(allDivs, dialog, 'error');
-  });
-});
diff --git a/chrome/test/data/webui/settings/security_keys_test_util.ts b/chrome/test/data/webui/settings/security_keys_test_util.ts
new file mode 100644
index 0000000..1c275ab5
--- /dev/null
+++ b/chrome/test/data/webui/settings/security_keys_test_util.ts
@@ -0,0 +1,17 @@
+// 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 {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+export function assertShown(
+    allDivs: string[], dialog: HTMLElement, expectedId: string) {
+  assertTrue(allDivs.includes(expectedId));
+
+  const allShown = allDivs.filter(id => {
+    return dialog.shadowRoot!.querySelector(`#${id}`)!.classList.contains(
+        'iron-selected');
+  });
+  assertEquals(1, allShown.length);
+  assertEquals(expectedId, allShown[0]);
+}
diff --git a/chrome/test/data/webui/settings/test_security_keys_browser_proxy.ts b/chrome/test/data/webui/settings/test_security_keys_browser_proxy.ts
new file mode 100644
index 0000000..e611e68
--- /dev/null
+++ b/chrome/test/data/webui/settings/test_security_keys_browser_proxy.ts
@@ -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.
+
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+/**
+ * A base class for all security key subpage test browser proxies to
+ * inherit from. Provides a |promiseMap_| that proxies can be used to
+ * simulate Promise resolution via |setResponseFor| and |handleMethod|.
+ */
+export class TestSecurityKeysBrowserProxy extends TestBrowserProxy {
+  /**
+   * A map from method names to a promise to return when that method is
+   * called. (If no promise is installed, a never-resolved promise is
+   * returned.)
+   */
+  private promiseMap_ = new Map<string, Promise<any>>();
+
+  setResponseFor(methodName: string, promise: Promise<any>) {
+    this.promiseMap_.set(methodName, promise);
+  }
+
+  protected handleMethod(methodName: string, arg?: any): Promise<any> {
+    this.methodCalled(methodName, arg);
+    const promise = this.promiseMap_.get(methodName);
+    if (promise !== undefined) {
+      this.promiseMap_.delete(methodName);
+      return promise;
+    }
+
+    // Return a Promise that never resolves.
+    return new Promise(() => {});
+  }
+}
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
index aacdf324..16a1964 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
@@ -13,7 +13,7 @@
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
-import {installMock} from './test_support.js';
+import {createTheme, installMock} from './test_support.js';
 
 suite('ColorsTest', () => {
   let colorsElement: ColorsElement;
@@ -42,11 +42,7 @@
   ] as Array<[boolean, Color]>)
       .forEach(([systemDarkMode, defaultColor]) => {
         test('renders default color', async () => {
-          const theme: Theme = {
-            backgroundImage: undefined,
-            systemDarkMode,
-            foregroundColor: undefined,
-          };
+          const theme: Theme = createTheme(systemDarkMode);
 
           callbackRouter.setTheme(theme);
           await callbackRouter.$.flushForTesting();
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
index 84b458c..dd47307 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
@@ -45,10 +45,11 @@
   };
 }
 
-export function createTheme(): Theme {
+export function createTheme(systemDarkMode = false): Theme {
   return {
     backgroundImage: undefined,
-    systemDarkMode: false,
+    systemDarkMode,
+    backgroundColor: {value: 0xffff0000},
     foregroundColor: undefined,
   };
 }
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
index 6da6b1e4..3fcfa8b 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
@@ -11,7 +11,7 @@
 import {assertEquals} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
-import {$$, createBackgroundImage, createTheme, installMock} from './test_support.js';
+import {$$, assertStyle, createBackgroundImage, createTheme, installMock} from './test_support.js';
 
 
 suite('ThemeSnapshotTest', () => {
@@ -54,6 +54,24 @@
     assertEquals(1, handler.getCallCount('updateTheme'));
     assertEquals(
         'chrome://theme/foo',
-        $$<HTMLImageElement>(themeSnapshotElement, '#themeSnapshot img')!.src);
+        $$<HTMLImageElement>(
+            themeSnapshotElement, '#themeSnapshot .image')!.src);
+  });
+
+  test('having no theme set updates preview background color', async () => {
+    // Arrange.
+    createThemeSnapshotElement();
+    const theme = createTheme();
+    theme.backgroundColor = {value: 4279522202};
+
+    // Act.
+    callbackRouterRemote.setTheme(theme);
+    await callbackRouterRemote.$.flushForTesting();
+
+    // Assert.
+    assertEquals(1, handler.getCallCount('updateTheme'));
+    assertStyle(
+        $$(themeSnapshotElement, '#themeSnapshot .image')!, 'background-color',
+        'rgb(20, 83, 154)');
   });
 });
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index 3fc3b07..1cac6a5 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -789,11 +789,11 @@
 STDMETHODIMP PolicyStatusImpl::get_lastCheckPeriodMinutes(DWORD* minutes) {
   DCHECK(minutes);
 
-  int period = 0;
-  if (!policy_service_->GetLastCheckPeriodMinutes(nullptr, &period))
+  PolicyStatus<int> period = policy_service_->GetLastCheckPeriodMinutes();
+  if (!period)
     return E_FAIL;
 
-  *minutes = period;
+  *minutes = period.policy();
   return S_OK;
 }
 
@@ -807,21 +807,21 @@
   DCHECK(duration_min);
   DCHECK(are_updates_suppressed);
 
-  UpdatesSuppressedTimes updates_suppressed_times;
-  if (!policy_service_->GetUpdatesSuppressedTimes(nullptr,
-                                                  &updates_suppressed_times) ||
-      !updates_suppressed_times.valid()) {
+  PolicyStatus<UpdatesSuppressedTimes> updates_suppressed_times =
+      policy_service_->GetUpdatesSuppressedTimes();
+  if (!updates_suppressed_times || !updates_suppressed_times.policy().valid()) {
     return E_FAIL;
   }
 
   base::Time::Exploded now;
   base::Time::Now().LocalExplode(&now);
-  *start_hour = updates_suppressed_times.start_hour_;
-  *start_min = updates_suppressed_times.start_minute_;
-  *duration_min = updates_suppressed_times.duration_minute_;
+  *start_hour = updates_suppressed_times.policy().start_hour_;
+  *start_min = updates_suppressed_times.policy().start_minute_;
+  *duration_min = updates_suppressed_times.policy().duration_minute_;
   *are_updates_suppressed =
-      updates_suppressed_times.contains(now.hour, now.minute) ? VARIANT_TRUE
-                                                              : VARIANT_FALSE;
+      updates_suppressed_times.policy().contains(now.hour, now.minute)
+          ? VARIANT_TRUE
+          : VARIANT_FALSE;
 
   return S_OK;
 }
@@ -829,40 +829,40 @@
 STDMETHODIMP PolicyStatusImpl::get_downloadPreferenceGroupPolicy(BSTR* pref) {
   DCHECK(pref);
 
-  std::string download_preference;
-  if (!policy_service_->GetDownloadPreferenceGroupPolicy(
-          nullptr, &download_preference)) {
+  PolicyStatus<std::string> download_preference =
+      policy_service_->GetDownloadPreferenceGroupPolicy();
+  if (!download_preference) {
     return E_FAIL;
   }
 
-  *pref =
-      base::win::ScopedBstr(base::ASCIIToWide(download_preference)).Release();
+  *pref = base::win::ScopedBstr(base::ASCIIToWide(download_preference.policy()))
+              .Release();
   return S_OK;
 }
 
 STDMETHODIMP PolicyStatusImpl::get_packageCacheSizeLimitMBytes(DWORD* limit) {
   DCHECK(limit);
 
-  int cache_size_limit = 0;
-  if (!policy_service_->GetPackageCacheSizeLimitMBytes(nullptr,
-                                                       &cache_size_limit)) {
+  PolicyStatus<int> cache_size_limit =
+      policy_service_->GetPackageCacheSizeLimitMBytes();
+  if (!cache_size_limit) {
     return E_FAIL;
   }
 
-  *limit = cache_size_limit;
+  *limit = cache_size_limit.policy();
   return S_OK;
 }
 
 STDMETHODIMP PolicyStatusImpl::get_packageCacheExpirationTimeDays(DWORD* days) {
   DCHECK(days);
 
-  int cache_life_limit = 0;
-  if (!policy_service_->GetPackageCacheExpirationTimeDays(nullptr,
-                                                          &cache_life_limit)) {
+  PolicyStatus<int> cache_life_limit =
+      policy_service_->GetPackageCacheExpirationTimeDays();
+  if (!cache_life_limit) {
     return E_FAIL;
   }
 
-  *days = cache_life_limit;
+  *days = cache_life_limit.policy();
   return S_OK;
 }
 
@@ -871,13 +871,13 @@
     DWORD* policy) {
   DCHECK(policy);
 
-  int install_policy = 0;
-  if (!policy_service_->GetEffectivePolicyForAppInstalls(
-          base::WideToASCII(app_id), nullptr, &install_policy)) {
+  PolicyStatus<int> install_policy =
+      policy_service_->GetPolicyForAppInstalls(base::WideToASCII(app_id));
+  if (!install_policy) {
     return E_FAIL;
   }
 
-  *policy = install_policy;
+  *policy = install_policy.policy();
   return S_OK;
 }
 
@@ -885,13 +885,13 @@
                                                                 DWORD* policy) {
   DCHECK(policy);
 
-  int update_policy = 0;
-  if (!policy_service_->GetEffectivePolicyForAppUpdates(
-          base::WideToASCII(app_id), nullptr, &update_policy)) {
+  PolicyStatus<int> update_policy =
+      policy_service_->GetPolicyForAppUpdates(base::WideToASCII(app_id));
+  if (!update_policy) {
     return E_FAIL;
   }
 
-  *policy = update_policy;
+  *policy = update_policy.policy();
   return S_OK;
 }
 
@@ -899,14 +899,15 @@
                                                        BSTR* prefix) {
   DCHECK(prefix);
 
-  std::string target_version_prefix;
-  if (!policy_service_->GetTargetVersionPrefix(
-          base::WideToASCII(app_id), nullptr, &target_version_prefix)) {
+  PolicyStatus<std::string> target_version_prefix =
+      policy_service_->GetTargetVersionPrefix(base::WideToASCII(app_id));
+  if (!target_version_prefix) {
     return E_FAIL;
   }
 
   *prefix =
-      base::win::ScopedBstr(base::ASCIIToWide(target_version_prefix)).Release();
+      base::win::ScopedBstr(base::ASCIIToWide(target_version_prefix.policy()))
+          .Release();
   return S_OK;
 }
 
@@ -915,13 +916,15 @@
     VARIANT_BOOL* rollback_allowed) {
   DCHECK(rollback_allowed);
 
-  bool is_rollback_allowed = false;
-  if (!policy_service_->IsRollbackToTargetVersionAllowed(
-          base::WideToASCII(app_id), nullptr, &is_rollback_allowed)) {
+  PolicyStatus<bool> is_rollback_allowed =
+      policy_service_->IsRollbackToTargetVersionAllowed(
+          base::WideToASCII(app_id));
+  if (!is_rollback_allowed) {
     return E_FAIL;
   }
 
-  *rollback_allowed = is_rollback_allowed ? VARIANT_TRUE : VARIANT_FALSE;
+  *rollback_allowed =
+      is_rollback_allowed.policy() ? VARIANT_TRUE : VARIANT_FALSE;
   return S_OK;
 }
 
@@ -950,7 +953,7 @@
 class PolicyStatusResult
     : public base::RefCountedThreadSafe<PolicyStatusResult<T>> {
  public:
-  using ValueGetter = base::RepeatingCallback<bool(PolicyStatus<T>*, T*)>;
+  using ValueGetter = base::RepeatingCallback<PolicyStatus<T>()>;
 
   static auto Get(ValueGetter value_getter) {
     auto result = base::WrapRefCounted(new PolicyStatusResult<T>(value_getter));
@@ -969,8 +972,8 @@
       : value_getter(value_getter) {}
 
   void GetValueOnSequence() {
-    PolicyStatus<T> policy_status;
-    if (value_getter.Run(&policy_status, nullptr)) {
+    PolicyStatus<T> policy_status = value_getter.Run();
+    if (policy_status) {
       value = policy_status;
     }
     completion_event.Signal();
@@ -1139,7 +1142,7 @@
     IPolicyStatusValue** value) {
   DCHECK(value);
   auto policy_status = PolicyStatusResult<int>::Get(
-      base::BindRepeating(&PolicyService::GetEffectivePolicyForAppInstalls,
+      base::BindRepeating(&PolicyService::GetPolicyForAppInstalls,
                           policy_service_, base::WideToASCII(app_id)));
   return policy_status.has_value()
              ? PolicyStatusValueImpl::Create(*policy_status, value)
@@ -1151,7 +1154,7 @@
     IPolicyStatusValue** value) {
   DCHECK(value);
   auto policy_status = PolicyStatusResult<int>::Get(
-      base::BindRepeating(&PolicyService::GetEffectivePolicyForAppUpdates,
+      base::BindRepeating(&PolicyService::GetPolicyForAppUpdates,
                           policy_service_, base::WideToASCII(app_id)));
   return policy_status.has_value()
              ? PolicyStatusValueImpl::Create(*policy_status, value)
diff --git a/chrome/updater/auto_run_on_os_upgrade_task_unittest.cc b/chrome/updater/auto_run_on_os_upgrade_task_unittest.cc
index ed906137..84255fe 100644
--- a/chrome/updater/auto_run_on_os_upgrade_task_unittest.cc
+++ b/chrome/updater/auto_run_on_os_upgrade_task_unittest.cc
@@ -32,10 +32,10 @@
 constexpr wchar_t kAppId[] = L"{3B1A3CCA-0525-4418-93E6-A0DB3398EC9B}";
 constexpr wchar_t kCmdId1[] = L"CreateOSVersionsFileOnOSUpgrade";
 constexpr wchar_t kCmdLineCreateOSVersionsFile[] =
-    L"cmd.exe /c \"echo %1 > %1 && exit 0\"";
+    L"/c \"echo %1 > %1 && exit 0\"";
 constexpr wchar_t kCmdId2[] = L"CreateHardcodedFileOnOSUpgrade";
 constexpr wchar_t kCmdLineCreateHardcodedFile[] =
-    L"cmd.exe /c \"echo HardcodedFile > HardcodedFile && exit 0\"";
+    L"/c \"echo HardcodedFile > HardcodedFile && exit 0\"";
 constexpr char kLastOSVersion[] = "last_os_version";
 
 }  // namespace
@@ -88,11 +88,11 @@
 
   CreateAppCommandOSUpgradeRegistry(
       GetTestScope(), kAppId, kCmdId1,
-      base::StrCat({cmd_exe_command_line_.GetCommandLineString(),
+      base::StrCat({cmd_exe_command_line_.GetCommandLineString(), L" ",
                     kCmdLineCreateOSVersionsFile}));
   CreateAppCommandOSUpgradeRegistry(
       GetTestScope(), kAppId, kCmdId2,
-      base::StrCat({cmd_exe_command_line_.GetCommandLineString(),
+      base::StrCat({cmd_exe_command_line_.GetCommandLineString(), L" ",
                     kCmdLineCreateHardcodedFile}));
 
   ASSERT_EQ(os_upgrade_task->RunOnOsUpgradeForApp(base::WideToASCII(kAppId)),
diff --git a/chrome/updater/check_for_updates_task.cc b/chrome/updater/check_for_updates_task.cc
index 74defd4..5986fa00 100644
--- a/chrome/updater/check_for_updates_task.cc
+++ b/chrome/updater/check_for_updates_task.cc
@@ -50,13 +50,12 @@
   }
 
   // Skip if the updater is in the update suppression period.
-  UpdatesSuppressedTimes suppression;
-  if (config->GetPolicyService()->GetUpdatesSuppressedTimes(nullptr,
-                                                            &suppression) &&
-      suppression.valid()) {
+  PolicyStatus<UpdatesSuppressedTimes> suppression =
+      config->GetPolicyService()->GetUpdatesSuppressedTimes();
+  if (suppression && suppression.policy().valid()) {
     base::Time::Exploded now;
     base::Time::Now().LocalExplode(&now);
-    if (suppression.contains(now.hour, now.minute)) {
+    if (suppression.policy().contains(now.hour, now.minute)) {
       VLOG(0) << "Skipping checking for updates: in update suppression period.";
       return true;
     }
diff --git a/chrome/updater/configurator.cc b/chrome/updater/configurator.cc
index 20e187ba..beea40b 100644
--- a/chrome/updater/configurator.cc
+++ b/chrome/updater/configurator.cc
@@ -76,9 +76,9 @@
 }
 
 int Configurator::NextCheckDelay() const {
-  int minutes = 0;
-  CHECK(policy_service_->GetLastCheckPeriodMinutes(nullptr, &minutes));
-  return base::Minutes(minutes).InSeconds();
+  PolicyStatus<int> minutes = policy_service_->GetLastCheckPeriodMinutes();
+  CHECK(minutes);
+  return base::Minutes(minutes.policy()).InSeconds();
 }
 
 int Configurator::OnDemandDelay() const {
@@ -123,10 +123,9 @@
 }
 
 std::string Configurator::GetDownloadPreference() const {
-  std::string preference;
-  return policy_service_->GetDownloadPreferenceGroupPolicy(nullptr, &preference)
-             ? preference
-             : std::string();
+  PolicyStatus<std::string> preference =
+      policy_service_->GetDownloadPreferenceGroupPolicy();
+  return preference ? preference.policy() : std::string();
 }
 
 scoped_refptr<update_client::NetworkFetcherFactory>
diff --git a/chrome/updater/policy/dm_policy_manager.cc b/chrome/updater/policy/dm_policy_manager.cc
index bf2fc4b..93a9cef 100644
--- a/chrome/updater/policy/dm_policy_manager.cc
+++ b/chrome/updater/policy/dm_policy_manager.cc
@@ -73,73 +73,66 @@
   return std::string("DeviceManagement");
 }
 
-bool DMPolicyManager::GetLastCheckPeriodMinutes(int* minutes) const {
+absl::optional<int> DMPolicyManager::GetLastCheckPeriodMinutes() const {
   if (!omaha_settings_.has_auto_update_check_period_minutes())
-    return false;
+    return absl::nullopt;
 
-  *minutes =
-      static_cast<int>(omaha_settings_.auto_update_check_period_minutes());
-  return true;
+  return static_cast<int>(omaha_settings_.auto_update_check_period_minutes());
 }
 
-bool DMPolicyManager::GetUpdatesSuppressedTimes(
-    UpdatesSuppressedTimes* suppressed_times) const {
+absl::optional<UpdatesSuppressedTimes>
+DMPolicyManager::GetUpdatesSuppressedTimes() const {
   if (!omaha_settings_.has_updates_suppressed())
-    return false;
+    return absl::nullopt;
 
   const auto& updates_suppressed = omaha_settings_.updates_suppressed();
   if (!updates_suppressed.has_start_hour() ||
       !updates_suppressed.has_start_minute() ||
       !updates_suppressed.has_duration_min())
-    return false;
+    return absl::nullopt;
 
-  suppressed_times->start_hour_ = updates_suppressed.start_hour();
-  suppressed_times->start_minute_ = updates_suppressed.start_minute();
-  suppressed_times->duration_minute_ = updates_suppressed.duration_min();
-  return true;
+  UpdatesSuppressedTimes suppressed_times;
+  suppressed_times.start_hour_ = updates_suppressed.start_hour();
+  suppressed_times.start_minute_ = updates_suppressed.start_minute();
+  suppressed_times.duration_minute_ = updates_suppressed.duration_min();
+  return suppressed_times;
 }
 
-bool DMPolicyManager::GetDownloadPreferenceGroupPolicy(
-    std::string* download_preference) const {
+absl::optional<std::string> DMPolicyManager::GetDownloadPreferenceGroupPolicy()
+    const {
   if (!omaha_settings_.has_download_preference())
-    return false;
+    return absl::nullopt;
 
-  *download_preference = omaha_settings_.download_preference();
-  return true;
+  return omaha_settings_.download_preference();
 }
 
-bool DMPolicyManager::GetPackageCacheSizeLimitMBytes(
-    int* cache_size_limit) const {
-  return false;
+absl::optional<int> DMPolicyManager::GetPackageCacheSizeLimitMBytes() const {
+  return absl::nullopt;
 }
 
-bool DMPolicyManager::GetPackageCacheExpirationTimeDays(
-    int* cache_life_limit) const {
-  return false;
+absl::optional<int> DMPolicyManager::GetPackageCacheExpirationTimeDays() const {
+  return absl::nullopt;
 }
 
-bool DMPolicyManager::GetProxyMode(std::string* proxy_mode) const {
+absl::optional<std::string> DMPolicyManager::GetProxyMode() const {
   if (!omaha_settings_.has_proxy_mode())
-    return false;
+    return absl::nullopt;
 
-  *proxy_mode = omaha_settings_.proxy_mode();
-  return true;
+  return omaha_settings_.proxy_mode();
 }
 
-bool DMPolicyManager::GetProxyPacUrl(std::string* proxy_pac_url) const {
+absl::optional<std::string> DMPolicyManager::GetProxyPacUrl() const {
   if (!omaha_settings_.has_proxy_pac_url())
-    return false;
+    return absl::nullopt;
 
-  *proxy_pac_url = omaha_settings_.proxy_pac_url();
-  return true;
+  return omaha_settings_.proxy_pac_url();
 }
 
-bool DMPolicyManager::GetProxyServer(std::string* proxy_server) const {
+absl::optional<std::string> DMPolicyManager::GetProxyServer() const {
   if (!omaha_settings_.has_proxy_server())
-    return false;
+    return absl::nullopt;
 
-  *proxy_server = omaha_settings_.proxy_server();
-  return true;
+  return omaha_settings_.proxy_server();
 }
 
 const ::wireless_android_enterprise_devicemanagement::ApplicationSettings*
@@ -164,82 +157,69 @@
   return nullptr;
 }
 
-bool DMPolicyManager::GetEffectivePolicyForAppInstalls(
-    const std::string& app_id,
-    int* install_policy) const {
+absl::optional<int> DMPolicyManager::GetEffectivePolicyForAppInstalls(
+    const std::string& app_id) const {
   const auto* app_settings = GetAppSettings(app_id);
   if (app_settings && app_settings->has_install()) {
-    *install_policy = PolicyValueFromProtoInstallValue(app_settings->install());
-    return true;
+    return PolicyValueFromProtoInstallValue(app_settings->install());
   }
 
   // Fallback to global-level settings.
   if (omaha_settings_.has_install_default()) {
-    *install_policy =
-        PolicyValueFromProtoInstallValue(omaha_settings_.install_default());
-    return true;
+    return PolicyValueFromProtoInstallValue(omaha_settings_.install_default());
   }
 
-  return false;
+  return absl::nullopt;
 }
 
-bool DMPolicyManager::GetEffectivePolicyForAppUpdates(
-    const std::string& app_id,
-    int* update_policy) const {
+absl::optional<int> DMPolicyManager::GetEffectivePolicyForAppUpdates(
+    const std::string& app_id) const {
   const auto* app_settings = GetAppSettings(app_id);
   if (app_settings && app_settings->has_update()) {
-    *update_policy = PolicyValueFromProtoUpdateValue(app_settings->update());
-    return true;
+    return PolicyValueFromProtoUpdateValue(app_settings->update());
   }
 
   // Fallback to global-level settings.
   if (omaha_settings_.has_update_default()) {
-    *update_policy =
-        PolicyValueFromProtoUpdateValue(omaha_settings_.update_default());
-    return true;
+    return PolicyValueFromProtoUpdateValue(omaha_settings_.update_default());
   }
 
-  return false;
+  return absl::nullopt;
 }
 
-bool DMPolicyManager::GetTargetVersionPrefix(
-    const std::string& app_id,
-    std::string* target_version_prefix) const {
+absl::optional<std::string> DMPolicyManager::GetTargetVersionPrefix(
+    const std::string& app_id) const {
   const auto* app_settings = GetAppSettings(app_id);
   if (!app_settings || !app_settings->has_target_version_prefix())
-    return false;
+    return absl::nullopt;
 
-  *target_version_prefix = app_settings->target_version_prefix();
-  return true;
+  return app_settings->target_version_prefix();
 }
 
-bool DMPolicyManager::GetTargetChannel(const std::string& app_id,
-                                       std::string* channel) const {
+absl::optional<std::string> DMPolicyManager::GetTargetChannel(
+    const std::string& app_id) const {
   const auto* app_settings = GetAppSettings(app_id);
   if (!app_settings || !app_settings->has_target_channel())
-    return false;
+    return absl::nullopt;
 
-  *channel = app_settings->target_channel();
-  return true;
+  return app_settings->target_channel();
 }
 
-bool DMPolicyManager::IsRollbackToTargetVersionAllowed(
-    const std::string& app_id,
-    bool* rollback_allowed) const {
+absl::optional<bool> DMPolicyManager::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id) const {
   const auto* app_settings = GetAppSettings(app_id);
   if (!app_settings || !app_settings->has_rollback_to_target_version())
-    return false;
+    return absl::nullopt;
 
-  *rollback_allowed = (app_settings->rollback_to_target_version() ==
-                       ::wireless_android_enterprise_devicemanagement::
-                           ROLLBACK_TO_TARGET_VERSION_ENABLED);
-  return true;
+  return (app_settings->rollback_to_target_version() ==
+          ::wireless_android_enterprise_devicemanagement::
+              ROLLBACK_TO_TARGET_VERSION_ENABLED);
 }
 
 // TODO(crbug.com/1347562): implement retrieving the force installs apps.
-bool DMPolicyManager::GetForceInstallApps(
-    std::vector<std::string>* /* force_install_apps */) const {
-  return false;
+absl::optional<std::vector<std::string>> DMPolicyManager::GetForceInstallApps()
+    const {
+  return absl::nullopt;
 }
 
 std::unique_ptr<PolicyManagerInterface> CreateDMPolicyManager() {
diff --git a/chrome/updater/policy/dm_policy_manager.h b/chrome/updater/policy/dm_policy_manager.h
index be3bad1..483ee8b 100644
--- a/chrome/updater/policy/dm_policy_manager.h
+++ b/chrome/updater/policy/dm_policy_manager.h
@@ -30,30 +30,26 @@
 
   bool HasActiveDevicePolicies() const override;
 
-  bool GetLastCheckPeriodMinutes(int* minutes) const override;
-  bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const override;
-  bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const override;
-  bool GetProxyMode(std::string* proxy_mode) const override;
-  bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
-  bool GetProxyServer(std::string* proxy_server) const override;
-  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override;
-  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override;
-
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        int* install_policy) const override;
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       int* update_policy) const override;
-  bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const override;
-  bool GetTargetChannel(const std::string& app_id,
-                        std::string* channel) const override;
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        bool* rollback_allowed) const override;
-  bool GetForceInstallApps(
-      std::vector<std::string>* force_install_apps) const override;
+  absl::optional<int> GetLastCheckPeriodMinutes() const override;
+  absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const override;
+  absl::optional<std::string> GetDownloadPreferenceGroupPolicy() const override;
+  absl::optional<int> GetPackageCacheSizeLimitMBytes() const override;
+  absl::optional<int> GetPackageCacheExpirationTimeDays() const override;
+  absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const override;
+  absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const override;
+  absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetProxyMode() const override;
+  absl::optional<std::string> GetProxyPacUrl() const override;
+  absl::optional<std::string> GetProxyServer() const override;
+  absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const override;
+  absl::optional<std::vector<std::string>> GetForceInstallApps() const override;
 
  private:
   const ::wireless_android_enterprise_devicemanagement::ApplicationSettings*
diff --git a/chrome/updater/policy/dm_policy_manager_unittest.cc b/chrome/updater/policy/dm_policy_manager_unittest.cc
index df7b330a..994beaa 100644
--- a/chrome/updater/policy/dm_policy_manager_unittest.cc
+++ b/chrome/updater/policy/dm_policy_manager_unittest.cc
@@ -113,45 +113,22 @@
 #endif  // BUILDFLAG(IS_LINUX)
   EXPECT_EQ(policy_manager->source(), "DeviceManagement");
 
-  int last_check_period_minutes = 0;
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
   EXPECT_FALSE(
-      policy_manager->GetLastCheckPeriodMinutes(&last_check_period_minutes));
-
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
+      policy_manager->GetEffectivePolicyForAppInstalls(test::kChromeAppId));
   EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-
-  int size_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheSizeLimitMBytes(&size_limit));
-  int time_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheExpirationTimeDays(&time_limit));
-
-  // Verify app-specific polices.
-  int install_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      test::kChromeAppId, &install_policy));
-  int update_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(
-      test::kChromeAppId, &update_policy));
-  bool rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      test::kChromeAppId, &rollback_allowed));
-
-  std::string target_version_prefix;
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix(test::kChromeAppId,
-                                                      &target_version_prefix));
+      policy_manager->GetEffectivePolicyForAppUpdates(test::kChromeAppId));
+  EXPECT_FALSE(
+      policy_manager->IsRollbackToTargetVersionAllowed(test::kChromeAppId));
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(test::kChromeAppId),
+            absl::nullopt);
 }
 
 TEST(DMPolicyManager, PolicyManagerFromProto) {
@@ -189,74 +166,47 @@
 #endif  // BUILDFLAG(IS_LINUX)
   EXPECT_EQ(policy_manager->source(), "DeviceManagement");
 
-  int last_check_period_minutes = 0;
-  EXPECT_TRUE(
-      policy_manager->GetLastCheckPeriodMinutes(&last_check_period_minutes));
-  EXPECT_EQ(last_check_period_minutes, 111);
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), 111);
 
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_TRUE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-  EXPECT_EQ(suppressed_times.start_hour_, 9);
-  EXPECT_EQ(suppressed_times.start_minute_, 30);
-  EXPECT_EQ(suppressed_times.duration_minute_, 120);
+  absl::optional<UpdatesSuppressedTimes> suppressed_times =
+      policy_manager->GetUpdatesSuppressedTimes();
+  ASSERT_TRUE(suppressed_times);
+  EXPECT_EQ(suppressed_times->start_hour_, 9);
+  EXPECT_EQ(suppressed_times->start_minute_, 30);
+  EXPECT_EQ(suppressed_times->duration_minute_, 120);
 
-  std::string download_preference;
-  EXPECT_TRUE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-  EXPECT_EQ(download_preference, "test_download_preference");
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(),
+            "test_download_preference");
 
-  std::string proxy_mode;
-  EXPECT_TRUE(policy_manager->GetProxyMode(&proxy_mode));
-  EXPECT_EQ(proxy_mode, "test_proxy_mode");
+  EXPECT_EQ(policy_manager->GetProxyMode(), "test_proxy_mode");
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), "foo.c/proxy.pa");
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
 
-  std::string proxy_pac_url;
-  EXPECT_TRUE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-  EXPECT_EQ(proxy_pac_url, "foo.c/proxy.pa");
-
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-
-  int size_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheSizeLimitMBytes(&size_limit));
-  int time_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheExpirationTimeDays(&time_limit));
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
 
   // Verify app-specific polices.
-  int install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      test::kChromeAppId, &install_policy));
-  EXPECT_EQ(install_policy, kPolicyDisabled);
-  int update_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-      test::kChromeAppId, &update_policy));
-  EXPECT_EQ(update_policy, kPolicyAutomaticUpdatesOnly);
-  bool rollback_allowed = false;
-  EXPECT_TRUE(policy_manager->IsRollbackToTargetVersionAllowed(
-      test::kChromeAppId, &rollback_allowed));
-  EXPECT_TRUE(rollback_allowed);
-
-  std::string target_version_prefix;
-  EXPECT_TRUE(policy_manager->GetTargetVersionPrefix(test::kChromeAppId,
-                                                     &target_version_prefix));
-  EXPECT_EQ(target_version_prefix, "81.");
+  EXPECT_EQ(
+      policy_manager->GetEffectivePolicyForAppInstalls(test::kChromeAppId),
+      kPolicyDisabled);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(test::kChromeAppId),
+            kPolicyAutomaticUpdatesOnly);
+  EXPECT_EQ(
+      policy_manager->IsRollbackToTargetVersionAllowed(test::kChromeAppId),
+      true);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(test::kChromeAppId), "81.");
 
   // Verify that if no app-specific polices, fallback to global-level policies
   // or return false if no fallback is available.
   const std::string app_guid = "ArbitraryAppGuid";
-  install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      app_guid, &install_policy));
-  EXPECT_EQ(install_policy, kPolicyEnabled);
-  update_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(app_guid,
-                                                              &update_policy));
-  EXPECT_EQ(update_policy, kPolicyManualUpdatesOnly);
-  rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      app_guid, &rollback_allowed));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(app_guid),
+            kPolicyEnabled);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(app_guid),
+            kPolicyManualUpdatesOnly);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(app_guid),
+            absl::nullopt);
 
-  EXPECT_FALSE(
-      policy_manager->GetTargetVersionPrefix(app_guid, &target_version_prefix));
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(app_guid), absl::nullopt);
 }
 
 #if BUILDFLAG(IS_MAC)
@@ -279,47 +229,24 @@
   EXPECT_EQ(policy_manager->HasActiveDevicePolicies(), base::IsManagedDevice());
   EXPECT_EQ(policy_manager->source(), "DeviceManagement");
 
-  int last_check_period_minutes = 0;
-  EXPECT_FALSE(
-      policy_manager->GetLastCheckPeriodMinutes(&last_check_period_minutes));
-
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
-  EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-
-  int size_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheSizeLimitMBytes(&size_limit));
-  int time_limit = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheExpirationTimeDays(&time_limit));
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
 
   const std::string chrome_guid = "com.google.Chrome";
-  int install_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      chrome_guid, &install_policy));
-  int update_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(chrome_guid,
-                                                               &update_policy));
-  bool rollback_allowed = false;
-  EXPECT_TRUE(policy_manager->IsRollbackToTargetVersionAllowed(
-      chrome_guid, &rollback_allowed));
-  EXPECT_TRUE(rollback_allowed);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(chrome_guid),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(chrome_guid),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(chrome_guid),
+            true);
 
-  std::string target_version_prefix;
-  EXPECT_TRUE(policy_manager->GetTargetVersionPrefix(chrome_guid,
-                                                     &target_version_prefix));
-  EXPECT_EQ(target_version_prefix, "82.0.");
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(chrome_guid), "82.0.");
 }
 
 #endif  // BUILDFLAG(IS_MAC)
diff --git a/chrome/updater/policy/mac/managed_preference_policy_manager.mm b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
index a02fe36..afaf71c 100644
--- a/chrome/updater/policy/mac/managed_preference_policy_manager.mm
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
@@ -10,6 +10,7 @@
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
+#include "chrome/updater/constants.h"
 #include "chrome/updater/policy/mac/managed_preference_policy_manager_impl.h"
 #include "chrome/updater/policy/manager.h"
 
@@ -32,30 +33,26 @@
 
   bool HasActiveDevicePolicies() const override;
 
-  bool GetLastCheckPeriodMinutes(int* minutes) const override;
-  bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const override;
-  bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const override;
-  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override;
-  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override;
-
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        int* install_policy) const override;
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       int* update_policy) const override;
-  bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const override;
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        bool* rollback_allowed) const override;
-  bool GetProxyMode(std::string* proxy_mode) const override;
-  bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
-  bool GetProxyServer(std::string* proxy_server) const override;
-  bool GetTargetChannel(const std::string& app_id,
-                        std::string* channel) const override;
-  bool GetForceInstallApps(
-      std::vector<std::string>* force_install_apps) const override;
+  absl::optional<int> GetLastCheckPeriodMinutes() const override;
+  absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const override;
+  absl::optional<std::string> GetDownloadPreferenceGroupPolicy() const override;
+  absl::optional<int> GetPackageCacheSizeLimitMBytes() const override;
+  absl::optional<int> GetPackageCacheExpirationTimeDays() const override;
+  absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const override;
+  absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const override;
+  absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetProxyMode() const override;
+  absl::optional<std::string> GetProxyPacUrl() const override;
+  absl::optional<std::string> GetProxyServer() const override;
+  absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const override;
+  absl::optional<std::vector<std::string>> GetForceInstallApps() const override;
 
  private:
   base::scoped_nsobject<CRUManagedPreferencePolicyManager> impl_;
@@ -76,131 +73,107 @@
   return base::SysNSStringToUTF8([impl_ source]);
 }
 
-bool ManagedPreferencePolicyManager::GetLastCheckPeriodMinutes(
-    int* minutes) const {
-  *minutes = [impl_ lastCheckPeriodMinutes];
-  return (*minutes != kPolicyNotSet);
+absl::optional<int> ManagedPreferencePolicyManager::GetLastCheckPeriodMinutes()
+    const {
+  int minutes = [impl_ lastCheckPeriodMinutes];
+  return minutes != kPolicyNotSet ? absl::optional<int>(minutes)
+                                  : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetUpdatesSuppressedTimes(
-    UpdatesSuppressedTimes* suppressed_times) const {
-  *suppressed_times = [impl_ updatesSuppressed];
-  return suppressed_times->valid();
+absl::optional<UpdatesSuppressedTimes>
+ManagedPreferencePolicyManager::GetUpdatesSuppressedTimes() const {
+  UpdatesSuppressedTimes suppressed_times = [impl_ updatesSuppressed];
+  return suppressed_times.valid()
+             ? absl::optional<UpdatesSuppressedTimes>(suppressed_times)
+             : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetDownloadPreferenceGroupPolicy(
-    std::string* download_preference) const {
+absl::optional<std::string>
+ManagedPreferencePolicyManager::GetDownloadPreferenceGroupPolicy() const {
   NSString* value = [impl_ downloadPreference];
-  if (value) {
-    *download_preference = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetPackageCacheSizeLimitMBytes(
-    int* cache_size_limit) const {
-  return false;  // Not supported on Mac.
+absl::optional<int>
+ManagedPreferencePolicyManager::GetPackageCacheSizeLimitMBytes() const {
+  return absl::nullopt;  // Not supported on Mac.
 }
 
-bool ManagedPreferencePolicyManager::GetPackageCacheExpirationTimeDays(
-    int* cache_life_limit) const {
-  return false;  // Not supported on Mac.
+absl::optional<int>
+ManagedPreferencePolicyManager::GetPackageCacheExpirationTimeDays() const {
+  return absl::nullopt;  // Not supported on Mac.
 }
 
-bool ManagedPreferencePolicyManager::GetEffectivePolicyForAppInstalls(
-    const std::string& app_id,
-    int* install_policy) const {
-  return false;  // Not supported on Mac.
+absl::optional<int>
+ManagedPreferencePolicyManager::GetEffectivePolicyForAppInstalls(
+    const std::string& app_id) const {
+  return absl::nullopt;  // Not supported on Mac.
 }
 
-bool ManagedPreferencePolicyManager::GetEffectivePolicyForAppUpdates(
-    const std::string& app_id,
-    int* update_policy) const {
+absl::optional<int>
+ManagedPreferencePolicyManager::GetEffectivePolicyForAppUpdates(
+    const std::string& app_id) const {
   // Check app-specific settings first.
-  *update_policy = [impl_ appUpdatePolicy:base::SysUTF8ToNSString(app_id)];
-  if (*update_policy != kPolicyNotSet)
-    return true;
+  int update_policy = [impl_ appUpdatePolicy:base::SysUTF8ToNSString(app_id)];
+  if (update_policy != kPolicyNotSet)
+    return update_policy;
 
   // Then fallback to global-level policy if needed.
-  *update_policy = [impl_ defaultUpdatePolicy];
-  return (*update_policy != kPolicyNotSet);
+  update_policy = [impl_ defaultUpdatePolicy];
+  return update_policy != kPolicyNotSet ? absl::optional<int>(update_policy)
+                                        : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetTargetVersionPrefix(
-    const std::string& app_id,
-    std::string* target_version_prefix) const {
+absl::optional<std::string>
+ManagedPreferencePolicyManager::GetTargetVersionPrefix(
+    const std::string& app_id) const {
   NSString* value = [impl_ targetVersionPrefix:base::SysUTF8ToNSString(app_id)];
-  if (value) {
-    *target_version_prefix = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::IsRollbackToTargetVersionAllowed(
-    const std::string& app_id,
-    bool* rollback_allowed) const {
+absl::optional<bool>
+ManagedPreferencePolicyManager::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id) const {
   int rollback_policy =
       [impl_ rollbackToTargetVersion:base::SysUTF8ToNSString(app_id)];
-  if (rollback_policy != kPolicyNotSet) {
-    *rollback_allowed = (rollback_policy != 0);
-    return true;
-  }
-
-  return false;
+  return rollback_policy != kPolicyNotSet
+             ? absl::optional<bool>(rollback_policy != 0)
+             : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetProxyMode(
-    std::string* proxy_mode) const {
+absl::optional<std::string> ManagedPreferencePolicyManager::GetProxyMode()
+    const {
   NSString* value = [impl_ proxyMode];
-  if (value) {
-    *proxy_mode = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetProxyPacUrl(
-    std::string* proxy_pac_url) const {
+absl::optional<std::string> ManagedPreferencePolicyManager::GetProxyPacUrl()
+    const {
   NSString* value = [impl_ proxyPacURL];
-  if (value) {
-    *proxy_pac_url = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetProxyServer(
-    std::string* proxy_server) const {
+absl::optional<std::string> ManagedPreferencePolicyManager::GetProxyServer()
+    const {
   NSString* value = [impl_ proxyServer];
-  if (value) {
-    *proxy_server = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetTargetChannel(
-    const std::string& app_id,
-    std::string* channel) const {
+absl::optional<std::string> ManagedPreferencePolicyManager::GetTargetChannel(
+    const std::string& app_id) const {
   NSString* value = [impl_ targetChannel:base::SysUTF8ToNSString(app_id)];
-  if (value) {
-    *channel = base::SysNSStringToUTF8(value);
-    return true;
-  }
-
-  return false;
+  return value ? absl::optional<std::string>(base::SysNSStringToUTF8(value))
+               : absl::nullopt;
 }
 
-bool ManagedPreferencePolicyManager::GetForceInstallApps(
-    std::vector<std::string>* /* force_install_apps */) const {
-  return false;
+absl::optional<std::vector<std::string>>
+ManagedPreferencePolicyManager::GetForceInstallApps() const {
+  return absl::nullopt;
 }
 
 NSDictionary* ReadManagedPreferencePolicyDictionary() {
diff --git a/chrome/updater/policy/manager.cc b/chrome/updater/policy/manager.cc
index 4a55e88..f3fc379 100644
--- a/chrome/updater/policy/manager.cc
+++ b/chrome/updater/policy/manager.cc
@@ -9,6 +9,7 @@
 
 #include "base/time/time.h"
 #include "chrome/updater/constants.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace updater {
 
@@ -62,30 +63,26 @@
 
   bool HasActiveDevicePolicies() const override;
 
-  bool GetLastCheckPeriodMinutes(int* minutes) const override;
-  bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const override;
-  bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const override;
-  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override;
-  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override;
-
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        int* install_policy) const override;
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       int* update_policy) const override;
-  bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const override;
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        bool* rollback_allowed) const override;
-  bool GetProxyMode(std::string* proxy_mode) const override;
-  bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
-  bool GetProxyServer(std::string* proxy_server) const override;
-  bool GetTargetChannel(const std::string& app_id,
-                        std::string* channel) const override;
-  bool GetForceInstallApps(
-      std::vector<std::string>* force_install_apps) const override;
+  absl::optional<int> GetLastCheckPeriodMinutes() const override;
+  absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const override;
+  absl::optional<std::string> GetDownloadPreferenceGroupPolicy() const override;
+  absl::optional<int> GetPackageCacheSizeLimitMBytes() const override;
+  absl::optional<int> GetPackageCacheExpirationTimeDays() const override;
+  absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const override;
+  absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const override;
+  absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetProxyMode() const override;
+  absl::optional<std::string> GetProxyPacUrl() const override;
+  absl::optional<std::string> GetProxyServer() const override;
+  absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const override;
+  absl::optional<std::vector<std::string>> GetForceInstallApps() const override;
 };
 
 DefaultValuesPolicyManager::DefaultValuesPolicyManager() = default;
@@ -100,80 +97,73 @@
   return std::string("default");
 }
 
-bool DefaultValuesPolicyManager::GetLastCheckPeriodMinutes(int* minutes) const {
-  *minutes = kDefaultLastCheckPeriod.InMinutes();
-  return true;
+absl::optional<int> DefaultValuesPolicyManager::GetLastCheckPeriodMinutes()
+    const {
+  return kDefaultLastCheckPeriod.InMinutes();
 }
 
-bool DefaultValuesPolicyManager::GetUpdatesSuppressedTimes(
-    UpdatesSuppressedTimes* suppressed_times) const {
+absl::optional<UpdatesSuppressedTimes>
+DefaultValuesPolicyManager::GetUpdatesSuppressedTimes() const {
+  return absl::nullopt;
+}
+
+absl::optional<std::string>
+DefaultValuesPolicyManager::GetDownloadPreferenceGroupPolicy() const {
+  return absl::nullopt;
+}
+
+absl::optional<int> DefaultValuesPolicyManager::GetPackageCacheSizeLimitMBytes()
+    const {
+  return absl::nullopt;
+}
+
+absl::optional<int>
+DefaultValuesPolicyManager::GetPackageCacheExpirationTimeDays() const {
+  return absl::nullopt;
+}
+
+absl::optional<int>
+DefaultValuesPolicyManager::GetEffectivePolicyForAppInstalls(
+    const std::string& app_id) const {
+  return kInstallPolicyDefault;
+}
+
+absl::optional<int> DefaultValuesPolicyManager::GetEffectivePolicyForAppUpdates(
+    const std::string& app_id) const {
+  return kUpdatePolicyDefault;
+}
+
+absl::optional<std::string> DefaultValuesPolicyManager::GetTargetVersionPrefix(
+    const std::string& app_id) const {
+  return absl::nullopt;
+}
+
+absl::optional<bool>
+DefaultValuesPolicyManager::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id) const {
   return false;
 }
 
-bool DefaultValuesPolicyManager::GetDownloadPreferenceGroupPolicy(
-    std::string* download_preference) const {
-  return false;
+absl::optional<std::string> DefaultValuesPolicyManager::GetProxyMode() const {
+  return absl::nullopt;
 }
 
-bool DefaultValuesPolicyManager::GetPackageCacheSizeLimitMBytes(
-    int* cache_size_limit) const {
-  return false;
+absl::optional<std::string> DefaultValuesPolicyManager::GetProxyPacUrl() const {
+  return absl::nullopt;
 }
 
-bool DefaultValuesPolicyManager::GetPackageCacheExpirationTimeDays(
-    int* cache_life_limit) const {
-  return false;
+absl::optional<std::string> DefaultValuesPolicyManager::GetProxyServer() const {
+  return absl::nullopt;
 }
 
-bool DefaultValuesPolicyManager::GetEffectivePolicyForAppInstalls(
-    const std::string& app_id,
-    int* install_policy) const {
-  *install_policy = kInstallPolicyDefault;
-  return true;
+absl::optional<std::string> DefaultValuesPolicyManager::GetTargetChannel(
+    const std::string& app_id) const {
+  return absl::nullopt;
 }
 
-bool DefaultValuesPolicyManager::GetEffectivePolicyForAppUpdates(
-    const std::string& app_id,
-    int* update_policy) const {
-  *update_policy = kUpdatePolicyDefault;
-  return true;
-}
-
-bool DefaultValuesPolicyManager::GetTargetVersionPrefix(
-    const std::string& app_id,
-    std::string* target_version_prefix) const {
-  return false;
-}
-
-bool DefaultValuesPolicyManager::IsRollbackToTargetVersionAllowed(
-    const std::string& app_id,
-    bool* rollback_allowed) const {
-  *rollback_allowed = false;
-  return true;
-}
-
-bool DefaultValuesPolicyManager::GetProxyMode(std::string* proxy_mode) const {
-  return false;
-}
-
-bool DefaultValuesPolicyManager::GetProxyPacUrl(
-    std::string* proxy_pac_url) const {
-  return false;
-}
-
-bool DefaultValuesPolicyManager::GetProxyServer(
-    std::string* proxy_server) const {
-  return false;
-}
-
-bool DefaultValuesPolicyManager::GetTargetChannel(const std::string& app_id,
-                                                  std::string* channel) const {
-  return false;
-}
-
-bool DefaultValuesPolicyManager::GetForceInstallApps(
-    std::vector<std::string>* /* force_install_apps */) const {
-  return false;
+absl::optional<std::vector<std::string>>
+DefaultValuesPolicyManager::GetForceInstallApps() const {
+  return absl::nullopt;
 }
 
 std::unique_ptr<PolicyManagerInterface> GetDefaultValuesPolicyManager() {
diff --git a/chrome/updater/policy/manager.h b/chrome/updater/policy/manager.h
index 463f15ef..59fa1c8 100644
--- a/chrome/updater/policy/manager.h
+++ b/chrome/updater/policy/manager.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "chrome/updater/constants.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace updater {
 
@@ -56,65 +57,62 @@
   // Returns the policy for how often the Updater should check for updates.
   // Returns the time interval between update checks in minutes.
   // 0 indicates updates are disabled.
-  virtual bool GetLastCheckPeriodMinutes(int* minutes) const = 0;
+  virtual absl::optional<int> GetLastCheckPeriodMinutes() const = 0;
 
   // For domain-joined machines, checks the current time against the times that
   // updates are suppressed.
-  virtual bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const = 0;
+  virtual absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const = 0;
 
   // Returns the policy for the download preference.
-  virtual bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const = 0;
+  virtual absl::optional<std::string> GetDownloadPreferenceGroupPolicy()
+      const = 0;
 
   // Returns the policy for the package cache size limit in megabytes.
-  virtual bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const = 0;
+  virtual absl::optional<int> GetPackageCacheSizeLimitMBytes() const = 0;
 
   // Returns the policy for the package cache expiration in days.
-  virtual bool GetPackageCacheExpirationTimeDays(
-      int* cache_life_limit) const = 0;
+  virtual absl::optional<int> GetPackageCacheExpirationTimeDays() const = 0;
 
   // Returns kPolicyEnabled if installation of the specified app is allowed.
   // Otherwise, returns kPolicyDisabled.
-  virtual bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                                int* install_policy) const = 0;
+  virtual absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const = 0;
   // Returns kPolicyEnabled if updates of the specified app is allowed.
   // Otherwise, returns one of kPolicyDisabled, kPolicyManualUpdatesOnly, or
   // kPolicyAutomaticUpdatesOnly.
-  virtual bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                               int* update_policy) const = 0;
+  virtual absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const = 0;
   // Returns the target version prefix for the app.
   // Examples:
   // * "" (or not configured): update to latest version available.
   // * "55.": update to any minor version of 55 (e.g. 55.24.34 or 55.60.2).
   // * "55.2.": update to any minor version of 55.2 (e.g. 55.2.34 or 55.2.2).
   // * "55.24.34": update to this specific version only.
-  virtual bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const = 0;
+  virtual absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const = 0;
   // Returns whether the RollbackToTargetVersion policy has been set for the
   // app. If RollbackToTargetVersion is set, the TargetVersionPrefix policy
   // governs the version to rollback clients with higher versions to.
-  virtual bool IsRollbackToTargetVersionAllowed(
-      const std::string& app_id,
-      bool* rollback_allowed) const = 0;
+  virtual absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const = 0;
   // Returns a proxy mode such as |auto_detect|.
-  virtual bool GetProxyMode(std::string* proxy_mode) const = 0;
+  virtual absl::optional<std::string> GetProxyMode() const = 0;
 
   // Returns a proxy PAC URL.
-  virtual bool GetProxyPacUrl(std::string* proxy_pac_url) const = 0;
+  virtual absl::optional<std::string> GetProxyPacUrl() const = 0;
 
   // Returns a proxy server.
-  virtual bool GetProxyServer(std::string* proxy_server) const = 0;
+  virtual absl::optional<std::string> GetProxyServer() const = 0;
 
   // Returns a channel, for example {stable|beta|dev}.
-  virtual bool GetTargetChannel(const std::string& app_id,
-                                std::string* channel) const = 0;
+  virtual absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const = 0;
 
   // Returns a list of apps that need to be downloaded and installed by the
   // updater.
-  virtual bool GetForceInstallApps(
-      std::vector<std::string>* force_install_apps) const = 0;
+  virtual absl::optional<std::vector<std::string>> GetForceInstallApps()
+      const = 0;
 };
 
 std::unique_ptr<PolicyManagerInterface> GetDefaultValuesPolicyManager();
diff --git a/chrome/updater/policy/policy_manager.cc b/chrome/updater/policy/policy_manager.cc
index 163dfc3..73c38b4e 100644
--- a/chrome/updater/policy/policy_manager.cc
+++ b/chrome/updater/policy/policy_manager.cc
@@ -88,121 +88,102 @@
   return std::string("DictValuePolicy");
 }
 
-bool PolicyManager::GetLastCheckPeriodMinutes(int* minutes) const {
-  return GetIntPolicy(kAutoUpdateCheckPeriodOverrideMinutes, minutes);
+absl::optional<int> PolicyManager::GetLastCheckPeriodMinutes() const {
+  return policies_.FindInt(kAutoUpdateCheckPeriodOverrideMinutes);
 }
 
-bool PolicyManager::GetUpdatesSuppressedTimes(
-    UpdatesSuppressedTimes* suppressed_times) const {
-  return GetIntPolicy(kUpdatesSuppressedStartHour,
-                      &suppressed_times->start_hour_) &&
-         GetIntPolicy(kUpdatesSuppressedStartMin,
-                      &suppressed_times->start_minute_) &&
-         GetIntPolicy(kUpdatesSuppressedDurationMin,
-                      &suppressed_times->duration_minute_);
+absl::optional<UpdatesSuppressedTimes>
+PolicyManager::GetUpdatesSuppressedTimes() const {
+  absl::optional<int> start_hour =
+      policies_.FindInt(kUpdatesSuppressedStartHour);
+  absl::optional<int> start_min = policies_.FindInt(kUpdatesSuppressedStartMin);
+  absl::optional<int> duration_min =
+      policies_.FindInt(kUpdatesSuppressedDurationMin);
+
+  if (!start_hour || !start_min || !duration_min)
+    return absl::nullopt;
+
+  UpdatesSuppressedTimes supressed_times;
+  supressed_times.start_hour_ = start_hour.value();
+  supressed_times.start_minute_ = start_min.value();
+  supressed_times.duration_minute_ = duration_min.value();
+  return supressed_times;
 }
 
-bool PolicyManager::GetDownloadPreferenceGroupPolicy(
-    std::string* download_preference) const {
-  return GetStringPolicy(kDownloadPreference, download_preference);
+absl::optional<std::string> PolicyManager::GetDownloadPreferenceGroupPolicy()
+    const {
+  return GetStringPolicy(kDownloadPreference);
 }
 
-bool PolicyManager::GetPackageCacheSizeLimitMBytes(
-    int* cache_size_limit) const {
-  return GetIntPolicy(kCacheSizeLimitMBytes, cache_size_limit);
+absl::optional<int> PolicyManager::GetPackageCacheSizeLimitMBytes() const {
+  return policies_.FindInt(kCacheSizeLimitMBytes);
 }
 
-bool PolicyManager::GetPackageCacheExpirationTimeDays(
-    int* cache_life_limit) const {
-  return GetIntPolicy(kCacheLifeLimitDays, cache_life_limit);
+absl::optional<int> PolicyManager::GetPackageCacheExpirationTimeDays() const {
+  return policies_.FindInt(kCacheLifeLimitDays);
 }
 
-bool PolicyManager::GetEffectivePolicyForAppInstalls(
-    const std::string& app_id,
-    int* install_policy) const {
+absl::optional<int> PolicyManager::GetEffectivePolicyForAppInstalls(
+    const std::string& app_id) const {
   std::string app_value_name(kInstallAppPrefix);
   app_value_name.append(app_id);
-  return GetIntPolicy(app_value_name.c_str(), install_policy)
-             ? true
-             : GetIntPolicy(kInstallAppsDefault, install_policy);
+  absl::optional<int> policy = policies_.FindInt(app_value_name.c_str());
+  return policy ? policy : policies_.FindInt(kInstallAppsDefault);
 }
 
-bool PolicyManager::GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                                    int* update_policy) const {
+absl::optional<int> PolicyManager::GetEffectivePolicyForAppUpdates(
+    const std::string& app_id) const {
   std::string app_value_name(kUpdateAppPrefix);
   app_value_name.append(app_id);
-  return GetIntPolicy(app_value_name.c_str(), update_policy)
-             ? true
-             : GetIntPolicy(kUpdateAppsDefault, update_policy);
+  absl::optional<int> policy = policies_.FindInt(app_value_name.c_str());
+  return policy ? policy : policies_.FindInt(kUpdateAppsDefault);
 }
 
-bool PolicyManager::GetTargetChannel(const std::string& app_id,
-                                     std::string* channel) const {
+absl::optional<std::string> PolicyManager::GetTargetChannel(
+    const std::string& app_id) const {
   std::string app_value_name(kTargetChannel);
   app_value_name.append(app_id);
-  return GetStringPolicy(app_value_name.c_str(), channel);
+  return GetStringPolicy(app_value_name.c_str());
 }
 
-bool PolicyManager::GetTargetVersionPrefix(
-    const std::string& app_id,
-    std::string* target_version_prefix) const {
+absl::optional<std::string> PolicyManager::GetTargetVersionPrefix(
+    const std::string& app_id) const {
   std::string app_value_name(kTargetVersionPrefix);
   app_value_name.append(app_id);
-  return GetStringPolicy(app_value_name.c_str(), target_version_prefix);
+  return GetStringPolicy(app_value_name.c_str());
 }
 
-bool PolicyManager::IsRollbackToTargetVersionAllowed(
-    const std::string& app_id,
-    bool* rollback_allowed) const {
+absl::optional<bool> PolicyManager::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id) const {
   std::string app_value_name(kRollbackToTargetVersion);
   app_value_name.append(app_id);
-  int is_rollback_allowed = 0;
-  if (GetIntPolicy(app_value_name.c_str(), &is_rollback_allowed)) {
-    *rollback_allowed = is_rollback_allowed;
-    return true;
-  }
-
-  return false;
+  absl::optional<int> policy = policies_.FindInt(app_value_name);
+  return policy ? absl::optional<bool>(policy.value()) : absl::nullopt;
 }
 
-bool PolicyManager::GetProxyMode(std::string* proxy_mode) const {
-  return GetStringPolicy(kProxyMode, proxy_mode);
+absl::optional<std::string> PolicyManager::GetProxyMode() const {
+  return GetStringPolicy(kProxyMode);
 }
 
-bool PolicyManager::GetProxyPacUrl(std::string* proxy_pac_url) const {
-  return GetStringPolicy(kProxyPacUrl, proxy_pac_url);
+absl::optional<std::string> PolicyManager::GetProxyPacUrl() const {
+  return GetStringPolicy(kProxyPacUrl);
 }
 
-bool PolicyManager::GetProxyServer(std::string* proxy_server) const {
-  return GetStringPolicy(kProxyServer, proxy_server);
+absl::optional<std::string> PolicyManager::GetProxyServer() const {
+  return GetStringPolicy(kProxyServer);
 }
 
-bool PolicyManager::GetForceInstallApps(
-    std::vector<std::string>* force_install_apps) const {
-  if (force_install_apps_.empty())
-    return false;
-
-  *force_install_apps = force_install_apps_;
-  return true;
+absl::optional<std::vector<std::string>> PolicyManager::GetForceInstallApps()
+    const {
+  return force_install_apps_.empty()
+             ? absl::optional<std::vector<std::string>>()
+             : force_install_apps_;
 }
 
-bool PolicyManager::GetIntPolicy(const std::string& key, int* value) const {
-  absl::optional<int> policy = policies_.FindInt(key);
-  if (!policy.has_value())
-    return false;
-
-  *value = *policy;
-  return true;
-}
-
-bool PolicyManager::GetStringPolicy(const std::string& key,
-                                    std::string* value) const {
+absl::optional<std::string> PolicyManager::GetStringPolicy(
+    const std::string& key) const {
   const std::string* policy = policies_.FindString(key);
-  if (policy == nullptr)
-    return false;
-
-  *value = *policy;
-  return true;
+  return policy ? absl::optional<std::string>(*policy) : absl::nullopt;
 }
 
 }  // namespace updater
diff --git a/chrome/updater/policy/policy_manager.h b/chrome/updater/policy/policy_manager.h
index d0f31ea..b759d375 100644
--- a/chrome/updater/policy/policy_manager.h
+++ b/chrome/updater/policy/policy_manager.h
@@ -30,35 +30,29 @@
 
   bool HasActiveDevicePolicies() const override;
 
-  bool GetLastCheckPeriodMinutes(int* minutes) const override;
-  bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const override;
-  bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const override;
-  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override;
-  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override;
-
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        int* install_policy) const override;
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       int* update_policy) const override;
-  bool GetTargetChannel(const std::string& app_id,
-                        std::string* channel) const override;
-  bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const override;
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        bool* rollback_allowed) const override;
-  bool GetProxyMode(std::string* proxy_mode) const override;
-  bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
-  bool GetProxyServer(std::string* proxy_server) const override;
-
-  bool GetForceInstallApps(
-      std::vector<std::string>* force_install_apps) const override;
+  absl::optional<int> GetLastCheckPeriodMinutes() const override;
+  absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const override;
+  absl::optional<std::string> GetDownloadPreferenceGroupPolicy() const override;
+  absl::optional<int> GetPackageCacheSizeLimitMBytes() const override;
+  absl::optional<int> GetPackageCacheExpirationTimeDays() const override;
+  absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const override;
+  absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const override;
+  absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const override;
+  absl::optional<std::string> GetProxyMode() const override;
+  absl::optional<std::string> GetProxyPacUrl() const override;
+  absl::optional<std::string> GetProxyServer() const override;
+  absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const override;
+  absl::optional<std::vector<std::string>> GetForceInstallApps() const override;
 
  private:
-  bool GetIntPolicy(const std::string& key, int* value) const;
-  bool GetStringPolicy(const std::string& key, std::string* value) const;
+  absl::optional<std::string> GetStringPolicy(const std::string& key) const;
 
   const base::Value::Dict policies_;
   std::vector<std::string> force_install_apps_;
diff --git a/chrome/updater/policy/policy_manager_unittest.cc b/chrome/updater/policy/policy_manager_unittest.cc
index ab948b0..2c61be2 100644
--- a/chrome/updater/policy/policy_manager_unittest.cc
+++ b/chrome/updater/policy/policy_manager_unittest.cc
@@ -27,61 +27,32 @@
 
   EXPECT_EQ(policy_manager->source(), "DictValuePolicy");
 
-  int check_period = 0;
-  EXPECT_FALSE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
-  EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  int cache_size_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  int cache_life_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-
-  int install_policy = -1;
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      kTestAppID, &install_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-
-  int update_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID,
-                                                               &update_policy));
+      "non-exist-app-fallback-to-global"));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-
-  std::string target_channel;
-  EXPECT_FALSE(policy_manager->GetTargetChannel(kTestAppID, &target_channel));
+      "non-exist-app-fallback-to-global"));
+  EXPECT_EQ(policy_manager->GetTargetChannel(kTestAppID), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(kTestAppID), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
-
-  std::string target_version_prefix;
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix(kTestAppID,
-                                                      &target_version_prefix));
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
-
-  bool is_rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      kTestAppID, &is_rollback_allowed));
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
-
-  std::vector<std::string> force_install_apps;
-  EXPECT_FALSE(policy_manager->GetForceInstallApps(&force_install_apps));
+      policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"));
+  EXPECT_EQ(policy_manager->GetForceInstallApps(), absl::nullopt);
 }
 
 TEST_F(PolicyManagerTests, PolicyRead) {
@@ -114,83 +85,52 @@
 
   EXPECT_TRUE(policy_manager->HasActiveDevicePolicies());
 
-  int check_period = 0;
-  EXPECT_TRUE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-  EXPECT_EQ(check_period, 480);
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), 480);
 
-  UpdatesSuppressedTimes suppressed_times = {};
-  EXPECT_TRUE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-  EXPECT_EQ(suppressed_times.start_hour_, 2);
-  EXPECT_EQ(suppressed_times.start_minute_, 30);
-  EXPECT_EQ(suppressed_times.duration_minute_, 500);
+  absl::optional<UpdatesSuppressedTimes> suppressed_times =
+      policy_manager->GetUpdatesSuppressedTimes();
+  ASSERT_TRUE(suppressed_times);
+  EXPECT_EQ(suppressed_times->start_hour_, 2);
+  EXPECT_EQ(suppressed_times->start_minute_, 30);
+  EXPECT_EQ(suppressed_times->duration_minute_, 500);
 
-  std::string download_preference;
-  EXPECT_TRUE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-  EXPECT_EQ(download_preference, "cacheable");
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), "cacheable");
 
-  int cache_size_limit = 0;
-  EXPECT_TRUE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  EXPECT_EQ(cache_size_limit, 100);
-  int cache_life_limit = 0;
-  EXPECT_TRUE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-  EXPECT_EQ(cache_life_limit, 45);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), 100);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), 45);
 
-  std::string proxy_mode;
-  EXPECT_TRUE(policy_manager->GetProxyMode(&proxy_mode));
-  EXPECT_EQ(proxy_mode, "fixed_servers");
-  std::string proxy_server;
-  EXPECT_TRUE(policy_manager->GetProxyServer(&proxy_server));
-  EXPECT_EQ(proxy_server, "http://foo.bar");
-  std::string proxy_pac_url;
-  EXPECT_TRUE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-  EXPECT_EQ(proxy_pac_url, "go/pac.url");
+  EXPECT_EQ(policy_manager->GetProxyMode(), "fixed_servers");
+  EXPECT_EQ(policy_manager->GetProxyServer(), "http://foo.bar");
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), "go/pac.url");
 
-  int install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      kTestAppID, &install_policy));
-  EXPECT_EQ(install_policy, 3);
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-  EXPECT_EQ(install_policy, 2);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(kTestAppID), 3);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(
+                "non-exist-app-fallback-to-global"),
+            2);
 
-  int update_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID,
-                                                              &update_policy));
-  EXPECT_EQ(update_policy, 2);
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-  EXPECT_EQ(update_policy, 1);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID), 2);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(
+                "non-exist-app-fallback-to-global"),
+            1);
 
-  std::string target_channel;
-  EXPECT_TRUE(policy_manager->GetTargetChannel(kTestAppID, &target_channel));
-  EXPECT_EQ(target_channel, "beta");
+  EXPECT_EQ(policy_manager->GetTargetChannel(kTestAppID), "beta");
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(kTestAppID), "55.55.");
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(kTestAppID), true);
   EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
+      policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"));
 
-  std::string target_version_prefix;
-  EXPECT_TRUE(policy_manager->GetTargetVersionPrefix(kTestAppID,
-                                                     &target_version_prefix));
-  EXPECT_EQ(target_version_prefix, "55.55.");
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
+  absl::optional<std::vector<std::string>> force_install_apps =
+      policy_manager->GetForceInstallApps();
+  ASSERT_EQ(force_install_apps.has_value(), !IsSystemInstall());
 
-  bool is_rollback_allowed = false;
-  EXPECT_TRUE(policy_manager->IsRollbackToTargetVersionAllowed(
-      kTestAppID, &is_rollback_allowed));
-  EXPECT_TRUE(is_rollback_allowed);
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
-
-  std::vector<std::string> force_install_apps;
-
-  EXPECT_EQ(policy_manager->GetForceInstallApps(&force_install_apps),
-            !IsSystemInstall());
   if (!IsSystemInstall()) {
-    ASSERT_EQ(force_install_apps.size(), 1U);
-    EXPECT_EQ(force_install_apps[0], kTestAppIDForceInstall);
+    ASSERT_EQ(force_install_apps->size(), 1U);
+    EXPECT_EQ(force_install_apps->at(0), kTestAppIDForceInstall);
   }
 }
 
@@ -223,58 +163,31 @@
 
   EXPECT_TRUE(policy_manager->HasActiveDevicePolicies());
 
-  int check_period = 0;
-  EXPECT_FALSE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-
-  UpdatesSuppressedTimes suppressed_times = {};
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
-  EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  int cache_size_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  int cache_life_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-
-  int install_policy = -1;
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      kTestAppID, &install_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-
-  int update_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID,
-                                                               &update_policy));
+      "non-exist-app-fallback-to-global"));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-
-  std::string target_channel;
-  EXPECT_FALSE(policy_manager->GetTargetChannel(kTestAppID, &target_channel));
+      "non-exist-app-fallback-to-global"));
+  EXPECT_EQ(policy_manager->GetTargetChannel(kTestAppID), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(kTestAppID), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(kTestAppID),
+            absl::nullopt);
   EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
-
-  std::string target_version_prefix;
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix(kTestAppID,
-                                                      &target_version_prefix));
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
-
-  bool is_rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      kTestAppID, &is_rollback_allowed));
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
+      policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"));
 }
 
 }  // namespace updater
diff --git a/chrome/updater/policy/service.cc b/chrome/updater/policy/service.cc
index cf4122c..ce7a1c8a 100644
--- a/chrome/updater/policy/service.cc
+++ b/chrome/updater/policy/service.cc
@@ -129,189 +129,143 @@
   return base::JoinString(sources, ";");
 }
 
-bool PolicyService::GetLastCheckPeriodMinutes(PolicyStatus<int>* policy_status,
-                                              int* minutes) const {
+PolicyStatus<int> PolicyService::GetLastCheckPeriodMinutes() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetLastCheckPeriodMinutes),
-      policy_status, minutes);
+      base::BindRepeating(&PolicyManagerInterface::GetLastCheckPeriodMinutes));
 }
 
-bool PolicyService::GetUpdatesSuppressedTimes(
-    PolicyStatus<UpdatesSuppressedTimes>* policy_status,
-    UpdatesSuppressedTimes* suppressed_times) const {
+PolicyStatus<UpdatesSuppressedTimes> PolicyService::GetUpdatesSuppressedTimes()
+    const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetUpdatesSuppressedTimes),
-      policy_status, suppressed_times);
+      base::BindRepeating(&PolicyManagerInterface::GetUpdatesSuppressedTimes));
 }
 
-bool PolicyService::GetDownloadPreferenceGroupPolicy(
-    PolicyStatus<std::string>* policy_status,
-    std::string* download_preference) const {
+PolicyStatus<std::string> PolicyService::GetDownloadPreferenceGroupPolicy()
+    const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return QueryPolicy(
-      base::BindRepeating(
-          &PolicyManagerInterface::GetDownloadPreferenceGroupPolicy),
-      policy_status, download_preference);
+  return QueryPolicy(base::BindRepeating(
+      &PolicyManagerInterface::GetDownloadPreferenceGroupPolicy));
 }
 
-bool PolicyService::GetPackageCacheSizeLimitMBytes(
-    PolicyStatus<int>* policy_status,
-    int* cache_size_limit) const {
+PolicyStatus<int> PolicyService::GetPackageCacheSizeLimitMBytes() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return QueryPolicy(
-      base::BindRepeating(
-          &PolicyManagerInterface::GetPackageCacheSizeLimitMBytes),
-      policy_status, cache_size_limit);
+  return QueryPolicy(base::BindRepeating(
+      &PolicyManagerInterface::GetPackageCacheSizeLimitMBytes));
 }
 
-bool PolicyService::GetPackageCacheExpirationTimeDays(
-    PolicyStatus<int>* policy_status,
-    int* cache_life_limit) const {
+PolicyStatus<int> PolicyService::GetPackageCacheExpirationTimeDays() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return QueryPolicy(
-      base::BindRepeating(
-          &PolicyManagerInterface::GetPackageCacheExpirationTimeDays),
-      policy_status, cache_life_limit);
+  return QueryPolicy(base::BindRepeating(
+      &PolicyManagerInterface::GetPackageCacheExpirationTimeDays));
 }
 
-bool PolicyService::GetEffectivePolicyForAppInstalls(
-    const std::string& app_id,
-    PolicyStatus<int>* policy_status,
-    int* install_policy) const {
+PolicyStatus<int> PolicyService::GetPolicyForAppInstalls(
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryAppPolicy(
       base::BindRepeating(
           &PolicyManagerInterface::GetEffectivePolicyForAppInstalls),
-      app_id, policy_status, install_policy);
+      app_id);
 }
 
-bool PolicyService::GetEffectivePolicyForAppUpdates(
-    const std::string& app_id,
-    PolicyStatus<int>* policy_status,
-    int* update_policy) const {
+PolicyStatus<int> PolicyService::GetPolicyForAppUpdates(
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryAppPolicy(
       base::BindRepeating(
           &PolicyManagerInterface::GetEffectivePolicyForAppUpdates),
-      app_id, policy_status, update_policy);
+      app_id);
 }
 
-bool PolicyService::GetTargetChannel(const std::string& app_id,
-                                     PolicyStatus<std::string>* policy_status,
-                                     std::string* channel) const {
+PolicyStatus<std::string> PolicyService::GetTargetChannel(
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryAppPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetTargetChannel), app_id,
-      policy_status, channel);
+      base::BindRepeating(&PolicyManagerInterface::GetTargetChannel), app_id);
 }
 
-bool PolicyService::GetTargetVersionPrefix(
-    const std::string& app_id,
-    PolicyStatus<std::string>* policy_status,
-    std::string* target_version_prefix) const {
+PolicyStatus<std::string> PolicyService::GetTargetVersionPrefix(
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryAppPolicy(
       base::BindRepeating(&PolicyManagerInterface::GetTargetVersionPrefix),
-      app_id, policy_status, target_version_prefix);
+      app_id);
 }
 
-bool PolicyService::IsRollbackToTargetVersionAllowed(
-    const std::string& app_id,
-    PolicyStatus<bool>* policy_status,
-    bool* rollback_allowed) const {
+PolicyStatus<bool> PolicyService::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryAppPolicy(
       base::BindRepeating(
           &PolicyManagerInterface::IsRollbackToTargetVersionAllowed),
-      app_id, policy_status, rollback_allowed);
+      app_id);
 }
 
-bool PolicyService::GetProxyMode(PolicyStatus<std::string>* policy_status,
-                                 std::string* proxy_mode) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return QueryPolicy(base::BindRepeating(&PolicyManagerInterface::GetProxyMode),
-                     policy_status, proxy_mode);
-}
-
-bool PolicyService::GetProxyPacUrl(PolicyStatus<std::string>* policy_status,
-                                   std::string* proxy_pac_url) const {
+PolicyStatus<std::string> PolicyService::GetProxyMode() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetProxyPacUrl),
-      policy_status, proxy_pac_url);
+      base::BindRepeating(&PolicyManagerInterface::GetProxyMode));
 }
 
-bool PolicyService::GetProxyServer(PolicyStatus<std::string>* policy_status,
-                                   std::string* proxy_server) const {
+PolicyStatus<std::string> PolicyService::GetProxyPacUrl() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetProxyServer),
-      policy_status, proxy_server);
+      base::BindRepeating(&PolicyManagerInterface::GetProxyPacUrl));
 }
 
-bool PolicyService::GetForceInstallApps(
-    PolicyStatus<std::vector<std::string>>* policy_status,
-    std::vector<std::string>* force_install_apps) const {
+PolicyStatus<std::string> PolicyService::GetProxyServer() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return QueryPolicy(
-      base::BindRepeating(&PolicyManagerInterface::GetForceInstallApps),
-      policy_status, force_install_apps);
+      base::BindRepeating(&PolicyManagerInterface::GetProxyServer));
+}
+
+PolicyStatus<std::vector<std::string>> PolicyService::GetForceInstallApps()
+    const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return QueryPolicy(
+      base::BindRepeating(&PolicyManagerInterface::GetForceInstallApps));
 }
 
 template <typename T>
-bool PolicyService::QueryPolicy(
-    const base::RepeatingCallback<bool(const PolicyManagerInterface*, T*)>&
+PolicyStatus<T> PolicyService::QueryPolicy(
+    const base::RepeatingCallback<absl::optional<T>(
+        const PolicyManagerInterface*)>& policy_query_callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  absl::optional<T> query_result;
+  PolicyStatus<T> status;
+  for (const std::unique_ptr<PolicyManagerInterface>& policy_manager :
+       policy_managers_) {
+    query_result = policy_query_callback.Run(policy_manager.get());
+    if (!query_result)
+      continue;
+    status.AddPolicyIfNeeded(policy_manager->HasActiveDevicePolicies(),
+                             policy_manager->source(), query_result.value());
+  }
+
+  return status;
+}
+
+template <typename T>
+PolicyStatus<T> PolicyService::QueryAppPolicy(
+    const base::RepeatingCallback<
+        absl::optional<T>(const PolicyManagerInterface*, const std::string&)>&
         policy_query_callback,
-    PolicyStatus<T>* policy_status,
-    T* result) const {
+    const std::string& app_id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  T value{};
+  absl::optional<T> query_result;
   PolicyStatus<T> status;
   for (const std::unique_ptr<PolicyManagerInterface>& policy_manager :
        policy_managers_) {
-    if (!policy_query_callback.Run(policy_manager.get(), &value))
+    query_result = policy_query_callback.Run(policy_manager.get(), app_id);
+    if (!query_result)
       continue;
     status.AddPolicyIfNeeded(policy_manager->HasActiveDevicePolicies(),
-                             policy_manager->source(), value);
+                             policy_manager->source(), query_result.value());
   }
-  if (!status.effective_policy())
-    return false;
 
-  if (result)
-    *result = status.effective_policy().value().policy;
-  if (policy_status)
-    *policy_status = status;
-  return true;
-}
-
-template <typename T>
-bool PolicyService::QueryAppPolicy(
-    const base::RepeatingCallback<bool(const PolicyManagerInterface*,
-                                       const std::string&,
-                                       T*)>& policy_query_callback,
-    const std::string& app_id,
-    PolicyStatus<T>* policy_status,
-    T* result) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  T value{};
-  PolicyStatus<T> status;
-  for (const std::unique_ptr<PolicyManagerInterface>& policy_manager :
-       policy_managers_) {
-    if (!policy_query_callback.Run(policy_manager.get(), app_id, &value))
-      continue;
-    status.AddPolicyIfNeeded(policy_manager->HasActiveDevicePolicies(),
-                             policy_manager->source(), value);
-  }
-  if (!status.effective_policy())
-    return false;
-
-  if (result)
-    *result = status.effective_policy().value().policy;
-  if (policy_status)
-    *policy_status = status;
-  return true;
+  return status;
 }
 
 PolicyServiceProxyConfiguration::PolicyServiceProxyConfiguration() = default;
@@ -324,33 +278,32 @@
 absl::optional<PolicyServiceProxyConfiguration>
 PolicyServiceProxyConfiguration::Get(
     scoped_refptr<PolicyService> policy_service) {
-  std::string policy_proxy_mode;
-  if (!policy_service->GetProxyMode(nullptr, &policy_proxy_mode) ||
-      policy_proxy_mode.compare(kProxyModeSystem) == 0) {
+  PolicyStatus<std::string> proxy_mode = policy_service->GetProxyMode();
+  if (!proxy_mode || proxy_mode.policy().compare(kProxyModeSystem) == 0) {
     return absl::nullopt;
   }
-  VLOG(2) << "Using policy proxy " << policy_proxy_mode;
+  VLOG(2) << "Using policy proxy " << proxy_mode.policy();
 
   PolicyServiceProxyConfiguration policy_service_proxy_configuration;
 
   bool is_policy_config_valid = true;
-  if (policy_proxy_mode.compare(kProxyModeFixedServers) == 0) {
-    std::string policy_proxy_url;
-    if (!policy_service->GetProxyServer(nullptr, &policy_proxy_url)) {
+  if (proxy_mode.policy().compare(kProxyModeFixedServers) == 0) {
+    PolicyStatus<std::string> proxy_url = policy_service->GetProxyServer();
+    if (!proxy_url) {
       VLOG(1) << "Fixed server mode proxy has no URL specified.";
       is_policy_config_valid = false;
     } else {
-      policy_service_proxy_configuration.proxy_url = policy_proxy_url;
+      policy_service_proxy_configuration.proxy_url = proxy_url.policy();
     }
-  } else if (policy_proxy_mode.compare(kProxyModePacScript) == 0) {
-    std::string policy_proxy_pac_url;
-    if (!policy_service->GetProxyPacUrl(nullptr, &policy_proxy_pac_url)) {
+  } else if (proxy_mode.policy().compare(kProxyModePacScript) == 0) {
+    PolicyStatus<std::string> proxy_pac_url;
+    if (!proxy_pac_url) {
       VLOG(1) << "PAC proxy policy has no PAC URL specified.";
       is_policy_config_valid = false;
     } else {
-      policy_service_proxy_configuration.proxy_pac_url = policy_proxy_pac_url;
+      policy_service_proxy_configuration.proxy_pac_url = proxy_pac_url.policy();
     }
-  } else if (policy_proxy_mode.compare(kProxyModeAutoDetect)) {
+  } else if (proxy_mode.policy().compare(kProxyModeAutoDetect)) {
     policy_service_proxy_configuration.proxy_auto_detect = true;
   }
 
diff --git a/chrome/updater/policy/service.h b/chrome/updater/policy/service.h
index e782f61..f9c38bd 100644
--- a/chrome/updater/policy/service.h
+++ b/chrome/updater/policy/service.h
@@ -23,6 +23,7 @@
 
 // This class contains the aggregate status of a policy value. It determines
 // whether a conflict exists when multiple policy providers set the same policy.
+// Instances are logically true if an effective policy is set.
 template <typename T>
 class PolicyStatus {
  public:
@@ -57,6 +58,16 @@
     return conflict_policy_;
   }
 
+  explicit operator bool() const { return effective_policy_.has_value(); }
+  // Convenience method to extract the effective policy's value.
+  const T& policy() {
+    DCHECK(effective_policy_);
+    return effective_policy_->policy;
+  }
+  const T& policy_or(const T& fallback) {
+    return effective_policy_ ? policy() : fallback;
+  }
+
  private:
   absl::optional<Entry> effective_policy_;
   absl::optional<Entry> conflict_policy_;
@@ -84,42 +95,22 @@
 
   std::string source() const;
 
-  bool GetLastCheckPeriodMinutes(PolicyStatus<int>* policy_status,
-                                 int* minutes) const;
-  bool GetUpdatesSuppressedTimes(
-      PolicyStatus<UpdatesSuppressedTimes>* policy_status,
-      UpdatesSuppressedTimes* suppressed_times) const;
-  bool GetDownloadPreferenceGroupPolicy(
-      PolicyStatus<std::string>* policy_status,
-      std::string* download_preference) const;
-  bool GetPackageCacheSizeLimitMBytes(PolicyStatus<int>* policy_status,
-                                      int* cache_size_limit) const;
-  bool GetPackageCacheExpirationTimeDays(PolicyStatus<int>* policy_status,
-                                         int* cache_life_limit) const;
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        PolicyStatus<int>* policy_status,
-                                        int* install_policy) const;
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       PolicyStatus<int>* policy_status,
-                                       int* update_policy) const;
-  bool GetTargetChannel(const std::string& app_id,
-                        PolicyStatus<std::string>* policy_status,
-                        std::string* channel) const;
-  bool GetTargetVersionPrefix(const std::string& app_id,
-                              PolicyStatus<std::string>* policy_status,
-                              std::string* target_version_prefix) const;
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        PolicyStatus<bool>* policy_status,
-                                        bool* rollback_allowed) const;
-  bool GetProxyMode(PolicyStatus<std::string>* policy_status,
-                    std::string* proxy_mode) const;
-  bool GetProxyPacUrl(PolicyStatus<std::string>* policy_status,
-                      std::string* proxy_pac_url) const;
-  bool GetProxyServer(PolicyStatus<std::string>* policy_status,
-                      std::string* proxy_server) const;
-  bool GetForceInstallApps(
-      PolicyStatus<std::vector<std::string>>* policy_status,
-      std::vector<std::string>* force_install_apps) const;
+  PolicyStatus<int> GetLastCheckPeriodMinutes() const;
+  PolicyStatus<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes() const;
+  PolicyStatus<std::string> GetDownloadPreferenceGroupPolicy() const;
+  PolicyStatus<int> GetPackageCacheSizeLimitMBytes() const;
+  PolicyStatus<int> GetPackageCacheExpirationTimeDays() const;
+  PolicyStatus<int> GetPolicyForAppInstalls(const std::string& app_id) const;
+  PolicyStatus<int> GetPolicyForAppUpdates(const std::string& app_id) const;
+  PolicyStatus<std::string> GetTargetChannel(const std::string& app_id) const;
+  PolicyStatus<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const;
+  PolicyStatus<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const;
+  PolicyStatus<std::string> GetProxyMode() const;
+  PolicyStatus<std::string> GetProxyPacUrl() const;
+  PolicyStatus<std::string> GetProxyServer() const;
+  PolicyStatus<std::vector<std::string>> GetForceInstallApps() const;
 
  protected:
   virtual ~PolicyService();
@@ -148,22 +139,18 @@
   // Helper function to query the policy from the managed policy providers and
   // determines the policy status.
   template <typename T>
-  bool QueryPolicy(
-      const base::RepeatingCallback<bool(const PolicyManagerInterface*, T*)>&
-          policy_query_callback,
-      PolicyStatus<T>* policy_status,
-      T* value) const;
+  PolicyStatus<T> QueryPolicy(
+      const base::RepeatingCallback<absl::optional<T>(
+          const PolicyManagerInterface*)>& policy_query_callback) const;
 
   // Helper function to query app policy from the managed policy providers and
   // determines the policy status.
   template <typename T>
-  bool QueryAppPolicy(
-      const base::RepeatingCallback<bool(const PolicyManagerInterface*,
-                                         const std::string& app_id,
-                                         T*)>& policy_query_callback,
-      const std::string& app_id,
-      PolicyStatus<T>* policy_status,
-      T* value) const;
+  PolicyStatus<T> QueryAppPolicy(
+      const base::RepeatingCallback<
+          absl::optional<T>(const PolicyManagerInterface*,
+                            const std::string& app_id)>& policy_query_callback,
+      const std::string& app_id) const;
 };
 
 // Decouples the proxy configuration from `PolicyService`.
diff --git a/chrome/updater/policy/service_unittest.cc b/chrome/updater/policy/service_unittest.cc
index 7772749..4271e66 100644
--- a/chrome/updater/policy/service_unittest.cc
+++ b/chrome/updater/policy/service_unittest.cc
@@ -27,81 +27,80 @@
   bool HasActiveDevicePolicies() const override {
     return has_active_device_policies_;
   }
-  bool GetLastCheckPeriodMinutes(int* minutes) const override { return false; }
-  bool GetUpdatesSuppressedTimes(
-      UpdatesSuppressedTimes* suppressed_times) const override {
+  absl::optional<int> GetLastCheckPeriodMinutes() const override {
+    return absl::nullopt;
+  }
+  absl::optional<UpdatesSuppressedTimes> GetUpdatesSuppressedTimes()
+      const override {
     if (!suppressed_times_.valid())
-      return false;
+      return absl::nullopt;
 
-    *suppressed_times = suppressed_times_;
-    return true;
+    return suppressed_times_;
   }
   void SetUpdatesSuppressedTimes(
       const UpdatesSuppressedTimes& suppressed_times) {
     suppressed_times_ = suppressed_times;
   }
-  bool GetDownloadPreferenceGroupPolicy(
-      std::string* download_preference) const override {
+  absl::optional<std::string> GetDownloadPreferenceGroupPolicy()
+      const override {
     if (download_preference_.empty())
-      return false;
+      return absl::nullopt;
 
-    *download_preference = download_preference_;
-    return true;
+    return download_preference_;
   }
   void SetDownloadPreferenceGroupPolicy(const std::string& preference) {
     download_preference_ = preference;
   }
-  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override {
-    return false;
+  absl::optional<int> GetPackageCacheSizeLimitMBytes() const override {
+    return absl::nullopt;
   }
-  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override {
-    return false;
+  absl::optional<int> GetPackageCacheExpirationTimeDays() const override {
+    return absl::nullopt;
   }
-  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
-                                        int* install_policy) const override {
-    return false;
+  absl::optional<int> GetEffectivePolicyForAppInstalls(
+      const std::string& app_id) const override {
+    return absl::nullopt;
   }
-  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
-                                       int* update_policy) const override {
+  absl::optional<int> GetEffectivePolicyForAppUpdates(
+      const std::string& app_id) const override {
     auto value = update_policies_.find(app_id);
     if (value == update_policies_.end())
-      return false;
-    *update_policy = value->second;
-    return true;
+      return absl::nullopt;
+    return value->second;
   }
   void SetUpdatePolicy(const std::string& app_id, int update_policy) {
     update_policies_[app_id] = update_policy;
   }
-  bool GetTargetVersionPrefix(
-      const std::string& app_id,
-      std::string* target_version_prefix) const override {
-    return false;
+  absl::optional<std::string> GetTargetVersionPrefix(
+      const std::string& app_id) const override {
+    return absl::nullopt;
   }
-  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
-                                        bool* rollback_allowed) const override {
-    return false;
+  absl::optional<bool> IsRollbackToTargetVersionAllowed(
+      const std::string& app_id) const override {
+    return absl::nullopt;
   }
-  bool GetProxyMode(std::string* proxy_mode) const override { return false; }
-  bool GetProxyPacUrl(std::string* proxy_pac_url) const override {
-    return false;
+  absl::optional<std::string> GetProxyMode() const override {
+    return absl::nullopt;
   }
-  bool GetProxyServer(std::string* proxy_server) const override {
-    return false;
+  absl::optional<std::string> GetProxyPacUrl() const override {
+    return absl::nullopt;
   }
-  bool GetTargetChannel(const std::string& app_id,
-                        std::string* channel) const override {
+  absl::optional<std::string> GetProxyServer() const override {
+    return absl::nullopt;
+  }
+  absl::optional<std::string> GetTargetChannel(
+      const std::string& app_id) const override {
     auto value = channels_.find(app_id);
     if (value == channels_.end())
-      return false;
-    *channel = value->second;
-    return true;
+      return absl::nullopt;
+    return value->second;
   }
   void SetChannel(const std::string& app_id, std::string channel) {
     channels_[app_id] = std::move(channel);
   }
-  bool GetForceInstallApps(
-      std::vector<std::string>* /* force_install_apps */) const override {
-    return false;
+  absl::optional<std::vector<std::string>> GetForceInstallApps()
+      const override {
+    return absl::nullopt;
   }
 
  private:
@@ -120,28 +119,28 @@
       base::MakeRefCounted<PolicyService>(std::move(managers));
   EXPECT_EQ(policy_service->source(), "default");
 
-  std::string version_prefix;
-  EXPECT_FALSE(
-      policy_service->GetTargetVersionPrefix("", nullptr, &version_prefix));
+  PolicyStatus<std::string> version_prefix =
+      policy_service->GetTargetVersionPrefix("");
+  EXPECT_FALSE(version_prefix);
 
-  int last_check = 0;
-  EXPECT_TRUE(policy_service->GetLastCheckPeriodMinutes(nullptr, &last_check));
-  EXPECT_EQ(last_check, 270);
+  PolicyStatus<int> last_check = policy_service->GetLastCheckPeriodMinutes();
+  ASSERT_TRUE(last_check);
+  EXPECT_EQ(last_check.policy(), 270);
 
-  int install_policy = 0;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppInstalls(
-      "test1", nullptr, &install_policy));
-  EXPECT_EQ(install_policy, 1);
+  PolicyStatus<int> app_installs =
+      policy_service->GetPolicyForAppInstalls("test1");
+  ASSERT_TRUE(app_installs);
+  EXPECT_EQ(app_installs.policy(), 1);
 
-  int update_policy = 0;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates("test1", nullptr,
-                                                              &update_policy));
-  EXPECT_EQ(update_policy, 1);
+  PolicyStatus<int> app_updates =
+      policy_service->GetPolicyForAppUpdates("test1");
+  ASSERT_TRUE(app_updates);
+  EXPECT_EQ(app_updates.policy(), 1);
 
-  bool rollback_allowed = true;
-  EXPECT_TRUE(policy_service->IsRollbackToTargetVersionAllowed(
-      "test1", nullptr, &rollback_allowed));
-  EXPECT_EQ(rollback_allowed, false);
+  PolicyStatus<bool> rollback_allowed =
+      policy_service->IsRollbackToTargetVersionAllowed("test1");
+  ASSERT_TRUE(rollback_allowed);
+  EXPECT_EQ(rollback_allowed.policy(), false);
 }
 
 TEST(PolicyService, SinglePolicyManager) {
@@ -154,36 +153,27 @@
       base::MakeRefCounted<PolicyService>(std::move(managers));
   EXPECT_EQ(policy_service->source(), "test_source");
 
-  PolicyStatus<std::string> app1_channel_status;
-  std::string app1_channel;
-  EXPECT_TRUE(policy_service->GetTargetChannel("app1", &app1_channel_status,
-                                               &app1_channel));
-  EXPECT_TRUE(app1_channel_status.effective_policy());
-  EXPECT_EQ(app1_channel_status.effective_policy().value().policy,
-            "test_channel");
-  EXPECT_EQ(app1_channel, "test_channel");
-  EXPECT_FALSE(app1_channel_status.conflict_policy());
+  PolicyStatus<std::string> app1_channel =
+      policy_service->GetTargetChannel("app1");
+  ASSERT_TRUE(app1_channel);
+  EXPECT_EQ(app1_channel.policy(), "test_channel");
+  EXPECT_EQ(app1_channel.conflict_policy(), absl::nullopt);
 
-  PolicyStatus<std::string> app2_channel_status;
-  std::string app2_channel;
-  EXPECT_FALSE(policy_service->GetTargetChannel("app2", &app2_channel_status,
-                                                &app2_channel));
-  EXPECT_FALSE(app2_channel_status.effective_policy());
-  EXPECT_FALSE(app2_channel_status.conflict_policy());
+  PolicyStatus<std::string> app2_channel =
+      policy_service->GetTargetChannel("app2");
+  EXPECT_FALSE(app2_channel);
+  EXPECT_EQ(app2_channel.conflict_policy(), absl::nullopt);
 
-  PolicyStatus<int> app1_update_status;
-  int update_policy = 0;
-  EXPECT_FALSE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app1", &app1_update_status, &update_policy));
-  EXPECT_FALSE(app1_update_status.conflict_policy());
+  PolicyStatus<int> app1_update_status =
+      policy_service->GetPolicyForAppUpdates("app1");
+  EXPECT_FALSE(app1_update_status);
+  EXPECT_EQ(app1_update_status.conflict_policy(), absl::nullopt);
 
-  PolicyStatus<int> app2_update_status;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app2", &app2_update_status, &update_policy));
-  EXPECT_TRUE(app2_update_status.effective_policy());
-  EXPECT_EQ(app2_update_status.effective_policy().value().policy, 3);
-  EXPECT_FALSE(app2_update_status.conflict_policy());
-  EXPECT_EQ(update_policy, 3);
+  PolicyStatus<int> app2_update_status =
+      policy_service->GetPolicyForAppUpdates("app2");
+  EXPECT_TRUE(app2_update_status);
+  EXPECT_EQ(app2_update_status.policy(), 3);
+  EXPECT_EQ(app2_update_status.conflict_policy(), absl::nullopt);
 }
 
 TEST(PolicyService, MultiplePolicyManagers) {
@@ -223,20 +213,19 @@
   EXPECT_EQ(policy_service->source(),
             "group_policy;device_management;imaginary;default");
 
-  PolicyStatus<UpdatesSuppressedTimes> suppressed_time_status;
-  EXPECT_TRUE(policy_service->GetUpdatesSuppressedTimes(
-      &suppressed_time_status, &updates_suppressed_times));
+  PolicyStatus<UpdatesSuppressedTimes> suppressed_time_status =
+      policy_service->GetUpdatesSuppressedTimes();
+  ASSERT_TRUE(suppressed_time_status);
   EXPECT_TRUE(suppressed_time_status.conflict_policy());
   EXPECT_EQ(suppressed_time_status.effective_policy().value().source,
             "group_policy");
-  EXPECT_EQ(updates_suppressed_times.start_hour_, 5);
-  EXPECT_EQ(updates_suppressed_times.start_minute_, 10);
-  EXPECT_EQ(updates_suppressed_times.duration_minute_, 30);
+  EXPECT_EQ(suppressed_time_status.policy().start_hour_, 5);
+  EXPECT_EQ(suppressed_time_status.policy().start_minute_, 10);
+  EXPECT_EQ(suppressed_time_status.policy().duration_minute_, 30);
 
-  PolicyStatus<std::string> channel_status;
-  std::string channel;
-  EXPECT_TRUE(
-      policy_service->GetTargetChannel("app1", &channel_status, &channel));
+  PolicyStatus<std::string> channel_status =
+      policy_service->GetTargetChannel("app1");
+  ASSERT_TRUE(channel_status);
   const PolicyStatus<std::string>::Entry& channel_policy =
       channel_status.effective_policy().value();
   EXPECT_EQ(channel_policy.source, "group_policy");
@@ -246,13 +235,11 @@
       channel_status.conflict_policy().value();
   EXPECT_EQ(channel_conflict_policy.source, "device_management");
   EXPECT_EQ(channel_conflict_policy.policy, "channel_dm");
-  EXPECT_EQ(channel, "channel_gp");
+  EXPECT_EQ(channel_status.policy(), "channel_gp");
 
-  PolicyStatus<int> app1_update_status;
-  int update_policy = 0;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app1", &app1_update_status, &update_policy));
-  EXPECT_TRUE(app1_update_status.effective_policy());
+  PolicyStatus<int> app1_update_status =
+      policy_service->GetPolicyForAppUpdates("app1");
+  ASSERT_TRUE(app1_update_status);
   const PolicyStatus<int>::Entry& app1_update_policy =
       app1_update_status.effective_policy().value();
   EXPECT_EQ(app1_update_policy.source, "device_management");
@@ -263,34 +250,29 @@
   EXPECT_TRUE(app1_update_status.conflict_policy());
   EXPECT_EQ(app1_update_conflict_policy.policy, 2);
   EXPECT_EQ(app1_update_conflict_policy.source, "imaginary");
-  EXPECT_EQ(update_policy, 3);
+  EXPECT_EQ(app1_update_status.policy(), 3);
 
-  PolicyStatus<int> app2_update_status;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app2", &app2_update_status, &update_policy));
-  EXPECT_TRUE(app2_update_status.effective_policy());
+  PolicyStatus<int> app2_update_status =
+      policy_service->GetPolicyForAppUpdates("app2");
+  ASSERT_TRUE(app2_update_status);
   const PolicyStatus<int>::Entry& app2_update_policy =
       app2_update_status.effective_policy().value();
   EXPECT_EQ(app2_update_policy.source, "group_policy");
   EXPECT_EQ(app2_update_policy.policy, 1);
-  EXPECT_EQ(update_policy, 1);
-  EXPECT_FALSE(app2_update_status.conflict_policy());
+  EXPECT_EQ(app2_update_status.policy(), 1);
+  EXPECT_EQ(app2_update_status.conflict_policy(), absl::nullopt);
 
-  PolicyStatus<std::string> download_preference_status;
-  std::string download_preference;
-  EXPECT_TRUE(policy_service->GetDownloadPreferenceGroupPolicy(
-      &download_preference_status, &download_preference));
-  EXPECT_TRUE(download_preference_status.effective_policy());
+  PolicyStatus<std::string> download_preference_status =
+      policy_service->GetDownloadPreferenceGroupPolicy();
+  ASSERT_TRUE(download_preference_status);
   const PolicyStatus<std::string>::Entry& download_preference_policy =
       download_preference_status.effective_policy().value();
   EXPECT_EQ(download_preference_policy.source, "imaginary");
   EXPECT_EQ(download_preference_policy.policy, "cacheable");
-  EXPECT_EQ(download_preference, "cacheable");
-  EXPECT_FALSE(download_preference_status.conflict_policy());
+  EXPECT_EQ(download_preference_status.policy(), "cacheable");
+  EXPECT_EQ(download_preference_status.conflict_policy(), absl::nullopt);
 
-  int cache_size_limit = 0;
-  EXPECT_FALSE(policy_service->GetPackageCacheSizeLimitMBytes(
-      nullptr, &cache_size_limit));
+  EXPECT_FALSE(policy_service->GetPackageCacheSizeLimitMBytes());
 }
 
 TEST(PolicyService, MultiplePolicyManagers_WithUnmanagedOnes) {
@@ -331,21 +313,18 @@
       base::MakeRefCounted<PolicyService>(std::move(managers));
   EXPECT_EQ(policy_service->source(), "device_management;imaginary;default");
 
-  PolicyStatus<UpdatesSuppressedTimes> suppressed_time_status;
-  EXPECT_TRUE(policy_service->GetUpdatesSuppressedTimes(
-      &suppressed_time_status, &updates_suppressed_times));
-  EXPECT_TRUE(suppressed_time_status.conflict_policy());
+  PolicyStatus<UpdatesSuppressedTimes> suppressed_time_status =
+      policy_service->GetUpdatesSuppressedTimes();
+  ASSERT_TRUE(suppressed_time_status);
   EXPECT_EQ(suppressed_time_status.effective_policy().value().source,
             "device_management");
-  EXPECT_EQ(updates_suppressed_times.start_hour_, 5);
-  EXPECT_EQ(updates_suppressed_times.start_minute_, 10);
-  EXPECT_EQ(updates_suppressed_times.duration_minute_, 30);
+  EXPECT_EQ(suppressed_time_status.policy().start_hour_, 5);
+  EXPECT_EQ(suppressed_time_status.policy().start_minute_, 10);
+  EXPECT_EQ(suppressed_time_status.policy().duration_minute_, 30);
 
-  PolicyStatus<std::string> channel_status;
-  std::string channel;
-  EXPECT_TRUE(
-      policy_service->GetTargetChannel("app1", &channel_status, &channel));
-  EXPECT_TRUE(channel_status.effective_policy());
+  PolicyStatus<std::string> channel_status =
+      policy_service->GetTargetChannel("app1");
+  ASSERT_TRUE(channel_status);
   const PolicyStatus<std::string>::Entry& channel_status_policy =
       channel_status.effective_policy().value();
   EXPECT_EQ(channel_status_policy.source, "device_management");
@@ -355,12 +334,11 @@
       channel_status.conflict_policy().value();
   EXPECT_EQ(channel_status_conflict_policy.policy, "channel_imaginary");
   EXPECT_EQ(channel_status_conflict_policy.source, "imaginary");
-  EXPECT_EQ(channel, "channel_dm");
+  EXPECT_EQ(channel_status.policy(), "channel_dm");
 
-  PolicyStatus<int> app1_update_status;
-  int update_policy = 0;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app1", &app1_update_status, &update_policy));
+  PolicyStatus<int> app1_update_status =
+      policy_service->GetPolicyForAppUpdates("app1");
+  ASSERT_TRUE(app1_update_status);
   const PolicyStatus<int>::Entry& app1_update_status_policy =
       app1_update_status.effective_policy().value();
   EXPECT_EQ(app1_update_status_policy.source, "device_management");
@@ -370,31 +348,27 @@
       app1_update_status.conflict_policy().value();
   EXPECT_EQ(app1_update_status_conflict_policy.source, "imaginary");
   EXPECT_EQ(app1_update_status_conflict_policy.policy, 2);
-  EXPECT_EQ(update_policy, 3);
+  EXPECT_EQ(app1_update_status.policy(), 3);
 
-  PolicyStatus<int> app2_update_status;
-  EXPECT_TRUE(policy_service->GetEffectivePolicyForAppUpdates(
-      "app2", &app2_update_status, &update_policy));
-  EXPECT_TRUE(app2_update_status.effective_policy());
-  EXPECT_FALSE(app2_update_status.conflict_policy());
+  PolicyStatus<int> app2_update_status =
+      policy_service->GetPolicyForAppUpdates("app2");
+  ASSERT_TRUE(app2_update_status);
+  EXPECT_EQ(app2_update_status.conflict_policy(), absl::nullopt);
   const PolicyStatus<int>::Entry& app2_update_status_policy =
       app2_update_status.effective_policy().value();
   EXPECT_EQ(app2_update_status_policy.source, "default");
   EXPECT_EQ(app2_update_status_policy.policy, 1);
+  EXPECT_EQ(app2_update_status.policy(), 1);
 
-  PolicyStatus<std::string> download_preference_status;
-  std::string download_preference;
-  EXPECT_TRUE(policy_service->GetDownloadPreferenceGroupPolicy(
-      &download_preference_status, &download_preference));
-  EXPECT_TRUE(download_preference_status.effective_policy());
+  PolicyStatus<std::string> download_preference_status =
+      policy_service->GetDownloadPreferenceGroupPolicy();
+  ASSERT_TRUE(download_preference_status);
   EXPECT_EQ(download_preference_status.effective_policy().value().source,
             "imaginary");
-  EXPECT_EQ(download_preference, "cacheable");
-  EXPECT_FALSE(download_preference_status.conflict_policy());
+  EXPECT_EQ(download_preference_status.policy(), "cacheable");
+  EXPECT_EQ(download_preference_status.conflict_policy(), absl::nullopt);
 
-  int cache_size_limit = 0;
-  EXPECT_FALSE(policy_service->GetPackageCacheSizeLimitMBytes(
-      nullptr, &cache_size_limit));
+  EXPECT_FALSE(policy_service->GetPackageCacheSizeLimitMBytes());
 }
 
 }  // namespace updater
diff --git a/chrome/updater/policy/win/group_policy_manager_unittest.cc b/chrome/updater/policy/win/group_policy_manager_unittest.cc
index 0262341..b537853e2 100644
--- a/chrome/updater/policy/win/group_policy_manager_unittest.cc
+++ b/chrome/updater/policy/win/group_policy_manager_unittest.cc
@@ -53,59 +53,35 @@
 
   EXPECT_EQ(policy_manager->source(), "GroupPolicy");
 
-  int check_period = 0;
-  EXPECT_FALSE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
-  EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  int cache_size_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  int cache_life_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
 
   std::string app_id = base::WideToUTF8(TEST_APP_ID);
-  int install_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      app_id, &install_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-
-  int update_policy = -1;
-  EXPECT_FALSE(
-      policy_manager->GetEffectivePolicyForAppUpdates(app_id, &update_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-
-  std::string target_channel;
-  EXPECT_FALSE(policy_manager->GetTargetChannel(app_id, &target_channel));
-  EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
-
-  std::string target_version_prefix;
-  EXPECT_FALSE(
-      policy_manager->GetTargetVersionPrefix(app_id, &target_version_prefix));
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
-
-  bool is_rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      app_id, &is_rollback_allowed));
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(
+                "non-exist-app-fallback-to-global"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(
+                "non-exist-app-fallback-to-global"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel(app_id), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(app_id), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"),
+            absl::nullopt);
 }
 
 TEST_F(GroupPolicyManagerTests, PolicyRead) {
@@ -146,76 +122,39 @@
   EXPECT_EQ(policy_manager->HasActiveDevicePolicies(),
             base::win::IsEnrolledToDomain());
 
-  int check_period = 0;
-  EXPECT_TRUE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-  EXPECT_EQ(check_period, 480);
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), 480);
 
-  UpdatesSuppressedTimes suppressed_times = {};
-  EXPECT_TRUE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-  EXPECT_EQ(suppressed_times.start_hour_, 2);
-  EXPECT_EQ(suppressed_times.start_minute_, 30);
-  EXPECT_EQ(suppressed_times.duration_minute_, 500);
+  absl::optional<UpdatesSuppressedTimes> suppressed_times =
+      policy_manager->GetUpdatesSuppressedTimes();
+  ASSERT_TRUE(suppressed_times);
+  EXPECT_EQ(suppressed_times->start_hour_, 2);
+  EXPECT_EQ(suppressed_times->start_minute_, 30);
+  EXPECT_EQ(suppressed_times->duration_minute_, 500);
 
-  std::string download_preference;
-  EXPECT_TRUE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-  EXPECT_EQ(download_preference, "cacheable");
-
-  int cache_size_limit = 0;
-  EXPECT_TRUE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  EXPECT_EQ(cache_size_limit, 100);
-  int cache_life_limit = 0;
-  EXPECT_TRUE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-  EXPECT_EQ(cache_life_limit, 45);
-
-  std::string proxy_mode;
-  EXPECT_TRUE(policy_manager->GetProxyMode(&proxy_mode));
-  EXPECT_EQ(proxy_mode, "fixed_servers");
-  std::string proxy_server;
-  EXPECT_TRUE(policy_manager->GetProxyServer(&proxy_server));
-  EXPECT_EQ(proxy_server, "http://foo.bar");
-  std::string proxy_pac_url;
-  EXPECT_TRUE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-  EXPECT_EQ(proxy_pac_url, "go/pac.url");
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), "cacheable");
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), 100);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), 45);
+  EXPECT_EQ(policy_manager->GetProxyMode(), "fixed_servers");
+  EXPECT_EQ(policy_manager->GetProxyServer(), "http://foo.bar");
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), "go/pac.url");
 
   std::string app_id = base::WideToUTF8(TEST_APP_ID);
-  int install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      app_id, &install_policy));
-  EXPECT_EQ(install_policy, 3);
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-  EXPECT_EQ(install_policy, 2);
-
-  int update_policy = -1;
-  EXPECT_TRUE(
-      policy_manager->GetEffectivePolicyForAppUpdates(app_id, &update_policy));
-  EXPECT_EQ(update_policy, 2);
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-  EXPECT_EQ(update_policy, 1);
-
-  std::string target_channel;
-  EXPECT_TRUE(policy_manager->GetTargetChannel(app_id, &target_channel));
-  EXPECT_EQ(target_channel, "beta");
-  EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
-
-  std::string target_version_prefix;
-  EXPECT_TRUE(
-      policy_manager->GetTargetVersionPrefix(app_id, &target_version_prefix));
-  EXPECT_EQ(target_version_prefix, "55.55.");
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
-
-  bool is_rollback_allowed = false;
-  EXPECT_TRUE(policy_manager->IsRollbackToTargetVersionAllowed(
-      app_id, &is_rollback_allowed));
-  EXPECT_TRUE(is_rollback_allowed);
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(app_id), 3);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(
+                "non-exist-app-fallback-to-global"),
+            2);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(app_id), 2);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(
+                "non-exist-app-fallback-to-global"),
+            1);
+  EXPECT_EQ(policy_manager->GetTargetChannel(app_id), "beta");
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(app_id), "55.55.");
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(app_id), true);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"),
+            absl::nullopt);
 }
 
 TEST_F(GroupPolicyManagerTests, WrongPolicyValueType) {
@@ -253,59 +192,35 @@
   std::unique_ptr<PolicyManagerInterface> policy_manager =
       std::make_unique<GroupPolicyManager>();
 
-  int check_period = 0;
-  EXPECT_FALSE(policy_manager->GetLastCheckPeriodMinutes(&check_period));
-
-  UpdatesSuppressedTimes suppressed_times = {};
-  EXPECT_FALSE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-
-  std::string download_preference;
-  EXPECT_FALSE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-
-  int cache_size_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size_limit));
-  int cache_life_limit = 0;
-  EXPECT_FALSE(
-      policy_manager->GetPackageCacheExpirationTimeDays(&cache_life_limit));
-
-  std::string proxy_mode;
-  EXPECT_FALSE(policy_manager->GetProxyMode(&proxy_mode));
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-  std::string proxy_pac_url;
-  EXPECT_FALSE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
+  EXPECT_EQ(policy_manager->GetLastCheckPeriodMinutes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetUpdatesSuppressedTimes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetDownloadPreferenceGroupPolicy(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheSizeLimitMBytes(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetPackageCacheExpirationTimeDays(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyMode(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyServer(), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetProxyPacUrl(), absl::nullopt);
 
   std::string app_id = base::WideToUTF8(TEST_APP_ID);
-  int install_policy = -1;
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      app_id, &install_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppInstalls(
-      "non-exist-app-fallback-to-global", &install_policy));
-
-  int update_policy = -1;
-  EXPECT_FALSE(
-      policy_manager->GetEffectivePolicyForAppUpdates(app_id, &update_policy));
-  EXPECT_FALSE(policy_manager->GetEffectivePolicyForAppUpdates(
-      "non-exist-app-fallback-to-global", &update_policy));
-
-  std::string target_channel;
-  EXPECT_FALSE(policy_manager->GetTargetChannel(app_id, &target_channel));
-  EXPECT_FALSE(
-      policy_manager->GetTargetChannel("non-exist-app", &target_channel));
-
-  std::string target_version_prefix;
-  EXPECT_FALSE(
-      policy_manager->GetTargetVersionPrefix(app_id, &target_version_prefix));
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix("non-exist-app",
-                                                      &target_version_prefix));
-
-  bool is_rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      app_id, &is_rollback_allowed));
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      "non-exist-app", &is_rollback_allowed));
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppInstalls(
+                "non-exist-app-fallback-to-global"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetEffectivePolicyForAppUpdates(
+                "non-exist-app-fallback-to-global"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel(app_id), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetChannel("non-exist-app"), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix(app_id), absl::nullopt);
+  EXPECT_EQ(policy_manager->GetTargetVersionPrefix("non-exist-app"),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed(app_id),
+            absl::nullopt);
+  EXPECT_EQ(policy_manager->IsRollbackToTargetVersionAllowed("non-exist-app"),
+            absl::nullopt);
 }
 
 }  // namespace updater
diff --git a/chrome/updater/update_service_impl.cc b/chrome/updater/update_service_impl.cc
index 1c615bf..fab466d 100644
--- a/chrome/updater/update_service_impl.cc
+++ b/chrome/updater/update_service_impl.cc
@@ -182,37 +182,30 @@
               return it != app_install_data_index.end() ? it->second : "";
             }(),
             [&config, &id]() {
-              std::string component_channel;
-              return config->GetPolicyService()->GetTargetChannel(
-                         id, nullptr, &component_channel)
-                         ? component_channel
-                         : std::string();
+              return config->GetPolicyService()->GetTargetChannel(id).policy_or(
+                  std::string());
             }(),
             [&config, &id]() {
-              std::string target_version_prefix;
-              return config->GetPolicyService()->GetTargetVersionPrefix(
-                         id, nullptr, &target_version_prefix)
-                         ? target_version_prefix
-                         : std::string();
-            }(),
-            [&config, &id]() {
-              bool rollback_allowed;
               return config->GetPolicyService()
-                             ->IsRollbackToTargetVersionAllowed(
-                                 id, nullptr, &rollback_allowed)
-                         ? rollback_allowed
-                         : false;
+                  ->GetTargetVersionPrefix(id)
+                  .policy_or(std::string());
+            }(),
+            [&config, &id]() {
+              return config->GetPolicyService()
+                  ->IsRollbackToTargetVersionAllowed(id)
+                  .policy_or(false);
             }(),
             [&config, &id, &foreground, update_blocked]() {
               if (update_blocked)
                 return true;
-              int policy = kPolicyEnabled;
-              return config->GetPolicyService()
-                         ->GetEffectivePolicyForAppUpdates(id, nullptr,
-                                                           &policy) &&
-                     (policy == kPolicyDisabled ||
-                      (!foreground && policy == kPolicyManualUpdatesOnly) ||
-                      (foreground && policy == kPolicyAutomaticUpdatesOnly));
+              PolicyStatus<int> app_updates =
+                  config->GetPolicyService()->GetPolicyForAppUpdates(id);
+              return app_updates &&
+                     (app_updates.policy() == kPolicyDisabled ||
+                      (!foreground &&
+                       app_updates.policy() == kPolicyManualUpdatesOnly) ||
+                      (foreground &&
+                       app_updates.policy() == kPolicyAutomaticUpdatesOnly));
             }(),
             policy_same_version_update, persisted_data,
             config->GetCrxVerifierFormat())
@@ -368,13 +361,15 @@
   VLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  std::vector<std::string> force_install_apps;
-  if (!config_->GetPolicyService()->GetForceInstallApps(nullptr,
-                                                        &force_install_apps)) {
+  PolicyStatus<std::vector<std::string>> force_install_apps_status =
+      config_->GetPolicyService()->GetForceInstallApps();
+  if (!force_install_apps_status) {
     base::BindPostTask(main_task_runner_, std::move(callback))
         .Run(UpdateService::Result::kSuccess);
     return;
   }
+  std::vector<std::string> force_install_apps =
+      force_install_apps_status.policy();
   DCHECK(!force_install_apps.empty());
 
   std::vector<std::string> installed_app_ids = persisted_data_->GetAppIds();
@@ -605,13 +600,19 @@
   policy = kPolicyEnabled;
 
   if (is_install) {
-    return config_->GetPolicyService()->GetEffectivePolicyForAppInstalls(
-               app_id, nullptr, &policy) &&
+    PolicyStatus<int> app_install_policy_status =
+        config_->GetPolicyService()->GetPolicyForAppInstalls(app_id);
+    if (app_install_policy_status)
+      policy = app_install_policy_status.policy();
+    return app_install_policy_status &&
            (policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
                                           policy == kPolicyEnabledMachineOnly));
   } else {
-    return config_->GetPolicyService()->GetEffectivePolicyForAppUpdates(
-               app_id, nullptr, &policy) &&
+    PolicyStatus<int> app_update_policy_status =
+        config_->GetPolicyService()->GetPolicyForAppUpdates(app_id);
+    if (app_update_policy_status)
+      policy = app_update_policy_status.policy();
+    return app_update_policy_status &&
            (policy == kPolicyDisabled ||
             ((policy == kPolicyManualUpdatesOnly) &&
              (priority != Priority::kForeground)) ||
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index d7031bb9..33996df2 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -45,6 +45,7 @@
 #include "base/win/registry.h"
 #include "base/win/scoped_bstr.h"
 #include "base/win/scoped_handle.h"
+#include "base/win/scoped_localalloc.h"
 #include "base/win/scoped_process_information.h"
 #include "base/win/scoped_variant.h"
 #include "base/win/startup_information.h"
@@ -711,17 +712,17 @@
     return std::wstring();
   }
 
-  return base::StrCat({base::CommandLine(exe_installer).GetCommandLineString(),
-                       L" ", arguments, [&installer_data_file]() {
-                         if (!installer_data_file)
-                           return std::wstring();
+  return base::StrCat(
+      {QuoteForCommandLineToArgvW(exe_installer.value()), L" ", arguments,
+       [&installer_data_file]() {
+         if (!installer_data_file)
+           return std::wstring();
 
-                         base::CommandLine installer_data_args(
-                             base::CommandLine::NO_PROGRAM);
-                         installer_data_args.AppendSwitchPath(
-                             kInstallerDataSwitch, *installer_data_file);
-                         return installer_data_args.GetCommandLineString();
-                       }()});
+         base::CommandLine installer_data_args(base::CommandLine::NO_PROGRAM);
+         installer_data_args.AppendSwitchPath(kInstallerDataSwitch,
+                                              *installer_data_file);
+         return base::StrCat({L" ", installer_data_args.GetArgumentsString()});
+       }()});
 }
 
 bool IsServiceRunning(const std::wstring& service_name) {
@@ -904,9 +905,11 @@
 
 absl::optional<base::CommandLine> CommandLineForLegacyFormat(
     const std::wstring& cmd_string) {
-  wchar_t** args = nullptr;
   int num_args = 0;
-  args = ::CommandLineToArgvW(cmd_string.c_str(), &num_args);
+  base::win::ScopedLocalAllocTyped<wchar_t*> args(
+      ::CommandLineToArgvW(cmd_string.c_str(), &num_args));
+  if (!args)
+    return absl::nullopt;
 
   auto is_switch = [](const std::wstring& arg) { return arg[0] == L'-'; };
 
@@ -915,23 +918,23 @@
   };
 
   // First argument is the program.
-  base::CommandLine command_line(base::FilePath{args[0]});
+  base::CommandLine command_line(base::FilePath{args.get()[0]});
 
   for (int i = 1; i < num_args; ++i) {
-    const std::wstring next_arg = i < num_args - 1 ? args[i + 1] : L"";
+    const std::wstring next_arg = i < num_args - 1 ? args.get()[i + 1] : L"";
 
-    if (is_switch(args[i]) || is_switch(next_arg)) {
+    if (is_switch(args.get()[i]) || is_switch(next_arg)) {
       // Won't parse Chromium-style command line.
       return absl::nullopt;
     }
 
-    if (!is_legacy_switch(args[i])) {
+    if (!is_legacy_switch(args.get()[i])) {
       // This is a bare argument.
-      command_line.AppendArg(base::WideToASCII(args[i]));
+      command_line.AppendArg(base::WideToASCII(args.get()[i]));
       continue;
     }
 
-    const std::string switch_name = base::WideToASCII(&args[i][1]);
+    const std::string switch_name = base::WideToASCII(&args.get()[i][1]);
     if (switch_name.empty()) {
       VLOG(1) << "Empty switch in command line: [" << cmd_string << "]";
       return absl::nullopt;
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h
index 70fe9dd3..d809a91 100644
--- a/chrome/updater/util/win_util.h
+++ b/chrome/updater/util/win_util.h
@@ -44,13 +44,6 @@
 
 namespace updater {
 
-struct LocalAllocTraits {
-  static HLOCAL InvalidValue() { return nullptr; }
-  static void Free(HLOCAL mem) { ::LocalFree(mem); }
-};
-
-using ScopedLocalAlloc = base::ScopedGeneric<HLOCAL, LocalAllocTraits>;
-
 // Helper for methods which perform system operations which may fail. The
 // failure reason is returned as an HRESULT.
 // TODO(crbug.com/1369769): Remove the following warning once resolved in
@@ -348,23 +341,12 @@
 // Quotes `input` if necessary so that it will be interpreted as a single
 // command-line parameter according to the rules for ::CommandLineToArgvW.
 //
-// ::CommandLineToArgvW has a special interpretation of backslash characters
-// when they are followed by a quotation mark character ("). This interpretation
-// assumes that any preceding argument is a valid file system path, or else it
-// may behave unpredictably.
-//
-// This special interpretation controls the "in quotes" mode tracked by the
-// parser. When this mode is off, whitespace terminates the current argument.
-// When on, whitespace is added to the argument like all other characters.
-
-// * 2n backslashes followed by a quotation mark produce n backslashes followed
-// by begin/end quote. This does not become part of the parsed argument, but
-// toggles the "in quotes" mode.
-// * (2n) + 1 backslashes followed by a quotation mark again produce n
-// backslashes followed by a quotation mark literal ("). This does not toggle
-// the "in quotes" mode.
-// * n backslashes not followed by a quotation mark simply produce n
-// backslashes.
+// As per the documentation "::CommandLineToArgvW has a special interpretation
+// of backslash characters when they are followed by a quotation mark
+// character". `QuoteForCommandLineToArgvW` follows this interpretation. See
+// the documentation at
+// https://learn.microsoft.com/en-us/search/?terms=CommandLineToArgvW
+// for more details.
 //
 // See examples in the `WinUtil.QuoteForCommandLineToArgvW` unit test.
 std::wstring QuoteForCommandLineToArgvW(const std::wstring& input);
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index c3a39b1..b7a7af01 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -26,6 +26,7 @@
 #include "base/test/test_timeouts.h"
 #include "base/win/atl.h"
 #include "base/win/scoped_handle.h"
+#include "base/win/scoped_localalloc.h"
 #include "chrome/updater/test_scope.h"
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_version.h"
@@ -351,7 +352,6 @@
     std::vector<std::wstring> input_args;
     const wchar_t* expected_output;
   } test_cases[] = {
-      // Unformatted parameters.
       {{L"abc=1"}, L"abc=1"},
       {{L"abc=1", L"xyz=2"}, L"abc=1 xyz=2"},
       {{L"abc=1", L"xyz=2", L"q"}, L"abc=1 xyz=2 q"},
@@ -367,14 +367,13 @@
         base::StrCat({L"c:\\test\\process.exe ",
                       base::JoinString(test_case.input_args, L" ")});
     int num_args = 0;
-    ScopedLocalAlloc args(
+    base::win::ScopedLocalAllocTyped<wchar_t*> argv(
         ::CommandLineToArgvW(&input_command_line[0], &num_args));
     ASSERT_EQ(num_args - 1U, test_case.input_args.size());
 
-    const wchar_t** argv = reinterpret_cast<const wchar_t**>(args.get());
     std::wstring recreated_command_line;
     for (int i = 1; i < num_args; ++i) {
-      recreated_command_line.append(QuoteForCommandLineToArgvW(argv[i]));
+      recreated_command_line.append(QuoteForCommandLineToArgvW(argv.get()[i]));
 
       if (i + 1 < num_args)
         recreated_command_line.push_back(L' ');
@@ -386,5 +385,4 @@
         << test_case.expected_output;
   }
 }
-
 }  // namespace updater
diff --git a/chrome/updater/win/app_command_runner.cc b/chrome/updater/win/app_command_runner.cc
index e9252d43..7e206d5d 100644
--- a/chrome/updater/win/app_command_runner.cc
+++ b/chrome/updater/win/app_command_runner.cc
@@ -23,6 +23,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/version.h"
 #include "base/win/registry.h"
+#include "base/win/scoped_localalloc.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
@@ -274,14 +275,14 @@
   VLOG(2) << __func__ << ": " << scope << ": " << command_format;
 
   int num_args = 0;
-  ScopedLocalAlloc args(::CommandLineToArgvW(&command_format[0], &num_args));
-  if (!args.is_valid() || num_args < 1) {
-    LOG(ERROR) << __func__ << "!args.is_valid() || num_args < 1: " << num_args;
+  base::win::ScopedLocalAllocTyped<wchar_t*> argv(
+      ::CommandLineToArgvW(&command_format[0], &num_args));
+  if (!argv || num_args < 1) {
+    LOG(ERROR) << __func__ << "!argv || num_args < 1: " << num_args;
     return E_INVALIDARG;
   }
 
-  const wchar_t** argv = reinterpret_cast<const wchar_t**>(args.get());
-  const base::FilePath exe = base::FilePath(argv[0]);
+  const base::FilePath exe = base::FilePath(argv.get()[0]);
   if (!IsSecureAppCommandExePath(scope, exe)) {
     LOG(WARNING) << __func__
                  << ": !IsSecureAppCommandExePath(scope, exe): " << exe;
@@ -291,7 +292,7 @@
   executable = exe;
   parameters.clear();
   for (int i = 1; i < num_args; ++i)
-    parameters.push_back(argv[i]);
+    parameters.push_back(argv.get()[i]);
 
   return S_OK;
 }
diff --git a/chrome/updater/win/app_command_runner_unittest.cc b/chrome/updater/win/app_command_runner_unittest.cc
index 900bff24..a4db269 100644
--- a/chrome/updater/win/app_command_runner_unittest.cc
+++ b/chrome/updater/win/app_command_runner_unittest.cc
@@ -21,7 +21,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/test_timeouts.h"
-#include "base/types/expected.h"
+#include "base/win/scoped_localalloc.h"
 #include "build/branding_buildflags.h"
 #include "chrome/updater/test_scope.h"
 #include "chrome/updater/updater_branding.h"
@@ -250,15 +250,15 @@
     // verify that it produces the original substitution.
     std::wstring cmd = base::StrCat({L"process.exe ", command_line.value()});
     int num_args = 0;
-    ScopedLocalAlloc argv_handle(::CommandLineToArgvW(&cmd[0], &num_args));
-    ASSERT_TRUE(argv_handle.is_valid());
+    base::win::ScopedLocalAllocTyped<wchar_t*> argv_handle(
+        ::CommandLineToArgvW(&cmd[0], &num_args));
+    ASSERT_TRUE(argv_handle);
     EXPECT_EQ(num_args, 2) << "substitution '" << test_case.substitutions[0]
                            << "' gave command line '" << cmd
                            << "' which unexpectedly did not parse to a single "
                            << "argument.";
 
-    EXPECT_EQ(reinterpret_cast<const wchar_t**>(argv_handle.get())[1],
-              test_case.substitutions[0])
+    EXPECT_EQ(argv_handle.get()[1], test_case.substitutions[0])
         << "substitution '" << test_case.substitutions[0]
         << "' gave command line '" << cmd
         << "' which did not parse back to the "
diff --git a/chrome/updater/win/installer/configuration.cc b/chrome/updater/win/installer/configuration.cc
index fac8159..e426aa4 100644
--- a/chrome/updater/win/installer/configuration.cc
+++ b/chrome/updater/win/installer/configuration.cc
@@ -35,10 +35,9 @@
 }
 
 void Configuration::Clear() {
-  if (args_) {
-    ::LocalFree(args_);
-    args_ = nullptr;
-  }
+  if (args_)
+    args_.reset();
+
   command_line_ = nullptr;
   operation_ = INSTALL_PRODUCT;
   argument_count_ = 0;
@@ -51,14 +50,14 @@
 // not release it.
 bool Configuration::ParseCommandLine(const wchar_t* command_line) {
   command_line_ = command_line;
-  args_ = ::CommandLineToArgvW(command_line_, &argument_count_);
+  args_.reset(::CommandLineToArgvW(command_line_, &argument_count_));
   if (!args_)
     return false;
 
   for (int i = 1; i < argument_count_; ++i) {
-    if (0 == ::lstrcmpi(args_[i], L"--system-level"))
+    if (0 == ::lstrcmpi(args_.get()[i], L"--system-level"))
       is_system_level_ = true;
-    else if (0 == ::lstrcmpi(args_[i], L"--cleanup"))
+    else if (0 == ::lstrcmpi(args_.get()[i], L"--cleanup"))
       operation_ = CLEANUP;
   }
 
diff --git a/chrome/updater/win/installer/configuration.h b/chrome/updater/win/installer/configuration.h
index bd6288fd..cdfa7319 100644
--- a/chrome/updater/win/installer/configuration.h
+++ b/chrome/updater/win/installer/configuration.h
@@ -7,7 +7,7 @@
 
 #include <windows.h>
 
-#include "base/memory/raw_ptr.h"
+#include "base/win/scoped_localalloc.h"
 
 namespace updater {
 
@@ -40,7 +40,7 @@
   void Clear();
   bool ParseCommandLine(const wchar_t* command_line);
 
-  raw_ptr<wchar_t*> args_ = nullptr;
+  base::win::ScopedLocalAllocTyped<wchar_t*> args_;
   const wchar_t* command_line_ = nullptr;
   int argument_count_ = 0;
   Operation operation_ = INSTALL_PRODUCT;
diff --git a/chrome/updater/win/installer/installer.cc b/chrome/updater/win/installer/installer.cc
index a59b5e1..fae7766 100644
--- a/chrome/updater/win/installer/installer.cc
+++ b/chrome/updater/win/installer/installer.cc
@@ -31,6 +31,7 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "base/win/scoped_com_initializer.h"
+#include "base/win/scoped_localalloc.h"
 #include "base/win/windows_version.h"
 #include "chrome/installer/util/lzma_util.h"
 #include "chrome/installer/util/util_constants.h"
@@ -193,26 +194,24 @@
 
   // Append the command line arguments in `cmd_line` first.
   int num_args = 0;
-  ScopedLocalAlloc scoped_arg_list(::CommandLineToArgvW(cmd_line, &num_args));
-  wchar_t** const arg_list =
-      reinterpret_cast<wchar_t** const>(scoped_arg_list.get());
+  base::win::ScopedLocalAllocTyped<wchar_t*> argv(
+      ::CommandLineToArgvW(cmd_line, &num_args));
   for (int i = 1; i != num_args; ++i) {
     if (!args.append(L" ") ||
-        !args.append(QuoteForCommandLineToArgvW(arg_list[i]).c_str())) {
+        !args.append(QuoteForCommandLineToArgvW(argv.get()[i]).c_str())) {
       return ProcessExitResult(COMMAND_STRING_OVERFLOW);
     }
   }
 
   // Handle the tag. Use the tag from the --tag command line argument if such
-  // argument exists. If --tag is present in the arg_list, then it is going
-  // to be handed over to the updater, along with the other arguments.
-  // Otherwise, try extracting a tag embedded in the program image of the meta
-  // installer.
-  if (![arg_list, num_args]() {
+  // argument exists. If --tag is present in `argv`, then it is going to be
+  // handed over to the updater, along with the other arguments. Otherwise, try
+  // extracting a tag embedded in the program image of the meta installer.
+  if (![&argv, num_args]() {
         // Returns true if the --tag argument is present on the command line.
         constexpr wchar_t kTagSwitch[] = L"--tag=";
         for (int i = 1; i != num_args; ++i) {
-          if (memcmp(arg_list[i], kTagSwitch, sizeof(kTagSwitch)) == 0)
+          if (memcmp(argv.get()[i], kTagSwitch, sizeof(kTagSwitch)) == 0)
             return true;
         }
         return false;
diff --git a/chrome/updater/win/task_scheduler.cc b/chrome/updater/win/task_scheduler.cc
index 3abd3860..2ac60e2 100644
--- a/chrome/updater/win/task_scheduler.cc
+++ b/chrome/updater/win/task_scheduler.cc
@@ -629,7 +629,7 @@
     // Quotes the command line before `put_Path`.
     hr = exec_action->put_Path(
         base::win::ScopedBstr(
-            base::CommandLine(run_command.GetProgram()).GetCommandLineString())
+            QuoteForCommandLineToArgvW(run_command.GetProgram().value()))
             .Get());
     if (FAILED(hr)) {
       PLOG(ERROR) << "Can't set path of exec action. " << std::hex << hr;
diff --git a/chrome/updater/win/task_scheduler_unittest.cc b/chrome/updater/win/task_scheduler_unittest.cc
index a967d9a..12edba7 100644
--- a/chrome/updater/win/task_scheduler_unittest.cc
+++ b/chrome/updater/win/task_scheduler_unittest.cc
@@ -300,7 +300,7 @@
 }
 
 TEST_F(TaskSchedulerTests, GetTaskInfoExecActions) {
-  base::CommandLine command_line1 = GetTestProcessCommandLine(GetTestScope());
+  base::CommandLine command_line1({L"c:\\test\\process 1.exe"});
 
   EXPECT_TRUE(task_scheduler_->RegisterTask(
       GetTestScope(), kTaskName1, kTaskDescription1, command_line1,
@@ -316,7 +316,7 @@
             info.exec_actions[0].application_path.value());
   EXPECT_EQ(command_line1.GetArgumentsString(), info.exec_actions[0].arguments);
 
-  base::CommandLine command_line2 = GetTestProcessCommandLine(GetTestScope());
+  base::CommandLine command_line2({L"c:\\test\\process2.exe"});
   command_line2.AppendSwitch(kUnitTestSwitch);
   EXPECT_TRUE(task_scheduler_->RegisterTask(
       GetTestScope(), kTaskName2, kTaskDescription2, command_line2,
@@ -327,7 +327,7 @@
   // the previous contents of the struct.
   EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
   ASSERT_EQ(1UL, info.exec_actions.size());
-  EXPECT_EQ(base::StrCat({L"\"", command_line2.GetProgram().value(), L"\""}),
+  EXPECT_EQ(command_line2.GetProgram().value(),
             info.exec_actions[0].application_path.value());
   EXPECT_EQ(command_line2.GetArgumentsString(), info.exec_actions[0].arguments);
 
diff --git a/chromecast/browser/cast_download_manager_delegate.cc b/chromecast/browser/cast_download_manager_delegate.cc
index a2ccc6e..2bc2a09 100644
--- a/chromecast/browser/cast_download_manager_delegate.cc
+++ b/chromecast/browser/cast_download_manager_delegate.cc
@@ -32,7 +32,7 @@
   std::move(*callback).Run(
       empty, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT,
-      download::DownloadItem::MixedContentStatus::UNKNOWN, empty, empty,
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN, empty, empty,
       std::string() /*mime_type*/,
       download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
   return true;
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 9a0dd0b..e60f38d 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15270.0.0
\ No newline at end of file
+15274.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/device_activity/device_active_use_case.cc b/chromeos/ash/components/device_activity/device_active_use_case.cc
index 2d14164..b24a2c5 100644
--- a/chromeos/ash/components/device_activity/device_active_use_case.cc
+++ b/chromeos/ash/components/device_activity/device_active_use_case.cc
@@ -168,10 +168,10 @@
   // Retrieve full hardware class from machine statistics object.
   // Default |full_hardware_class| to kHardwareClassKeyNotFound if retrieval
   // from machine statistics fails.
-  std::string full_hardware_class = kHardwareClassKeyNotFound;
-  statistics_provider_->GetMachineStatistic(chromeos::system::kHardwareClassKey,
-                                            &full_hardware_class);
-  return full_hardware_class;
+  const absl::optional<base::StringPiece> full_hardware_class =
+      statistics_provider_->GetMachineStatistic(
+          chromeos::system::kHardwareClassKey);
+  return std::string(full_hardware_class.value_or(kHardwareClassKeyNotFound));
 }
 
 std::string DeviceActiveUseCase::GetChromeOSVersion() const {
diff --git a/chromeos/ash/components/device_activity/device_activity_client_unittest.cc b/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
index 8ec32069..021cc3ce 100644
--- a/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
+++ b/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
@@ -488,7 +488,7 @@
        << "wifi_guid"
        << "\","
        << "  \"Type\": \"" << shill::kTypeWifi << "\","
-       << "  \"State\": \"" << shill::kStateOffline << "\""
+       << "  \"State\": \"" << shill::kStateIdle << "\""
        << "}";
 
     wifi_network_service_path_ =
@@ -719,7 +719,7 @@
 // so the client is expected to go back to |kIdle| state.
 TEST_F(DeviceActivityClientTest,
        FireTimerWithoutNetworkKeepsClientinIdleState) {
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
   FireTimer();
 
   EXPECT_EQ(device_activity_client_->GetState(),
@@ -747,7 +747,7 @@
   }
 
   // Reconnecting network connection triggers |TransitionOutOfIdle|.
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
   SetWifiNetworkState(shill::kStateOnline);
 
   // Check that no additional network requests are pending since all use cases
@@ -1003,7 +1003,7 @@
   EXPECT_EQ(device_activity_client_->GetState(),
             DeviceActivityClient::State::kIdle);
 
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
   FireTimer();
 
   // Verify that no network requests were sent.
@@ -1055,7 +1055,7 @@
   EXPECT_GT(test_url_loader_factory_.NumPending(), 0);
 
   // Disconnect network.
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
 
   // All pending requests should be cancelled, and our device activity client
   // should get set back to |kIdle|.
@@ -1080,7 +1080,7 @@
   EXPECT_NE(first_use_case->GetPsmRlweClient(), nullptr);
 
   // While waiting for OPRF request, simulate network disconnection.
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
 
   // Network offline should cancel all pending use cases, and clear the saved
   // state of the attempted pings.
@@ -1163,7 +1163,7 @@
   EXPECT_NE(first_use_case->GetPsmRlweClient(), nullptr);
 
   // While waiting for OPRF response, simulate network disconnection.
-  SetWifiNetworkState(shill::kStateOffline);
+  SetWifiNetworkState(shill::kStateIdle);
 
   // Network offline should cancel all pending use cases, and clear the saved
   // state of the attempted pings.
diff --git a/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc b/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
index 2e1c774..57855540 100644
--- a/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
+++ b/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
@@ -787,13 +787,13 @@
   // Set cellular networks to disconnected state.
   service_client_test()->SetServiceProperty(kTestPSimCellularServicePath,
                                             shill::kStateProperty,
-                                            base::Value(shill::kStateOffline));
+                                            base::Value(shill::kStateIdle));
   service_client_test()->SetServiceProperty(kTestESimCellularServicePath,
                                             shill::kStateProperty,
-                                            base::Value(shill::kStateOffline));
+                                            base::Value(shill::kStateIdle));
   service_client_test()->SetServiceProperty(kTestESimPolicyCellularServicePath,
                                             shill::kStateProperty,
-                                            base::Value(shill::kStateOffline));
+                                            base::Value(shill::kStateIdle));
   base::RunLoop().RunUntilIdle();
 
   // A connected to disconnected state change should not impact connection
@@ -966,7 +966,7 @@
 
   InitCellular();
   const base::Value kOnlineStateValue(shill::kStateOnline);
-  const base::Value kOfflineStateValue(shill::kStateOffline);
+  const base::Value kIdleStateValue(shill::kStateIdle);
   const base::Value kFailedToConnect(shill::kStateFailure);
 
   ON_CALL(*mock_managed_network_configuration_manager_, AllowCellularSimLock())
@@ -979,7 +979,7 @@
   service_client_test()->SetServiceProperty(
       kTestESimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   service_client_test()->SetServiceProperty(
-      kTestPSimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestPSimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   base::RunLoop().RunUntilIdle();
   histogram_tester_->ExpectTotalCount(
       CellularMetricsLogger::kRestrictedActiveNetworkSIMLockStatus, 1);
@@ -991,13 +991,13 @@
 
   // Unlocked ESIM connection -> PSIM connection -> PinLocked ESiM connection
   service_client_test()->SetServiceProperty(
-      kTestESimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestESimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   service_client_test()->SetServiceProperty(
       kTestPSimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   base::RunLoop().RunUntilIdle();
 
   service_client_test()->SetServiceProperty(
-      kTestPSimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestPSimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   SetCellularSimLock(shill::kSIMLockPin);
   service_client_test()->SetServiceProperty(
       kTestESimCellularServicePath, shill::kStateProperty, kFailedToConnect);
@@ -1008,13 +1008,13 @@
 
   // PinLocked ESIM -> PSIM connection -> PukBlocked ESIM
   service_client_test()->SetServiceProperty(
-      kTestESimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestESimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   service_client_test()->SetServiceProperty(
       kTestPSimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   base::RunLoop().RunUntilIdle();
 
   service_client_test()->SetServiceProperty(
-      kTestPSimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestPSimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   SetCellularSimLock(shill::kSIMLockPuk);
   service_client_test()->SetServiceProperty(
       kTestESimCellularServicePath, shill::kStateProperty, kFailedToConnect);
@@ -1029,7 +1029,7 @@
 
   InitCellular();
   const base::Value kOnlineStateValue(shill::kStateOnline);
-  const base::Value kOfflineStateValue(shill::kStateOffline);
+  const base::Value kIdleStateValue(shill::kStateIdle);
 
   ON_CALL(*mock_managed_network_configuration_manager_, AllowCellularSimLock())
       .WillByDefault(testing::Return(true));
@@ -1054,7 +1054,7 @@
   service_client_test()->SetServiceProperty(
       kTestESimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   service_client_test()->SetServiceProperty(
-      kTestPSimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestPSimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   base::RunLoop().RunUntilIdle();
   histogram_tester_->ExpectTotalCount(
       CellularMetricsLogger::kUnrestrictedActiveNetworkSIMLockStatus, 1);
@@ -1063,7 +1063,7 @@
 
   // ESIM connection -> PSIM connection
   service_client_test()->SetServiceProperty(
-      kTestESimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestESimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   service_client_test()->SetServiceProperty(
       kTestPSimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   base::RunLoop().RunUntilIdle();
@@ -1072,16 +1072,16 @@
 
   // PSIM connection -> No connection
   service_client_test()->SetServiceProperty(
-      kTestESimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestESimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   service_client_test()->SetServiceProperty(
-      kTestPSimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestPSimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   base::RunLoop().RunUntilIdle();
   histogram_tester_->ExpectTotalCount(
       CellularMetricsLogger::kUnrestrictedActiveNetworkSIMLockStatus, 2);
 
   // No connection -> PSIM connection
   service_client_test()->SetServiceProperty(
-      kTestESimCellularServicePath, shill::kStateProperty, kOfflineStateValue);
+      kTestESimCellularServicePath, shill::kStateProperty, kIdleStateValue);
   service_client_test()->SetServiceProperty(
       kTestPSimCellularServicePath, shill::kStateProperty, kOnlineStateValue);
   base::RunLoop().RunUntilIdle();
diff --git a/chromeos/ash/components/network/client_cert_resolver_unittest.cc b/chromeos/ash/components/network/client_cert_resolver_unittest.cc
index 5340d4c..3d65f0fa 100644
--- a/chromeos/ash/components/network/client_cert_resolver_unittest.cc
+++ b/chromeos/ash/components/network/client_cert_resolver_unittest.cc
@@ -650,7 +650,7 @@
   // notified its observers with |network_properties_changed| = true.
   network_properties_changed_count_ = 0;
   test_clock_->SetNow(base::Time::Max());
-  SetWifiState(shill::kStateOffline);
+  SetWifiState(shill::kStateIdle);
   task_environment_.RunUntilIdle();
   GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
   EXPECT_EQ(std::string(), pkcs11_id);
@@ -687,7 +687,7 @@
   // certificate doesn't change and ClientCertResolver does not notify its
   // observers with |network_properties_changed| = true.
   network_properties_changed_count_ = 0;
-  SetWifiState(shill::kStateOffline);
+  SetWifiState(shill::kStateIdle);
   task_environment_.RunUntilIdle();
   GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
   EXPECT_EQ(test_cert_id_, pkcs11_id);
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index 90841fc..a02d0f31 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -392,9 +392,6 @@
          connection_state_ == shill::kStateNoConnectivity ||
          connection_state_ == shill::kStateRedirectFound ||
          connection_state_ == shill::kStatePortalSuspected ||
-         // TODO(https://crbug.com/552190): Remove kStateOffline from this list
-         // when occurrences in chromium code have been eliminated.
-         connection_state_ == shill::kStateOffline ||
          connection_state_ == shill::kStateOnline ||
          connection_state_ == shill::kStateFailure ||
          connection_state_ == shill::kStateDisconnect ||
diff --git a/chromeos/ash/components/phonehub/app_stream_launcher_data_model_unittest.cc b/chromeos/ash/components/phonehub/app_stream_launcher_data_model_unittest.cc
index b68728a..155b411 100644
--- a/chromeos/ash/components/phonehub/app_stream_launcher_data_model_unittest.cc
+++ b/chromeos/ash/components/phonehub/app_stream_launcher_data_model_unittest.cc
@@ -109,9 +109,11 @@
 TEST_F(AppStreamLauncherDataModelTest, SetAppsList) {
   std::vector<Notification::AppMetadata> apps_list;
   apps_list.emplace_back(Notification::AppMetadata(
-      u"b_app", "com.fakeapp1", gfx::Image(), absl::nullopt, true, 1));
+      u"b_app", "com.fakeapp1", gfx::Image(), absl::nullopt, true, 1,
+      proto::AppStreamabilityStatus::STREAMABLE));
   apps_list.emplace_back(Notification::AppMetadata(
-      u"a_app", "com.fakeapp2", gfx::Image(), absl::nullopt, true, 1));
+      u"a_app", "com.fakeapp2", gfx::Image(), absl::nullopt, true, 1,
+      proto::AppStreamabilityStatus::STREAMABLE));
   SetAppList(apps_list);
   EXPECT_TRUE(IsObserverAppListChanged());
   EXPECT_EQ(GetAppsList()->size(), 2u);
@@ -122,4 +124,4 @@
   EXPECT_EQ(GetAppsListSortedByName()->at(1).visible_app_name, u"b_app");
 }
 }  // namespace phonehub
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/chromeos/ash/components/phonehub/notification.cc b/chromeos/ash/components/phonehub/notification.cc
index b26f21b5..86baf4a75 100644
--- a/chromeos/ash/components/phonehub/notification.cc
+++ b/chromeos/ash/components/phonehub/notification.cc
@@ -19,24 +19,28 @@
 const char kVisibleAppName[] = "visible_app_name";
 const char kPackageName[] = "package_name";
 const char kUserId[] = "user_id";
+const char kAppStreamabilityStatus[] = "app_streamability_status";
 const char kIcon[] = "icon";
 const char kIconColorR[] = "icon_color_r";
 const char kIconColorG[] = "icon_color_g";
 const char kIconColorB[] = "icon_color_b";
 const char kIconIsMonochrome[] = "icon_is_monochrome";
 
-Notification::AppMetadata::AppMetadata(const std::u16string& visible_app_name,
-                                       const std::string& package_name,
-                                       const gfx::Image& icon,
-                                       const absl::optional<SkColor> icon_color,
-                                       bool icon_is_monochrome,
-                                       int64_t user_id)
+Notification::AppMetadata::AppMetadata(
+    const std::u16string& visible_app_name,
+    const std::string& package_name,
+    const gfx::Image& icon,
+    const absl::optional<SkColor> icon_color,
+    bool icon_is_monochrome,
+    int64_t user_id,
+    proto::AppStreamabilityStatus app_streamability_status)
     : visible_app_name(visible_app_name),
       package_name(package_name),
       icon(icon),
       icon_color(icon_color),
       icon_is_monochrome(icon_is_monochrome),
-      user_id(user_id) {}
+      user_id(user_id),
+      app_streamability_status(app_streamability_status) {}
 
 Notification::AppMetadata::AppMetadata(const AppMetadata& other) = default;
 
@@ -67,6 +71,8 @@
     val.SetIntKey(kIconColorG, SkColorGetG(*icon_color));
     val.SetIntKey(kIconColorB, SkColorGetB(*icon_color));
   }
+  val.SetIntKey(kAppStreamabilityStatus,
+                static_cast<int>(app_streamability_status));
   return val;
 }
 
@@ -113,10 +119,14 @@
   gfx::Image decode_icon = gfx::Image::CreateFrom1xPNGBytes(
       base::MakeRefCounted<base::RefCountedString>(std::move(icon_str)));
 
-  return Notification::AppMetadata(visible_app_name_string_value,
-                                   *(value.FindStringPath(kPackageName)),
-                                   decode_icon, icon_color, icon_is_monochrome,
-                                   *(value.FindDoublePath(kUserId)));
+  return Notification::AppMetadata(
+      visible_app_name_string_value, *(value.FindStringPath(kPackageName)),
+      decode_icon, icon_color, icon_is_monochrome,
+      *(value.FindDoublePath(kUserId)),
+      static_cast<proto::AppStreamabilityStatus>(
+          value.FindIntPath(kAppStreamabilityStatus)
+              .value_or(static_cast<int>(
+                  proto::AppStreamabilityStatus::STREAMABLE))));
 }
 
 Notification::Notification(
diff --git a/chromeos/ash/components/phonehub/notification.h b/chromeos/ash/components/phonehub/notification.h
index 85378d3..e0958d5 100644
--- a/chromeos/ash/components/phonehub/notification.h
+++ b/chromeos/ash/components/phonehub/notification.h
@@ -13,6 +13,7 @@
 #include "base/containers/flat_map.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/image/image.h"
 
@@ -34,7 +35,9 @@
                 const gfx::Image& icon,
                 const absl::optional<SkColor> icon_color,
                 bool icon_is_monochrome,
-                int64_t user_id);
+                int64_t user_id,
+                proto::AppStreamabilityStatus app_streamability_status =
+                    proto::AppStreamabilityStatus::STREAMABLE);
     AppMetadata(const AppMetadata& other);
     AppMetadata& operator=(const AppMetadata& other);
 
@@ -52,6 +55,7 @@
     // Whether the icon image is just a mask used to generate a monochrome icon.
     bool icon_is_monochrome;
     int64_t user_id;
+    proto::AppStreamabilityStatus app_streamability_status;
   };
 
   // Interaction behavior for integration with other features.
diff --git a/chromeos/ash/components/phonehub/notification_interaction_handler_impl_unittest.cc b/chromeos/ash/components/phonehub/notification_interaction_handler_impl_unittest.cc
index 613c206..01e138f3 100644
--- a/chromeos/ash/components/phonehub/notification_interaction_handler_impl_unittest.cc
+++ b/chromeos/ash/components/phonehub/notification_interaction_handler_impl_unittest.cc
@@ -97,7 +97,7 @@
   auto expected_app_metadata = Notification::AppMetadata(
       expected_app_visible_name, expected_package_name, gfx::Image(),
       /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-      expected_user_id);
+      expected_user_id, proto::AppStreamabilityStatus::STREAMABLE);
 
   handler().HandleNotificationClicked(expected_id, expected_app_metadata);
 
diff --git a/chromeos/ash/components/phonehub/notification_manager_impl_unittest.cc b/chromeos/ash/components/phonehub/notification_manager_impl_unittest.cc
index 0a05610..f134d7d 100644
--- a/chromeos/ash/components/phonehub/notification_manager_impl_unittest.cc
+++ b/chromeos/ash/components/phonehub/notification_manager_impl_unittest.cc
@@ -34,10 +34,12 @@
 Notification CreateNotification(int64_t id) {
   return phonehub::Notification(
       id,
-      phonehub::Notification::AppMetadata(kAppName, kPackageName,
-                                          /*icon=*/gfx::Image(),
-                                          /*icon_color=*/absl::nullopt,
-                                          /*icon_is_monochrome=*/true, kUserId),
+      phonehub::Notification::AppMetadata(
+          kAppName, kPackageName,
+          /*icon=*/gfx::Image(),
+          /*icon_color=*/absl::nullopt,
+          /*icon_is_monochrome=*/true, kUserId,
+          proto::AppStreamabilityStatus::STREAMABLE),
       base::Time::Now(), Notification::Importance::kDefault,
       Notification::Category::kConversation,
       {{Notification::ActionType::kInlineReply, /*action_id=*/0}},
diff --git a/chromeos/ash/components/phonehub/notification_processor.cc b/chromeos/ash/components/phonehub/notification_processor.cc
index 2ad3014..3f8607fb 100644
--- a/chromeos/ash/components/phonehub/notification_processor.cc
+++ b/chromeos/ash/components/phonehub/notification_processor.cc
@@ -153,7 +153,8 @@
                       Notification::AppMetadata(
                           base::UTF8ToUTF16(proto.origin_app().visible_name()),
                           proto.origin_app().package_name(), icon, icon_color,
-                          icon_is_monochrome, proto.origin_app().user_id()),
+                          icon_is_monochrome, proto.origin_app().user_id(),
+                          proto.origin_app().app_streamability_status()),
                       base::Time::FromJsTime(proto.epoch_time_millis()),
                       GetNotificationImportanceFromProto(proto.importance()),
                       category, action_id_map, behavior, title, text_content,
diff --git a/chromeos/ash/components/phonehub/notification_processor_unittest.cc b/chromeos/ash/components/phonehub/notification_processor_unittest.cc
index dc9f214..12d4d06 100644
--- a/chromeos/ash/components/phonehub/notification_processor_unittest.cc
+++ b/chromeos/ash/components/phonehub/notification_processor_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chromeos/ash/components/phonehub/notification_processor.h"
+#include <string>
 
 #include "ash/constants/ash_features.h"
 #include "base/memory/ptr_util.h"
@@ -152,9 +153,12 @@
       std::string shared_image = std::string(),
       std::string contact_image = std::string(),
       Notification::InteractionBehavior behavior =
-          Notification::InteractionBehavior::kNone) {
+          Notification::InteractionBehavior::kNone,
+      proto::AppStreamabilityStatus app_streamability_status =
+          proto::AppStreamabilityStatus::STREAMABLE) {
     auto origin_app = std::make_unique<proto::App>();
     origin_app->set_icon(icon);
+    origin_app->set_app_streamability_status(app_streamability_status);
 
     proto::Notification notification;
     notification.set_id(notification_id);
@@ -430,6 +434,22 @@
       gfx::test::AreImagesEqual(*notification->contact_image(), TestImage()));
 }
 
+TEST_F(NotificationProcessorTest, StreamabilityStatus) {
+  std::vector<proto::Notification> first_set_of_notifications;
+
+  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
+      kNotificationIdA, kInlineReplyIdA, kIconDataA, std::string(),
+      std::string(), Notification::InteractionBehavior::kNone,
+      proto::AppStreamabilityStatus::BLOCK_LISTED));
+  notification_processor()->AddNotifications(first_set_of_notifications);
+  image_decoder_delegate()->RunAllCallbacks();
+
+  const Notification* notification =
+      fake_notification_manager()->GetNotification(kNotificationIdA);
+  EXPECT_EQ(proto::AppStreamabilityStatus::BLOCK_LISTED,
+            notification->app_metadata().app_streamability_status);
+}
+
 TEST_F(NotificationProcessorTest, AddRemoveClearWithoutRace) {
   // Add 2 notifications with all images populated.
   std::vector<proto::Notification> first_set_of_notifications;
diff --git a/chromeos/ash/components/phonehub/phone_model_test_util.cc b/chromeos/ash/components/phonehub/phone_model_test_util.cc
index fcefa63..4ba651e 100644
--- a/chromeos/ash/components/phonehub/phone_model_test_util.cc
+++ b/chromeos/ash/components/phonehub/phone_model_test_util.cc
@@ -79,8 +79,13 @@
 
 const Notification::AppMetadata& CreateFakeAppMetadata() {
   static const base::NoDestructor<Notification::AppMetadata> fake_app_metadata{
-      kFakeAppVisibleName,          kFakeAppPackageName,         gfx::Image(),
-      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, kUserId};
+      kFakeAppVisibleName,
+      kFakeAppPackageName,
+      gfx::Image(),
+      /*icon_color=*/absl::nullopt,
+      /*icon_is_monochrome=*/true,
+      kUserId,
+      phonehub::proto::AppStreamabilityStatus::STREAMABLE};
   return *fake_app_metadata;
 }
 
diff --git a/chromeos/ash/components/phonehub/phone_status_processor.cc b/chromeos/ash/components/phonehub/phone_status_processor.cc
index 5b7f694..9da318c7 100644
--- a/chromeos/ash/components/phonehub/phone_status_processor.cc
+++ b/chromeos/ash/components/phonehub/phone_status_processor.cc
@@ -449,7 +449,7 @@
         absl::nullopt,
         app.icon_styling() ==
             proto::NotificationIconStyling::ICON_STYLE_MONOCHROME_SMALL_ICON,
-        app.user_id()));
+        app.user_id(), app.app_streamability_status()));
     std::string key = app.package_name() + base::NumberToString(app.user_id());
     decoding_data_list->emplace_back(
         IconDecoder::DecodingData(str_hash(key), app.icon()));
diff --git a/chromeos/ash/components/phonehub/proto/phonehub_api.proto b/chromeos/ash/components/phonehub/proto/phonehub_api.proto
index 1bcaf9db..78242dd 100644
--- a/chromeos/ash/components/phonehub/proto/phonehub_api.proto
+++ b/chromeos/ash/components/phonehub/proto/phonehub_api.proto
@@ -63,6 +63,12 @@
   ICON_STYLE_MONOCHROME_SMALL_ICON = 1;
 }
 
+enum AppStreamabilityStatus {
+  STREAMABLE = 0;
+  BLOCK_LISTED = 1;
+  BLOCKED_BY_APP = 2;
+}
+
 enum ChargingState {
   NOT_CHARGING = 0;
   CHARGING_AC = 1;
@@ -245,6 +251,8 @@
   // Optionally used only when icon_styling is ICON_STYLE_MONOCHROME_SMALL_ICON.
   // When unset, the ChromeOS side uses the system theme's default color.
   ColorRgb icon_color = 6;
+
+  AppStreamabilityStatus app_streamability_status = 7;
 }
 
 message CrosState {
diff --git a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
index c2cda260..f3302ae1 100644
--- a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
+++ b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
@@ -85,7 +85,7 @@
     auto app_metadata1 = Notification::AppMetadata(
         app_visible_name1, package_name1, gfx::Image(),
         /*icon_color=*/kIconColor, /*icon_is_monochrome=*/true,
-        expected_user_id1);
+        expected_user_id1, proto::AppStreamabilityStatus::STREAMABLE);
 
     const char16_t app_visible_name2[] = u"Fake App2";
     const char package_name2[] = "com.fakeapp2";
@@ -93,7 +93,7 @@
     auto app_metadata2 = Notification::AppMetadata(
         app_visible_name2, package_name2, gfx::Image(),
         /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/false,
-        expected_user_id2);
+        expected_user_id2, proto::AppStreamabilityStatus::STREAMABLE);
 
     base::Value::List app_metadata_value_list;
     app_metadata_value_list.Append(app_metadata1.ToValue());
@@ -111,7 +111,7 @@
         Notification::AppMetadata(
             app_visible_name1, package_name1, gfx::Image(),
             /*icon_color=*/kIconColor, /*icon_is_monochrome=*/false,
-            expected_user_id1)
+            expected_user_id1, proto::AppStreamabilityStatus::STREAMABLE)
             .ToValue();
 
     // Simulate an un-migrated preference without new fields.
@@ -201,14 +201,14 @@
     auto app_metadata1 = Notification::AppMetadata(
         app_visible_name1, package_name1, gfx::Image(),
         /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-        expected_user_id1);
+        expected_user_id1, proto::AppStreamabilityStatus::STREAMABLE);
     const char16_t app_visible_name2[] = u"Fake App2";
     const char package_name2[] = "com.fakeapp2";
     const int64_t expected_user_id2 = 2;
     auto app_metadata2 = Notification::AppMetadata(
         app_visible_name2, package_name2, gfx::Image(),
         /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-        expected_user_id2);
+        expected_user_id2, proto::AppStreamabilityStatus::STREAMABLE);
     handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
     handler().NotifyRecentAppAddedOrUpdated(app_metadata2, now);
   }
@@ -221,13 +221,13 @@
     auto app_metadata1 = Notification::AppMetadata(
         app_visible_name1, package_name1, gfx::Image(),
         /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-        expected_user_id);
+        expected_user_id, proto::AppStreamabilityStatus::STREAMABLE);
     const char16_t app_visible_name2[] = u"Fake App2";
     const char package_name2[] = "com.fakeapp2";
     auto app_metadata2 = Notification::AppMetadata(
         app_visible_name2, package_name2, gfx::Image(),
         /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-        expected_user_id);
+        expected_user_id, proto::AppStreamabilityStatus::STREAMABLE);
     handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
     handler().NotifyRecentAppAddedOrUpdated(app_metadata2, now);
   }
@@ -250,7 +250,7 @@
   auto expected_app_metadata = Notification::AppMetadata(
       expected_app_visible_name, expected_package_name, gfx::Image(),
       /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true,
-      expected_user_id);
+      expected_user_id, proto::AppStreamabilityStatus::STREAMABLE);
 
   handler().NotifyRecentAppClicked(
       expected_app_metadata,
@@ -266,7 +266,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const char16_t app_visible_name2[] = u"Fake App2";
   const char package_name2[] = "com.fakeapp2";
@@ -274,7 +275,8 @@
   auto app_metadata2 =
       Notification::AppMetadata(app_visible_name2, package_name2, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id2);
+                                /*icon_is_monochrome=*/true, expected_user_id2,
+                                proto::AppStreamabilityStatus::STREAMABLE);
   const base::Time now = base::Time::Now();
 
   handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
@@ -299,11 +301,13 @@
   streamable_apps.emplace_back(
       Notification::AppMetadata(u"App1", "com.fakeapp1", gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, 1));
+                                /*icon_is_monochrome=*/true, 1,
+                                proto::AppStreamabilityStatus::STREAMABLE));
   streamable_apps.emplace_back(
       Notification::AppMetadata(u"App2", "com.fakeapp2", gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, 1));
+                                /*icon_is_monochrome=*/true, 1,
+                                proto::AppStreamabilityStatus::STREAMABLE));
 
   handler().SetStreamableApps(streamable_apps);
 
@@ -324,11 +328,13 @@
   streamable_apps.emplace_back(
       Notification::AppMetadata(u"App1", "com.fakeapp1", gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, 1));
+                                /*icon_is_monochrome=*/true, 1,
+                                proto::AppStreamabilityStatus::STREAMABLE));
   streamable_apps.emplace_back(
       Notification::AppMetadata(u"App2", "com.fakeapp2", gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, 1));
+                                /*icon_is_monochrome=*/true, 1,
+                                proto::AppStreamabilityStatus::STREAMABLE));
 
   handler().SetStreamableApps(streamable_apps);
 
@@ -346,7 +352,8 @@
   streamable_apps2.emplace_back(
       Notification::AppMetadata(u"App3", "com.fakeapp3", gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, 1));
+                                /*icon_is_monochrome=*/true, 1,
+                                proto::AppStreamabilityStatus::STREAMABLE));
 
   handler().SetStreamableApps(streamable_apps2);
 
@@ -372,7 +379,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const char16_t app_visible_name2[] = u"Fake App2";
   const char package_name2[] = "com.fakeapp2";
@@ -380,7 +388,8 @@
   auto app_metadata2 =
       Notification::AppMetadata(app_visible_name2, package_name2, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id2);
+                                /*icon_is_monochrome=*/true, expected_user_id2,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const char16_t app_visible_name3[] = u"Fake App3";
   const char package_name3[] = "com.fakeapp3";
@@ -388,7 +397,8 @@
   auto app_metadata3 =
       Notification::AppMetadata(app_visible_name3, package_name3, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id3);
+                                /*icon_is_monochrome=*/true, expected_user_id3,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const base::Time now = base::Time::Now();
   const base::Time next_minute = base::Time::Now() + base::Minutes(1);
@@ -416,7 +426,8 @@
   auto app_metadata4 =
       Notification::AppMetadata(app_visible_name4, package_name4, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id4);
+                                /*icon_is_monochrome=*/true, expected_user_id4,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const char16_t app_visible_name5[] = u"Fake App5";
   const char package_name5[] = "com.fakeapp5";
@@ -424,7 +435,8 @@
   auto app_metadata5 =
       Notification::AppMetadata(app_visible_name5, package_name5, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id5);
+                                /*icon_is_monochrome=*/true, expected_user_id5,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const char16_t app_visible_name6[] = u"Fake App6";
   const char package_name6[] = "com.fakeapp6";
@@ -432,7 +444,8 @@
   auto app_metadata6 =
       Notification::AppMetadata(app_visible_name6, package_name6, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id6);
+                                /*icon_is_monochrome=*/true, expected_user_id6,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   const base::Time next_two_hour = base::Time::Now() + base::Hours(2);
   const base::Time next_three_hour = base::Time::Now() + base::Hours(3);
@@ -516,7 +529,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
   SetEcheFeatureState(FeatureState::kDisabledByUser);
@@ -583,7 +597,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
   SetAppsAccessStatus(true);
   handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
   SetEcheFeatureState(FeatureState::kEnabledByUser);
@@ -601,7 +616,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
 
   SetAppsAccessStatus(true);
   handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
@@ -648,7 +664,8 @@
   auto app_metadata1 =
       Notification::AppMetadata(app_visible_name1, package_name1, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id1);
+                                /*icon_is_monochrome=*/true, expected_user_id1,
+                                proto::AppStreamabilityStatus::STREAMABLE);
   handler().NotifyRecentAppAddedOrUpdated(app_metadata1, now);
 
   EXPECT_EQ(RecentAppsInteractionHandler::RecentAppsUiState::ITEMS_VISIBLE,
@@ -715,7 +732,8 @@
   auto app_metadata =
       Notification::AppMetadata(app_visible_name, package_name, gfx::Image(),
                                 /*icon_color=*/absl::nullopt,
-                                /*icon_is_monochrome=*/true, expected_user_id);
+                                /*icon_is_monochrome=*/true, expected_user_id,
+                                proto::AppStreamabilityStatus::STREAMABLE);
   handler().NotifyRecentAppAddedOrUpdated(app_metadata, now);
   SetHostStatus(HostStatus::kHostSetButNotYetVerified);
 
diff --git a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
index d963b1f..eb4e729 100644
--- a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
+++ b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
@@ -796,6 +796,12 @@
   std::move(callback).Run(run_routine_response_.Clone());
 }
 
+void FakeCrosHealthd::RunEmmcLifetimeRoutine(
+    RunEmmcLifetimeRoutineCallback callback) {
+  last_run_routine_ = mojom::DiagnosticRoutineEnum::kEmmcLifetime;
+  std::move(callback).Run(run_routine_response_.Clone());
+}
+
 void FakeCrosHealthd::AddBluetoothObserver(
     mojo::PendingRemote<mojom::CrosHealthdBluetoothObserver> observer) {
   bluetooth_observers_.Add(std::move(observer));
diff --git a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
index fb3c020..849fd0945 100644
--- a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
+++ b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
@@ -333,6 +333,7 @@
       mojom::LedColor color,
       mojo::PendingRemote<mojom::LedLitUpRoutineReplier> replier,
       RunLedLitUpRoutineCallback callback) override;
+  void RunEmmcLifetimeRoutine(RunEmmcLifetimeRoutineCallback callback) override;
 
   // CrosHealthdEventService overrides:
   void AddBluetoothObserver(
diff --git a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
index 6ac9255..b0a033a0 100644
--- a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
+++ b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
@@ -54,7 +54,7 @@
 
 // Diagnostics interface exposed by the cros_healthd daemon.
 //
-// NextMinVersion: 7, NextIndex: 38
+// NextMinVersion: 8, NextIndex: 39
 [Stable]
 interface CrosHealthdDiagnosticsService {
   // Returns an array of all diagnostic routines that the platform supports.
@@ -613,6 +613,19 @@
   [MinVersion=5] RunLedLitUpRoutine@37(LedName name, LedColor color,
     pending_remote<LedLitUpRoutineReplier> replier)
         => (RunRoutineResponse response);
+
+  // Requests that the EmmcLifetime routine is created and started on the
+  // platform. This routine checks the lifetime of the eMMC drive. The routine
+  // will pass if PRE_EOL_INFO is 0x01 (normal). In addition, the value of
+  // DEVICE_LIFE_TIME_EST_TYP_A and DEVICE_LIFE_TIME_EST_TYP_B will be
+  // included in the output.
+  // The availability of this routine can be determined by checking that
+  // kEmmcLifetime is returned by GetAvailableRoutines.
+  //
+  // The response:
+  // * |response| - contains a unique identifier and status for the created
+  //                routine.
+  [MinVersion=7] RunEmmcLifetimeRoutine@38() => (RunRoutineResponse response);
 };
 
 // Event interface exposed by the cros_healthd daemon.
diff --git a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
index 20b073d..261d11b 100644
--- a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
+++ b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
@@ -15,7 +15,7 @@
 
 // Enumeration of each of the diagnostics routines the platform may support.
 //
-// NextMinVersion: 6, NextIndex: 37
+// NextMinVersion: 7, NextIndex: 38
 [Stable, Extensible]
 enum DiagnosticRoutineEnum {
   [Default] kUnknown = 30,
@@ -55,6 +55,7 @@
   [MinVersion=3] kPrivacyScreen = 34,
   [MinVersion=4] kLedLitUp = 35,
   [MinVersion=5] kSmartctlCheckWithPercentageUsed = 36,
+  [MinVersion=6] kEmmcLifetime = 37
 };
 
 // Enumeration of the possible DiskRead routine's command type
diff --git a/chromeos/crosapi/mojom/download_controller.mojom b/chromeos/crosapi/mojom/download_controller.mojom
index b01def0..f8f4d32 100644
--- a/chromeos/crosapi/mojom/download_controller.mojom
+++ b/chromeos/crosapi/mojom/download_controller.mojom
@@ -45,10 +45,10 @@
   kDownloadDangerTypeDangerousAccountCompromise = 19,
 };
 
-// This mirrors `download::DownloadItem::MixedContentStatus`, anything added or
-// removed here must also be added or removed there.
-[Stable, Extensible]
-enum DownloadMixedContentStatus {
+// This mirrors `download::DownloadItem::InsecureDownloadStatus`, anything
+// added or removed here must also be added or removed there.
+[Stable, Extensible, RenamedFrom="crosapi.mojom.DownloadMixedContentStatus"]
+enum InsecureDownloadStatus {
   [Default] kInvalid = -1,
   kUnknown = 0,
   kSafe = 1,
@@ -85,10 +85,10 @@
   [MinVersion=2] mojo_base.mojom.Time? start_time@13;
   [MinVersion=3] bool has_is_dangerous@14;
   [MinVersion=3] bool is_dangerous@15;
-  [MinVersion=3] bool has_is_mixed_content@16;
-  [MinVersion=3] bool is_mixed_content@17;
+  [MinVersion=3] bool has_is_insecure@16;
+  [MinVersion=3] bool is_insecure@17;
   [MinVersion=4] DownloadDangerType danger_type@18;
-  [MinVersion=4] DownloadMixedContentStatus mixed_content_status@19;
+  [MinVersion=4] InsecureDownloadStatus insecure_download_status@19;
 };
 
 // A client implemented in lacros-chrome for the DownloadController which is
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 378cc47e..d533244 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-109-5414.21-1670240880-benchmark-109.0.5414.37-r1.orderfile.xz
+chromeos-chrome-orderfile-field-110-5414.7-1669632342-benchmark-110.0.5447.0-r1.orderfile.xz
diff --git a/chromeos/services/machine_learning/public/mojom/text_suggester.mojom b/chromeos/services/machine_learning/public/mojom/text_suggester.mojom
index 6b5f2e224..6099b75 100644
--- a/chromeos/services/machine_learning/public/mojom/text_suggester.mojom
+++ b/chromeos/services/machine_learning/public/mojom/text_suggester.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 3
+// Next MinVersion: 4
 
 // Datatypes and interfaces of text suggester API.
 
@@ -102,8 +102,9 @@
   array<TextSuggestionCandidate> candidates@1;
 };
 
-// Experiment groups for multi word suggestions
-// Next value: 5
+// Experiment groups for multi word suggestions. See the following go link for
+// more details on the experiments: go/cros-text-suggester-experiment-defs
+// Next value: 8
 [Stable, Extensible]
 enum MultiWordExperimentGroup {
   [Default] kDefault = 0,
@@ -111,7 +112,10 @@
   // Experiment groups used for the relaxed gboard settings finch experiment
   [MinVersion=2] kGboardRelaxedA = 2,
   [MinVersion=2] kGboardRelaxedB = 3,
-  [MinVersion=2] kGboardRelaxedC = 4
+  [MinVersion=2] kGboardRelaxedC = 4,
+  [MinVersion=3] kGboardD = 5,
+  [MinVersion=3] kGboardE = 6,
+  [MinVersion=3] kGboardF = 7,
 };
 
 // Encapsulates any settings details for a TextSuggester
diff --git a/chromeos/services/network_health/network_health_service_unittest.cc b/chromeos/services/network_health/network_health_service_unittest.cc
index 61f4c9b..7544323 100644
--- a/chromeos/services/network_health/network_health_service_unittest.cc
+++ b/chromeos/services/network_health/network_health_service_unittest.cc
@@ -326,7 +326,7 @@
         .service_test()
         ->AddService(kWifiServicePath + idx, kWifiGuid + idx,
                      kWifiServiceName + idx, shill::kTypeWifi,
-                     shill::kStateOffline, true);
+                     shill::kStateIdle, true);
   }
 
   ValidateNetworkState(network_config::mojom::NetworkType::kWiFi,
@@ -380,8 +380,7 @@
 
   // Change the connection state of the service.
   cros_network_config_test_helper_.network_state_helper().SetServiceProperty(
-      kWifiServicePath, shill::kStateProperty,
-      base::Value(shill::kStateOffline));
+      kWifiServicePath, shill::kStateProperty, base::Value(shill::kStateIdle));
   // Wait until the connection state change event has been fired.
   task_environment_.RunUntilIdle();
 
@@ -549,7 +548,7 @@
   cros_network_config_test_helper_.network_state_helper()
       .service_test()
       ->AddService(kOtherWifiServicePath, kOtherWifiGuid, kOtherWifiServiceName,
-                   shill::kTypeWifi, shill::kStateOffline, true);
+                   shill::kTypeWifi, shill::kStateIdle, true);
 
   // Set five signal strength samples for the original network.
   for (int i = 0; i < 5; i++) {
@@ -558,8 +557,7 @@
 
   // Swap the active WiFi networks.
   cros_network_config_test_helper_.network_state_helper().SetServiceProperty(
-      kWifiServicePath, shill::kStateProperty,
-      base::Value(shill::kStateOffline));
+      kWifiServicePath, shill::kStateProperty, base::Value(shill::kStateIdle));
   cros_network_config_test_helper_.network_state_helper().SetServiceProperty(
       kOtherWifiServicePath, shill::kStateProperty,
       base::Value(shill::kStateOnline));
@@ -596,8 +594,7 @@
   ASSERT_EQ(2u, network_health_.GetTrackedGuidsForTest().size());
 
   cros_network_config_test_helper_.network_state_helper().SetServiceProperty(
-      kWifiServicePath, shill::kStateProperty,
-      base::Value(shill::kStateOffline));
+      kWifiServicePath, shill::kStateProperty, base::Value(shill::kStateIdle));
 
   task_environment_.FastForwardBy(base::Hours(1));
 
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index 505dc40..46c7b7d8 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -900,7 +900,7 @@
     return absl::nullopt;
 
   bool found_existing_iban = base::ranges::any_of(
-      personal_data_manager_->GetIBANs(), [&](const auto& iban) {
+      personal_data_manager_->GetLocalIBANs(), [&](const auto& iban) {
         return iban->value() == candidate_iban.value();
       });
 
diff --git a/components/autofill/core/browser/form_data_importer_unittest.cc b/components/autofill/core/browser/form_data_importer_unittest.cc
index 0250fbc5..6ce30a7 100644
--- a/components/autofill/core/browser/form_data_importer_unittest.cc
+++ b/components/autofill/core/browser/form_data_importer_unittest.cc
@@ -3333,7 +3333,7 @@
 
   WaitForOnPersonalDataChanged();
 
-  const std::vector<IBAN*>& results = personal_data_manager_->GetIBANs();
+  const std::vector<IBAN*>& results = personal_data_manager_->GetLocalIBANs();
   ASSERT_EQ(1U, results.size());
   EXPECT_THAT(*results[0], ComparesEqual(iban));
 
@@ -3365,7 +3365,7 @@
 
   WaitForOnPersonalDataChanged();
 
-  const std::vector<IBAN*>& results = personal_data_manager_->GetIBANs();
+  const std::vector<IBAN*>& results = personal_data_manager_->GetLocalIBANs();
   ASSERT_EQ(1U, results.size());
   EXPECT_THAT(*results[0], ComparesEqual(iban));
 
diff --git a/components/autofill/core/browser/iban_manager.cc b/components/autofill/core/browser/iban_manager.cc
index e4ac5cc5..03206ae 100644
--- a/components/autofill/core/browser/iban_manager.cc
+++ b/components/autofill/core/browser/iban_manager.cc
@@ -27,7 +27,7 @@
     base::WeakPtr<SuggestionsHandler> handler,
     const SuggestionsContext& context) {
   if (!is_off_the_record_ && personal_data_manager_) {
-    std::vector<IBAN*> ibans = personal_data_manager_->GetIBANs();
+    std::vector<IBAN*> ibans = personal_data_manager_->GetLocalIBANs();
     if (!ibans.empty()) {
       // Rank the IBANs by ranking score (see AutoFillDataModel for details).
       base::Time comparison_time = AutofillClock::Now();
diff --git a/components/autofill/core/browser/payments/iban_save_manager_unittest.cc b/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
index 7ab5553..fe8fc06 100644
--- a/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
@@ -92,7 +92,7 @@
   OnUserDidDecideOnLocalSave(
       AutofillClient::SaveIBANOfferUserDecision::kAccepted,
       u"  My teacher's IBAN ");
-  const std::vector<IBAN*> ibans = personal_data().GetIBANs();
+  const std::vector<IBAN*> ibans = personal_data().GetLocalIBANs();
 
   // Verify IBAN has been successfully updated with the new nickname on accept.
   EXPECT_EQ(ibans.size(), 1U);
@@ -106,13 +106,13 @@
   iban.set_value(value);
 
   EXPECT_TRUE(iban_save_manager_->AttemptToOfferIBANLocalSave(iban));
-  EXPECT_TRUE(personal_data().GetIBANs().empty());
+  EXPECT_TRUE(personal_data().GetLocalIBANs().empty());
 
   OnUserDidDecideOnLocalSave(
       AutofillClient::SaveIBANOfferUserDecision::kDeclined);
-  const std::vector<IBAN*> ibans = personal_data().GetIBANs();
+  const std::vector<IBAN*> ibans = personal_data().GetLocalIBANs();
 
-  EXPECT_TRUE(personal_data().GetIBANs().empty());
+  EXPECT_TRUE(personal_data().GetLocalIBANs().empty());
 }
 
 TEST_F(IBANSaveManagerTest, OnUserDidDecideOnLocalSave_Ignored) {
@@ -121,13 +121,13 @@
   iban.set_value(value);
 
   EXPECT_TRUE(iban_save_manager_->AttemptToOfferIBANLocalSave(iban));
-  EXPECT_TRUE(personal_data().GetIBANs().empty());
+  EXPECT_TRUE(personal_data().GetLocalIBANs().empty());
 
   OnUserDidDecideOnLocalSave(
       AutofillClient::SaveIBANOfferUserDecision::kIgnored);
-  const std::vector<IBAN*> ibans = personal_data().GetIBANs();
+  const std::vector<IBAN*> ibans = personal_data().GetLocalIBANs();
 
-  EXPECT_TRUE(personal_data().GetIBANs().empty());
+  EXPECT_TRUE(personal_data().GetLocalIBANs().empty());
 }
 
 TEST_F(IBANSaveManagerTest, LocallySaveIBAN_NotEnoughStrikesShouldOfferToSave) {
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index c5b43088..2d5bf2b 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -1087,7 +1087,7 @@
 }
 
 IBAN* PersonalDataManager::GetIBANByGUID(const std::string& guid) {
-  const std::vector<IBAN*>& ibans = GetIBANs();
+  const std::vector<IBAN*>& ibans = GetLocalIBANs();
   auto iter = FindElementByGUID(ibans, guid);
   return iter != ibans.end() ? *iter : nullptr;
 }
@@ -1206,7 +1206,7 @@
   return result;
 }
 
-std::vector<IBAN*> PersonalDataManager::GetIBANs() const {
+std::vector<IBAN*> PersonalDataManager::GetLocalIBANs() const {
   std::vector<IBAN*> result;
   result.reserve(local_ibans_.size());
   for (const auto& iban : local_ibans_) {
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index a08e981..68ce3df 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -320,8 +320,8 @@
   // Returns all credit cards, server and local.
   virtual std::vector<CreditCard*> GetCreditCards() const;
 
-  // Returns local Ibans.
-  virtual std::vector<IBAN*> GetIBANs() const;
+  // Returns local IBANs.
+  virtual std::vector<IBAN*> GetLocalIBANs() const;
 
   // Returns the Payments customer data. Returns nullptr if no data is present.
   virtual PaymentsCustomerData* GetPaymentsCustomerData() const;
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index 6fb884d..b9b9e69 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -890,7 +890,7 @@
   personal_data_->AddIBAN(iban0);
   personal_data_->AddIBAN(iban1);
 
-  EXPECT_EQ(0U, personal_data_->GetIBANs().size());
+  EXPECT_EQ(0U, personal_data_->GetLocalIBANs().size());
 }
 
 TEST_F(PersonalDataManagerTest, AddUpdateRemoveIBANs) {
@@ -924,13 +924,13 @@
   std::vector<IBAN*> ibans;
   ibans.push_back(&iban0);
   ibans.push_back(&iban1);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // `iban1_2` has the same fields as `iban1_1`, verify that `iban1_2` is
   // not added.
   IBAN iban1_2 = iban1_1;
   personal_data_->AddIBAN(iban1_1);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Update IBAN0, remove IBAN1, and add IBAN2.
   iban0.set_nickname(u"Nickname new 0");
@@ -944,7 +944,7 @@
   ibans.clear();
   ibans.push_back(&iban0);
   ibans.push_back(&iban2);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Verify that a duplicate IBAN should not be added.
   IBAN iban0_dup = iban0;
@@ -952,7 +952,7 @@
   ibans.clear();
   ibans.push_back(&iban0);
   ibans.push_back(&iban2);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Reset the PersonalDataManager. This tests that the personal data was saved
   // to the web database, and that we can load the IBANs from the web database.
@@ -962,7 +962,7 @@
   ibans.clear();
   ibans.push_back(&iban0);
   ibans.push_back(&iban2);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 }
 
 // Ensure that new IBANs can be updated and saved via
@@ -979,7 +979,7 @@
 
   // Make sure everything is set up correctly.
   WaitForOnPersonalDataChanged();
-  EXPECT_EQ(1U, personal_data_->GetIBANs().size());
+  EXPECT_EQ(1U, personal_data_->GetLocalIBANs().size());
 
   // Creates a new IBAN and call `OnAcceptedLocalIBANSave()` and verify that
   // the new IBAN is saved.
@@ -990,13 +990,13 @@
   WaitForOnPersonalDataChanged();
 
   // Expect that the new IBAN is added.
-  ASSERT_EQ(2U, personal_data_->GetIBANs().size());
+  ASSERT_EQ(2U, personal_data_->GetLocalIBANs().size());
 
   std::vector<IBAN*> ibans;
   ibans.push_back(&iban0);
   ibans.push_back(&iban1);
   // Verify that we've loaded the IBAN from the web database.
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Creates a new `iban2` which has the same value as `iban0` but with
   // different nickname and call `OnAcceptedLocalIBANSave()`.
@@ -1014,21 +1014,21 @@
   ibans.push_back(&iban1);
   ibans.push_back(&iban2);
   // Expect that the existing IBANs are updated.
-  ASSERT_EQ(2U, personal_data_->GetIBANs().size());
+  ASSERT_EQ(2U, personal_data_->GetLocalIBANs().size());
 
   // Verify that we've loaded the IBANs from the web database.
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Call `OnAcceptedLocalIBANSave()` with the same iban1, verify that nothing
   // changes.
   personal_data_->OnAcceptedLocalIBANSave(iban1);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 
   // Reset the PersonalDataManager. This tests that the IBANs are persisted
   // in the local web database even if the browser is re-loaded, ensuring that
   // the user can load the IBANs from the local web database on browser startup.
   ResetPersonalDataManager(USER_MODE_NORMAL);
-  ExpectSameElements(ibans, personal_data_->GetIBANs());
+  ExpectSameElements(ibans, personal_data_->GetLocalIBANs());
 }
 
 // Ensure that new IBAN cannot be updated nor saved via
@@ -1046,7 +1046,7 @@
   personal_data_->AddIBAN(iban0);
 
   // Verify the new IBAN is not saved.
-  EXPECT_TRUE(personal_data_->GetIBANs().empty());
+  EXPECT_TRUE(personal_data_->GetLocalIBANs().empty());
 
   // Creates a new IBAN and call `OnAcceptedLocalIBANSave()` and verify that
   // the new IBAN is not saved.
@@ -1055,14 +1055,14 @@
   iban1.set_nickname(u"Nickname 1");
   personal_data_->OnAcceptedLocalIBANSave(iban1);
 
-  EXPECT_TRUE(personal_data_->GetIBANs().empty());
+  EXPECT_TRUE(personal_data_->GetLocalIBANs().empty());
 
   // Updates the nickname for `iban1` and call `OnAcceptedLocalIBANSave()`,
   // verify that nothing happens.
   iban1.set_nickname(u"Nickname 0 updated");
   personal_data_->OnAcceptedLocalIBANSave(iban1);
 
-  EXPECT_TRUE(personal_data_->GetIBANs().empty());
+  EXPECT_TRUE(personal_data_->GetLocalIBANs().empty());
 }
 
 TEST_F(PersonalDataManagerTest, AddUpdateRemoveCreditCards) {
diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc
index a843fd4..e86808a 100644
--- a/components/download/internal/common/download_item_impl.cc
+++ b/components/download/internal/common/download_item_impl.cc
@@ -535,14 +535,14 @@
   MaybeCompleteDownload();
 }
 
-void DownloadItemImpl::ValidateMixedContentDownload() {
+void DownloadItemImpl::ValidateInsecureDownload() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!IsDone());
-  DCHECK(IsMixedContent());
+  DCHECK(IsInsecure());
 
   DVLOG(20) << __func__ << "() download=" << DebugString(true);
 
-  mixed_content_status_ = MixedContentStatus::VALIDATED;
+  insecure_download_status_ = InsecureDownloadStatus::VALIDATED;
 
   UpdateObservers();  // TODO(asanka): This is potentially unsafe. The download
                       // may not be in a consistent state or around at all after
@@ -1038,19 +1038,19 @@
          danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE;
 }
 
-bool DownloadItemImpl::IsMixedContent() const {
-  return mixed_content_status_ == MixedContentStatus::WARN ||
-         mixed_content_status_ == MixedContentStatus::BLOCK ||
-         mixed_content_status_ == MixedContentStatus::SILENT_BLOCK;
+bool DownloadItemImpl::IsInsecure() const {
+  return insecure_download_status_ == InsecureDownloadStatus::WARN ||
+         insecure_download_status_ == InsecureDownloadStatus::BLOCK ||
+         insecure_download_status_ == InsecureDownloadStatus::SILENT_BLOCK;
 }
 
 DownloadDangerType DownloadItemImpl::GetDangerType() const {
   return danger_type_;
 }
 
-DownloadItem::MixedContentStatus DownloadItemImpl::GetMixedContentStatus()
-    const {
-  return mixed_content_status_;
+DownloadItem::InsecureDownloadStatus
+DownloadItemImpl::GetInsecureDownloadStatus() const {
+  return insecure_download_status_;
 }
 
 bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const {
@@ -1705,7 +1705,7 @@
     const base::FilePath& target_path,
     TargetDisposition disposition,
     DownloadDangerType danger_type,
-    MixedContentStatus mixed_content_status,
+    InsecureDownloadStatus insecure_download_status,
     const base::FilePath& intermediate_path,
     const base::FilePath& display_name,
     const std::string& mime_type,
@@ -1747,7 +1747,7 @@
   destination_info_.target_path = target_path;
   destination_info_.target_disposition = disposition;
   SetDangerType(danger_type);
-  mixed_content_status_ = mixed_content_status;
+  insecure_download_status_ = insecure_download_status;
   if (!display_name.empty())
     SetDisplayName(display_name);
   if (!mime_type.empty())
@@ -2303,9 +2303,9 @@
   if (IsDangerous())
     return false;
 
-  // If the download is mixed content, but not yet validated, it's not ready for
+  // If the download is insecure, but not yet validated, it's not ready for
   // completion.
-  if (IsMixedContent())
+  if (IsInsecure())
     return false;
 
   // Check for consistency before invoking delegate. Since there are no pending
diff --git a/components/download/internal/common/download_item_impl_delegate.cc b/components/download/internal/common/download_item_impl_delegate.cc
index 22e75f9..5b0d370 100644
--- a/components/download/internal/common/download_item_impl_delegate.cc
+++ b/components/download/internal/common/download_item_impl_delegate.cc
@@ -37,8 +37,8 @@
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, target_path, base::FilePath(),
-      std::string(), DOWNLOAD_INTERRUPT_REASON_NONE);
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, target_path,
+      base::FilePath(), std::string(), DOWNLOAD_INTERRUPT_REASON_NONE);
 }
 
 bool DownloadItemImplDelegate::ShouldCompleteDownload(
diff --git a/components/download/internal/common/download_item_impl_unittest.cc b/components/download/internal/common/download_item_impl_unittest.cc
index c56be85a..a5dee2c 100644
--- a/components/download/internal/common/download_item_impl_unittest.cc
+++ b/components/download/internal/common/download_item_impl_unittest.cc
@@ -369,7 +369,7 @@
 
     std::move(callback).Run(
         target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, danger_type,
-        DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+        DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
         base::FilePath(), std::string() /*mime_type*/,
         DOWNLOAD_INTERRUPT_REASON_NONE);
     task_environment_.RunUntilIdle();
@@ -631,7 +631,7 @@
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_FALSE(observer.CheckAndResetDownloadUpdated());
@@ -921,7 +921,7 @@
     std::move(callback).Run(
         target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+        DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
         base::FilePath(), std::string() /*mime_type*/,
         DOWNLOAD_INTERRUPT_REASON_NONE);
     task_environment_.RunUntilIdle();
@@ -1015,7 +1015,7 @@
       .Run(base::FilePath(kDummyTargetPath),
            DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN,
+           DownloadItem::InsecureDownloadStatus::UNKNOWN,
            base::FilePath(kDummyIntermediatePath), base::FilePath(),
            std::string() /*mime_type*/, DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1218,7 +1218,7 @@
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1272,7 +1272,7 @@
       .Run(base::FilePath(kDummyTargetPath),
            DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN,
+           DownloadItem::InsecureDownloadStatus::UNKNOWN,
            base::FilePath(kDummyIntermediatePath), base::FilePath(),
            std::string() /*mime_type*/, DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1316,7 +1316,7 @@
   std::move(download_target_callback)
       .Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN, target_path,
+           DownloadItem::InsecureDownloadStatus::UNKNOWN, target_path,
            base::FilePath(), std::string() /*mime_type*/,
            DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1348,7 +1348,7 @@
   std::move(callback).Run(
       final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1399,7 +1399,7 @@
   std::move(callback).Run(
       final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1465,7 +1465,7 @@
   std::move(callback).Run(
       final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1512,7 +1512,7 @@
   std::move(callback).Run(
       final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1554,7 +1554,7 @@
   std::move(callback).Run(
       final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   task_environment_.RunUntilIdle();
@@ -1597,7 +1597,7 @@
   std::move(callback).Run(base::FilePath(FILE_PATH_LITERAL("foo")),
                           DownloadItem::TARGET_DISPOSITION_OVERWRITE,
                           DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                          DownloadItem::MixedContentStatus::UNKNOWN,
+                          DownloadItem::InsecureDownloadStatus::UNKNOWN,
                           base::FilePath(FILE_PATH_LITERAL("bar")),
                           base::FilePath(), std::string() /*mime_type*/,
                           DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
@@ -1613,7 +1613,7 @@
   std::move(callback).Run(
       base::FilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, base::FilePath(),
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, base::FilePath(),
       base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
@@ -1629,8 +1629,8 @@
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, target_path, base::FilePath(),
-      std::string() /*mime_type*/,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, target_path,
+      base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
   EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE,
@@ -1651,7 +1651,7 @@
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      DownloadItem::MixedContentStatus::UNKNOWN, intermediate_path,
+      DownloadItem::InsecureDownloadStatus::UNKNOWN, intermediate_path,
       base::FilePath(), mime_type, DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_EQ(mime_type, item->GetMimeType());
   CleanupItem(item, file, DownloadItem::IN_PROGRESS);
@@ -2509,7 +2509,7 @@
   std::move(target_callback)
       .Run(base::FilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN, base::FilePath(),
+           DownloadItem::InsecureDownloadStatus::UNKNOWN, base::FilePath(),
            base::FilePath(), std::string() /*mime_type*/,
            DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_EQ(DownloadItem::CANCELLED, item_->GetState());
@@ -2560,7 +2560,7 @@
       .Run(base::FilePath(kDummyTargetPath),
            DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN,
+           DownloadItem::InsecureDownloadStatus::UNKNOWN,
            base::FilePath(kDummyIntermediatePath), base::FilePath(),
            std::string() /*mime_type*/, DOWNLOAD_INTERRUPT_REASON_NONE);
 
@@ -2626,7 +2626,7 @@
       .Run(base::FilePath(kDummyTargetPath),
            DownloadItem::TARGET_DISPOSITION_OVERWRITE,
            DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-           DownloadItem::MixedContentStatus::UNKNOWN,
+           DownloadItem::InsecureDownloadStatus::UNKNOWN,
            base::FilePath(kDummyIntermediatePath), base::FilePath(),
            std::string() /*mime_type*/, DOWNLOAD_INTERRUPT_REASON_NONE);
 
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index 2de9e3e..0f3f2cda 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -156,14 +156,15 @@
       base::BindOnce(std::move(callback), std::move(download_names)));
 }
 
-void OnPathReserved(DownloadItemImplDelegate::DownloadTargetCallback callback,
-                    DownloadDangerType danger_type,
-                    DownloadItem::MixedContentStatus mixed_content_status,
-                    const InProgressDownloadManager::IntermediatePathCallback&
-                        intermediate_path_cb,
-                    const base::FilePath& forced_file_path,
-                    PathValidationResult result,
-                    const base::FilePath& target_path) {
+void OnPathReserved(
+    DownloadItemImplDelegate::DownloadTargetCallback callback,
+    DownloadDangerType danger_type,
+    DownloadItem::InsecureDownloadStatus insecure_download_status,
+    const InProgressDownloadManager::IntermediatePathCallback&
+        intermediate_path_cb,
+    const base::FilePath& forced_file_path,
+    PathValidationResult result,
+    const base::FilePath& target_path) {
   base::FilePath intermediate_path;
   if (!target_path.empty() &&
       (result == PathValidationResult::SUCCESS ||
@@ -182,7 +183,7 @@
           : BackgroudTargetDeterminationResultTypes::kSuccess);
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, danger_type,
-      mixed_content_status, intermediate_path, base::FilePath(),
+      insecure_download_status, intermediate_path, base::FilePath(),
       std::string() /*mime_type*/,
       intermediate_path.empty() ? DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
                                 : DOWNLOAD_INTERRUPT_REASON_NONE);
@@ -403,7 +404,7 @@
   if (target_path.empty()) {
     std::move(callback).Run(
         target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-        download->GetDangerType(), download->GetMixedContentStatus(),
+        download->GetDangerType(), download->GetInsecureDownloadStatus(),
         target_path, base::FilePath(), std::string() /*mime_type*/,
         DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
     RecordBackgroundTargetDeterminationResult(
@@ -416,7 +417,7 @@
   if (target_path.IsContentUri()) {
     std::move(callback).Run(
         target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-        download->GetDangerType(), download->GetMixedContentStatus(),
+        download->GetDangerType(), download->GetInsecureDownloadStatus(),
         target_path, base::FilePath(), std::string() /*mime_type*/,
         DOWNLOAD_INTERRUPT_REASON_NONE);
     RecordBackgroundTargetDeterminationResult(
@@ -432,15 +433,15 @@
           : DownloadPathReservationTracker::OVERWRITE,
       base::BindOnce(&OnPathReserved, std::move(callback),
                      download->GetDangerType(),
-                     download->GetMixedContentStatus(), intermediate_path_cb_,
-                     download->GetForcedFilePath()));
+                     download->GetInsecureDownloadStatus(),
+                     intermediate_path_cb_, download->GetForcedFilePath()));
 #else
   // For non-android, the code below is only used by tests.
   base::FilePath intermediate_path =
       download->GetFullPath().empty() ? target_path : download->GetFullPath();
   std::move(callback).Run(
       target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-      download->GetDangerType(), download->GetMixedContentStatus(),
+      download->GetDangerType(), download->GetInsecureDownloadStatus(),
       intermediate_path, base::FilePath(), std::string() /*mime_type*/,
       DOWNLOAD_INTERRUPT_REASON_NONE);
 #endif  // BUILDFLAG(IS_ANDROID)
diff --git a/components/download/public/common/download_item.h b/components/download/public/common/download_item.h
index d1d079ea..dfbe5773 100644
--- a/components/download/public/common/download_item.h
+++ b/components/download/public/common/download_item.h
@@ -102,19 +102,19 @@
     RESULT_MAX = FAILURE_UNKNOWN
   };
 
-  // The mixed content status for a download item.
-  enum MixedContentStatus {
+  // The insecure status for a download item.
+  enum InsecureDownloadStatus {
     // Target not yet determined, so status not yet available.
     UNKNOWN = 0,
-    // Download is not mixed content.
+    // Download is not insecure.
     SAFE = 1,
     // Download has been explicitly OK'd by the user. Only used on Desktop.
     VALIDATED = 2,
-    // Download is mixed content, and the user should be warned.
+    // Download is insecure, and the user should be warned.
     WARN = 3,
-    // Download is mixed content, and the user should see an error.
+    // Download is insecure, and the user should see an error.
     BLOCK = 4,
-    // Download is mixed content, and it should be silently dropped.
+    // Download is insecure, and it should be silently dropped.
     SILENT_BLOCK = 5,
   };
 
@@ -176,8 +176,8 @@
   // Called when the user has validated the download of a dangerous file.
   virtual void ValidateDangerousDownload() = 0;
 
-  // Called when the user has validated the download of a mixed content file.
-  virtual void ValidateMixedContentDownload() = 0;
+  // Called when the user has validated the download of an insecure file.
+  virtual void ValidateInsecureDownload() = 0;
 
   // Called to acquire a dangerous download. If |delete_file_afterward| is true,
   // invokes |callback| on the UI thread with the path to the downloaded file,
@@ -427,18 +427,18 @@
   // False if the download is safe or that function has been called.
   virtual bool IsDangerous() const = 0;
 
-  // True if the file that will be written by the download is mixed content
-  // and we will require a call to ValidateMixedContentDownload() to complete.
-  // False if not mixed content or that function has been called.
-  virtual bool IsMixedContent() const = 0;
+  // True if the file that will be written by the download is insecurely
+  // delivered and we will require a call to ValidateInsecureDownload() to
+  // complete.  False if not insecure or that function has been called.
+  virtual bool IsInsecure() const = 0;
 
   // Why |safety_state_| is not SAFE.
   virtual DownloadDangerType GetDangerType() const = 0;
 
-  // Returns the mixed content status of the download, indicating whether the
-  // download should be blocked or the user warned. This may be UNKNOWN if the
-  // download target hasn't been determined.
-  virtual MixedContentStatus GetMixedContentStatus() const = 0;
+  // Returns the insecure download status of the download, indicating whether
+  // the download should be blocked or the user warned. This may be UNKNOWN if
+  // the download target hasn't been determined.
+  virtual InsecureDownloadStatus GetInsecureDownloadStatus() const = 0;
 
   // Gets the pointer to the DownloadFile owned by this object.
   virtual DownloadFile* GetDownloadFile() = 0;
diff --git a/components/download/public/common/download_item_impl.h b/components/download/public/common/download_item_impl.h
index 42d16dc..ed9e6b0 100644
--- a/components/download/public/common/download_item_impl.h
+++ b/components/download/public/common/download_item_impl.h
@@ -247,7 +247,7 @@
   void RemoveObserver(DownloadItem::Observer* observer) override;
   void UpdateObservers() override;
   void ValidateDangerousDownload() override;
-  void ValidateMixedContentDownload() override;
+  void ValidateInsecureDownload() override;
   void StealDangerousDownload(bool need_removal,
                               AcquireFileCallback callback) override;
   void Pause() override;
@@ -304,9 +304,9 @@
   DownloadItemRenameHandler* GetRenameHandler() override;
   const DownloadItemRerouteInfo& GetRerouteInfo() const override;
   bool IsDangerous() const override;
-  bool IsMixedContent() const override;
+  bool IsInsecure() const override;
   DownloadDangerType GetDangerType() const override;
-  MixedContentStatus GetMixedContentStatus() const override;
+  InsecureDownloadStatus GetInsecureDownloadStatus() const override;
   bool TimeRemaining(base::TimeDelta* remaining) const override;
   int64_t CurrentSpeed() const override;
   int PercentComplete() const override;
@@ -597,7 +597,7 @@
       const base::FilePath& target_path,
       TargetDisposition disposition,
       DownloadDangerType danger_type,
-      MixedContentStatus mixed_content_status,
+      InsecureDownloadStatus insecure_download_status,
       const base::FilePath& intermediate_path,
       const base::FilePath& display_name,
       const std::string& mime_type,
@@ -890,8 +890,9 @@
   // UKM ID for reporting, default to 0 if uninitialized.
   uint64_t ukm_download_id_ = 0;
 
-  // The MixedContentStatus if determined.
-  MixedContentStatus mixed_content_status_ = MixedContentStatus::UNKNOWN;
+  // The InsecureDownloadStatus if determined.
+  InsecureDownloadStatus insecure_download_status_ =
+      InsecureDownloadStatus::UNKNOWN;
 
   // A handler for renaming and helping with display the item.
   std::unique_ptr<DownloadItemRenameHandler> rename_handler_;
diff --git a/components/download/public/common/download_item_impl_delegate.h b/components/download/public/common/download_item_impl_delegate.h
index 0a1ddf4..b3ee9af 100644
--- a/components/download/public/common/download_item_impl_delegate.h
+++ b/components/download/public/common/download_item_impl_delegate.h
@@ -49,7 +49,7 @@
       const base::FilePath& target_path,
       DownloadItem::TargetDisposition disposition,
       DownloadDangerType danger_type,
-      DownloadItem::MixedContentStatus mixed_content_status,
+      DownloadItem::InsecureDownloadStatus insecure_download_status,
       const base::FilePath& intermediate_path,
       const base::FilePath& display_name,
       const std::string& mime_type,
diff --git a/components/download/public/common/download_item_utils.cc b/components/download/public/common/download_item_utils.cc
index 796faea..db8a8e7e 100644
--- a/components/download/public/common/download_item_utils.cc
+++ b/components/download/public/common/download_item_utils.cc
@@ -80,22 +80,21 @@
   }
 }
 
-crosapi::mojom::DownloadMixedContentStatus
-ConvertToMojoDownloadMixedContentStatus(
-    DownloadItem::MixedContentStatus value) {
+crosapi::mojom::InsecureDownloadStatus ConvertToMojoInsecureDownloadStatus(
+    DownloadItem::InsecureDownloadStatus value) {
   switch (value) {
-    case DownloadItem::MixedContentStatus::UNKNOWN:
-      return crosapi::mojom::DownloadMixedContentStatus::kUnknown;
-    case DownloadItem::MixedContentStatus::SAFE:
-      return crosapi::mojom::DownloadMixedContentStatus::kSafe;
-    case DownloadItem::MixedContentStatus::VALIDATED:
-      return crosapi::mojom::DownloadMixedContentStatus::kValidated;
-    case DownloadItem::MixedContentStatus::WARN:
-      return crosapi::mojom::DownloadMixedContentStatus::kWarn;
-    case DownloadItem::MixedContentStatus::BLOCK:
-      return crosapi::mojom::DownloadMixedContentStatus::kBlock;
-    case DownloadItem::MixedContentStatus::SILENT_BLOCK:
-      return crosapi::mojom::DownloadMixedContentStatus::kSilentBlock;
+    case DownloadItem::InsecureDownloadStatus::UNKNOWN:
+      return crosapi::mojom::InsecureDownloadStatus::kUnknown;
+    case DownloadItem::InsecureDownloadStatus::SAFE:
+      return crosapi::mojom::InsecureDownloadStatus::kSafe;
+    case DownloadItem::InsecureDownloadStatus::VALIDATED:
+      return crosapi::mojom::InsecureDownloadStatus::kValidated;
+    case DownloadItem::InsecureDownloadStatus::WARN:
+      return crosapi::mojom::InsecureDownloadStatus::kWarn;
+    case DownloadItem::InsecureDownloadStatus::BLOCK:
+      return crosapi::mojom::InsecureDownloadStatus::kBlock;
+    case DownloadItem::InsecureDownloadStatus::SILENT_BLOCK:
+      return crosapi::mojom::InsecureDownloadStatus::kSilentBlock;
   }
 }
 
@@ -138,12 +137,12 @@
   download->start_time = item->GetStartTime();
   download->is_dangerous = item->IsDangerous();
   download->has_is_dangerous = true;
-  download->is_mixed_content = item->IsMixedContent();
-  download->has_is_mixed_content = true;
+  download->is_insecure = item->IsInsecure();
+  download->has_is_insecure = true;
   download->danger_type =
       ConvertToMojoDownloadDangerType(item->GetDangerType());
-  download->mixed_content_status =
-      ConvertToMojoDownloadMixedContentStatus(item->GetMixedContentStatus());
+  download->insecure_download_status =
+      ConvertToMojoInsecureDownloadStatus(item->GetInsecureDownloadStatus());
   return download;
 }
 
diff --git a/components/download/public/common/mock_download_item.h b/components/download/public/common/mock_download_item.h
index b5fc67b..7556109c8 100644
--- a/components/download/public/common/mock_download_item.h
+++ b/components/download/public/common/mock_download_item.h
@@ -45,7 +45,7 @@
 
   MOCK_METHOD0(UpdateObservers, void());
   MOCK_METHOD0(ValidateDangerousDownload, void());
-  MOCK_METHOD0(ValidateMixedContentDownload, void());
+  MOCK_METHOD0(ValidateInsecureDownload, void());
   MOCK_METHOD2(StealDangerousDownload, void(bool, AcquireFileCallback));
   MOCK_METHOD0(Pause, void());
   MOCK_METHOD1(Resume, void(bool));
@@ -107,9 +107,9 @@
               (),
               (const override));
   MOCK_CONST_METHOD0(IsDangerous, bool());
-  MOCK_CONST_METHOD0(IsMixedContent, bool());
+  MOCK_CONST_METHOD0(IsInsecure, bool());
   MOCK_CONST_METHOD0(GetDangerType, DownloadDangerType());
-  MOCK_CONST_METHOD0(GetMixedContentStatus, MixedContentStatus());
+  MOCK_CONST_METHOD0(GetInsecureDownloadStatus, InsecureDownloadStatus());
   MOCK_CONST_METHOD1(TimeRemaining, bool(base::TimeDelta*));
   MOCK_CONST_METHOD0(CurrentSpeed, int64_t());
   MOCK_CONST_METHOD0(PercentComplete, int());
@@ -143,7 +143,7 @@
   MOCK_METHOD1(SetOpened, void(bool));
   MOCK_METHOD1(SetLastAccessTime, void(base::Time));
   MOCK_METHOD1(SetDisplayName, void(const base::FilePath&));
-  MOCK_METHOD1(SetMixedContentStatus, void(MixedContentStatus));
+  MOCK_METHOD1(SetInsecureDownloadStatus, void(InsecureDownloadStatus));
   MOCK_CONST_METHOD1(DebugString, std::string(bool));
   MOCK_METHOD1(SimulateErrorForTesting, void(DownloadInterruptReason));
   MOCK_METHOD2(Rename, void(const base::FilePath&, RenameDownloadCallback));
diff --git a/components/download/public/common/mock_download_item_impl.h b/components/download/public/common/mock_download_item_impl.h
index 34f72519..d37ed92 100644
--- a/components/download/public/common/mock_download_item_impl.h
+++ b/components/download/public/common/mock_download_item_impl.h
@@ -30,7 +30,7 @@
                void(const base::FilePath&,
                     TargetDisposition,
                     DownloadDangerType,
-                    MixedContentStatus,
+                    InsecureDownloadStatus,
                     const base::FilePath&,
                     const base::FilePath&,
                     const std::string&,
@@ -105,9 +105,9 @@
   MOCK_METHOD1(SetOpenWhenComplete, void(bool));
   MOCK_CONST_METHOD0(GetFileExternallyRemoved, bool());
   MOCK_CONST_METHOD0(GetDangerType, DownloadDangerType());
-  MOCK_CONST_METHOD0(GetMixedContentStatus, MixedContentStatus());
+  MOCK_CONST_METHOD0(GetInsecureDownloadStatus, InsecureDownloadStatus());
   MOCK_CONST_METHOD0(IsDangerous, bool());
-  MOCK_CONST_METHOD0(IsMixedContent, bool());
+  MOCK_CONST_METHOD0(IsInsecure, bool());
   MOCK_METHOD0(GetAutoOpened, bool());
   MOCK_CONST_METHOD0(GetForcedFilePath, const base::FilePath&());
   MOCK_CONST_METHOD0(HasUserGesture, bool());
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
index c1e2a32..9e9bb4c 100644
--- a/components/history/core/browser/history_backend.cc
+++ b/components/history/core/browser/history_backend.cc
@@ -2146,8 +2146,9 @@
   return db_ ? db_->ReserveNextClusterId() : 0;
 }
 
-void HistoryBackend::AddVisitsToCluster(int64_t cluster_id,
-                                        const std::vector<VisitID>& visits) {
+void HistoryBackend::AddVisitsToCluster(
+    int64_t cluster_id,
+    const std::vector<ClusterVisit>& visits) {
   TRACE_EVENT0("browser", "HistoryBackend::AddVisitsToCluster");
   if (!db_)
     return;
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h
index e422e33..ba2fe87 100644
--- a/components/history/core/browser/history_backend.h
+++ b/components/history/core/browser/history_backend.h
@@ -519,7 +519,7 @@
   int64_t ReserveNextClusterId();
 
   void AddVisitsToCluster(int64_t cluster_id,
-                          const std::vector<VisitID>& visits);
+                          const std::vector<ClusterVisit>& visits);
 
   std::vector<Cluster> GetMostRecentClusters(
       base::Time inclusive_min_time,
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 53aea9d..2be2e36 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -4068,7 +4068,13 @@
 
   AddAnnotatedVisit(1);
   AddAnnotatedVisit(2);
-  backend_->AddVisitsToCluster(cluster_id, {1, 2});
+  ClusterVisit visit_1;
+  visit_1.annotated_visit.visit_row.visit_id = 1;
+  // Verify the cluster visits are being flushed out.
+  visit_1.url_for_display = u"url_for_display";
+  ClusterVisit visit_2;
+  visit_2.annotated_visit.visit_row.visit_id = 2;
+  backend_->AddVisitsToCluster(cluster_id, {visit_1, visit_2});
 
   VerifyCluster(backend_->GetCluster(cluster_id, false), {cluster_id, {2, 1}});
 }
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
index 7947ee2..aad6d1da 100644
--- a/components/history/core/browser/history_service.cc
+++ b/components/history/core/browser/history_service.cc
@@ -314,7 +314,7 @@
 
 base::CancelableTaskTracker::TaskId HistoryService::AddVisitsToCluster(
     int64_t cluster_id,
-    const std::vector<VisitID>& visits,
+    const std::vector<ClusterVisit>& visits,
     base::CancelableTaskTracker* tracker) {
   DCHECK(backend_task_runner_) << "History service being called after cleanup";
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
index 7a889a4..e3a8d0d 100644
--- a/components/history/core/browser/history_service.h
+++ b/components/history/core/browser/history_service.h
@@ -598,11 +598,11 @@
       base::OnceCallback<void(int64_t)> callback,
       base::CancelableTaskTracker* tracker);
 
-  // Adds all visit IDs in `visits` to the cluster `cluster_id`.
+  // Adds `visits` to the cluster `cluster_id`.
   // Virtual for testing.
   virtual base::CancelableTaskTracker::TaskId AddVisitsToCluster(
       int64_t cluster_id,
-      const std::vector<VisitID>& visits,
+      const std::vector<ClusterVisit>& visits,
       base::CancelableTaskTracker* tracker);
 
   // Get the most recent `Cluster`s within the constraints. The most recent
diff --git a/components/history/core/browser/sync/history_sync_bridge.cc b/components/history/core/browser/sync/history_sync_bridge.cc
index 6eca31f..32ed9073 100644
--- a/components/history/core/browser/sync/history_sync_bridge.cc
+++ b/components/history/core/browser/sync/history_sync_bridge.cc
@@ -560,9 +560,6 @@
           // Updating didn't work, so actually add the data instead.
           if (!AddEntityInBackend(&id_remapper, specifics)) {
             // Something went wrong.
-            // TODO(crbug.com/1364576): This can happen if the incoming URL
-            // shouldn't be added to the history DB. In that case, we should
-            // *not* record a DB error here.
             RecordDatabaseError(
                 SyncHistoryDatabaseError::kApplySyncChangesAddSyncedVisit);
             break;
@@ -610,8 +607,6 @@
   if (delete_metadata_change_list) {
     // A non-null `delete_metadata_change_list` indicates that Sync is being
     // turned off only permanently. Delete all foreign visits from the DB.
-    // TODO(crbug.com/1383912): The signal to delete metadata currently doesn't
-    // reliably arrive here.
     history_backend_->DeleteAllForeignVisits();
   }
 
@@ -810,7 +805,7 @@
     syncer::SyncService::TransportState state) {
   sync_transport_state_ = state;
 
-  // TODO(crbug.com/1383912): Currently ApplyStopSyncChanges() doesn't always
+  // TODO(crbug.com/897628): Currently ApplyStopSyncChanges() doesn't always
   // get called with a non-null MetadataChangeList when Sync is turned off. This
   // is a workaround to still clear foreign history in that case. Remove once
   // that bug is fixed.
diff --git a/components/history/core/browser/visit_annotations_database.cc b/components/history/core/browser/visit_annotations_database.cc
index 0432fc8b..aaf0a03 100644
--- a/components/history/core/browser/visit_annotations_database.cc
+++ b/components/history/core/browser/visit_annotations_database.cc
@@ -690,7 +690,7 @@
 
 void VisitAnnotationsDatabase::AddVisitsToCluster(
     int64_t cluster_id,
-    const std::vector<VisitID>& visits) {
+    const std::vector<ClusterVisit>& visits) {
   DCHECK_GT(cluster_id, 0);
   sql::Statement clusters_and_visits_statement(GetDB().GetCachedStatement(
       SQL_FROM_HERE,
@@ -700,21 +700,22 @@
       "VALUES(?,?,?,?,?,?,?)"));
 
   // Insert each visit into 'clusters_and_visits'.
-  base::ranges::for_each(visits, [&](const auto visit_id) {
-    DCHECK_GT(visit_id, 0);
+  base::ranges::for_each(visits, [&](const auto& visit) {
+    DCHECK_GT(visit.annotated_visit.visit_row.visit_id, 0);
     clusters_and_visits_statement.Reset(true);
     clusters_and_visits_statement.BindInt64(0, cluster_id);
-    clusters_and_visits_statement.BindInt64(1, visit_id);
+    clusters_and_visits_statement.BindInt64(
+        1, visit.annotated_visit.visit_row.visit_id);
     // Tentatively score everything as 1.0.
     clusters_and_visits_statement.BindDouble(2, 1.0);
-    // Do not populate these initially.
-    clusters_and_visits_statement.BindDouble(3, 0);
-    clusters_and_visits_statement.BindString(4, "");
-    clusters_and_visits_statement.BindString(5, "");
-    clusters_and_visits_statement.BindString16(6, u"");
+    clusters_and_visits_statement.BindDouble(3, visit.engagement_score);
+    clusters_and_visits_statement.BindString(4, visit.url_for_deduping.spec());
+    clusters_and_visits_statement.BindString(5, visit.normalized_url.spec());
+    clusters_and_visits_statement.BindString16(6, visit.url_for_display);
     if (!clusters_and_visits_statement.Run()) {
       DVLOG(0) << "Failed to execute 'clusters_and_visits' insert statement:  "
-               << "cluster_id = " << cluster_id << ", visit_id = " << visit_id;
+               << "cluster_id = " << cluster_id
+               << ", visit_id = " << visit.annotated_visit.visit_row.visit_id;
     }
   });
 }
diff --git a/components/history/core/browser/visit_annotations_database.h b/components/history/core/browser/visit_annotations_database.h
index aef8c367..15fd2119 100644
--- a/components/history/core/browser/visit_annotations_database.h
+++ b/components/history/core/browser/visit_annotations_database.h
@@ -88,7 +88,7 @@
 
   // Adds visits to the cluster with id `cluster_id`.
   void AddVisitsToCluster(int64_t cluster_id,
-                          const std::vector<VisitID>& visits);
+                          const std::vector<ClusterVisit>& visits);
 
   // Get a `Cluster`. Does not include the cluster's `visits` or
   // `keyword_to_data_map`.
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index 07561ca..94ab0f1 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -179,6 +179,12 @@
             "omnibox_history_cluster_provider_shortcuts",
             omnibox_history_cluster_provider_shortcuts);
 
+    omnibox_history_cluster_provider_allow_default =
+        base::GetFieldTrialParamByFeatureAsBool(
+            internal::kOmniboxHistoryClusterProvider,
+            "omnibox_history_cluster_provider_allow_default",
+            omnibox_history_cluster_provider_allow_default);
+
     omnibox_history_cluster_provider_navigation_intent_score_threshold =
         base::GetFieldTrialParamByFeatureAsInt(
             internal::kOmniboxHistoryClusterProvider,
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index d112f5f..42c0800 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -193,6 +193,10 @@
   // `omnibox_history_cluster_provider` is disabled.
   bool omnibox_history_cluster_provider_shortcuts = false;
 
+  // Whether journey suggestions from the `ShortcutsProvider` can be default.
+  // Journey suggestions from the `HistoryClusterProvider` can never be default.
+  bool omnibox_history_cluster_provider_allow_default = false;
+
   // If `omnibox_history_cluster_provider_on_navigation_intents` is false, this
   // threshold helps determine when the user is intending to perform a
   // navigation. Meaningless if either `omnibox_history_cluster_provider` is
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.cc b/components/history_clusters/core/context_clusterer_history_service_observer.cc
index ef1441f..fbc5193 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer.cc
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.cc
@@ -8,8 +8,10 @@
 #include "base/time/default_clock.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history_clusters/core/config.h"
+#include "components/history_clusters/core/history_clusters_util.h"
 #include "components/optimization_guide/core/new_optimization_guide_decider.h"
 #include "components/search_engines/template_url_service.h"
+#include "components/site_engagement/core/site_engagement_score_provider.h"
 
 namespace history_clusters {
 
@@ -40,10 +42,12 @@
 ContextClustererHistoryServiceObserver::ContextClustererHistoryServiceObserver(
     history::HistoryService* history_service,
     TemplateURLService* template_url_service,
-    optimization_guide::NewOptimizationGuideDecider* optimization_guide_decider)
+    optimization_guide::NewOptimizationGuideDecider* optimization_guide_decider,
+    site_engagement::SiteEngagementScoreProvider* engagement_score_provider)
     : history_service_(history_service),
       template_url_service_(template_url_service),
       optimization_guide_decider_(optimization_guide_decider),
+      engagement_score_provider_(engagement_score_provider),
       clock_(base::DefaultClock::GetInstance()) {
   history_service_observation_.Observe(history_service);
 
@@ -150,6 +154,18 @@
   visit_url_to_cluster_map_[normalized_url] = *cluster_id;
 
   if (GetConfig().persist_context_clusters_at_navigation) {
+    history::ClusterVisit cluster_visit;
+    cluster_visit.annotated_visit.visit_row.visit_id = new_visit.visit_id;
+    cluster_visit.normalized_url = GURL(normalized_url);
+    cluster_visit.url_for_deduping =
+        ComputeURLForDeduping(cluster_visit.normalized_url);
+    cluster_visit.url_for_display =
+        ComputeURLForDisplay(cluster_visit.normalized_url);
+    if (engagement_score_provider_) {
+      cluster_visit.engagement_score =
+          engagement_score_provider_->GetScore(cluster_visit.normalized_url);
+    }
+
     // For new clusters, asyncly reserve an ID and have the
     //   `OnPersistedClusterIdReceived()` callback add the visits.
     // For clusters created recently for which history service hasn't yet
@@ -159,9 +175,16 @@
     if (in_progress_cluster.persisted_cluster_id > 0) {
       // Persist visit to existing cluster.
       history_service->AddVisitsToCluster(
-          in_progress_cluster.persisted_cluster_id, {new_visit.visit_id},
+          in_progress_cluster.persisted_cluster_id, {std::move(cluster_visit)},
           &task_tracker_);
-    } else if (is_new_cluster) {
+      return;
+    }
+
+    // As `in_progress_cluster` does not have a persisted cluster ID yet, add
+    // the ClusterVisit to the vector of visits that needs to get persisted.
+    in_progress_cluster.unpersisted_visits.push_back(std::move(cluster_visit));
+
+    if (is_new_cluster) {
       // Cluster creation is async. Reserve next cluster ID and wait to persist
       // items until it comes back in `OnPersistedClusterIdReceived()`.
       history_service->ReserveNextClusterId(
@@ -274,8 +297,14 @@
 
   cluster_it->second.persisted_cluster_id = persisted_cluster_id;
   // Persist all visits we've seen so far.
-  history_service_->AddVisitsToCluster(
-      persisted_cluster_id, cluster_it->second.visit_ids, &task_tracker_);
+  history_service_->AddVisitsToCluster(persisted_cluster_id,
+                                       cluster_it->second.unpersisted_visits,
+                                       &task_tracker_);
+
+  // Clear these out since the visits have now been requested to be persisted.
+  // This is safe to clear here as the vector should have already been copied to
+  // the history DB thread in `AddVisitsToCluster()`.
+  cluster_it->second.unpersisted_visits.clear();
 }
 
 void ContextClustererHistoryServiceObserver::OverrideClockForTesting(
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.h b/components/history_clusters/core/context_clusterer_history_service_observer.h
index fe82c70e..86260a0 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer.h
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.h
@@ -27,6 +27,10 @@
 class NewOptimizationGuideDecider;
 }  // namespace optimization_guide
 
+namespace site_engagement {
+class SiteEngagementScoreProvider;
+}  // namespace site_engagement
+
 namespace history_clusters {
 
 // Information required for determine pending cluster.
@@ -46,6 +50,9 @@
   std::u16string search_terms;
   // The corresponding cluster ID in the persisted database.
   int64_t persisted_cluster_id = 0;
+  // The vector of visits that have not been persisted yet. Note that each entry
+  // only contains the minimum required to persist a cluster visit.
+  std::vector<history::ClusterVisit> unpersisted_visits;
 };
 
 // A HistoryServiceObserver responsible for grouping visits into clusters.
@@ -66,7 +73,8 @@
       history::HistoryService* history_service,
       TemplateURLService* template_url_service,
       optimization_guide::NewOptimizationGuideDecider*
-          optimization_guide_decider);
+          optimization_guide_decider,
+      site_engagement::SiteEngagementScoreProvider* engagement_score_provider);
   ~ContextClustererHistoryServiceObserver() override;
 
   // history::HistoryServiceObserver:
@@ -115,15 +123,21 @@
   // The History Service that `this` observers. Should never be null.
   raw_ptr<history::HistoryService> history_service_;
 
-  // The Template URL Service used to determine if a visit is a search visit.
+  // Used to determine if a visit is a search visit. Should only be null for
+  // tests.
   raw_ptr<TemplateURLService> template_url_service_;
 
-  // The Optimization Guide decider used to determine whether to include a visit
-  // in a cluster.
+  // Used to determine whether to include a visit in any cluster. Can be null,
+  // but is guaranteed to outlive `this`.
   raw_ptr<optimization_guide::NewOptimizationGuideDecider>
       optimization_guide_decider_;
 
-  // The clock used to schedule the clean up of clusters.
+  // Used to determine how "interesting" a visit is likely to be to a user.
+  // Should only be null for tests.
+  raw_ptr<site_engagement::SiteEngagementScoreProvider>
+      engagement_score_provider_;
+
+  // Used to schedule the clean up of clusters.
   raw_ptr<const base::Clock> clock_;
 
   // Tracks the observed history service, for cleanup.
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
index 8d8d726f..ff3f5af9 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
+++ b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/history_clusters/core/config.h"
 #include "components/optimization_guide/core/new_optimization_guide_decider.h"
 #include "components/search_engines/template_url_service.h"
+#include "components/site_engagement/core/site_engagement_score_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -21,8 +22,10 @@
 
 using ::testing::_;
 using ::testing::DoAll;
+using ::testing::ElementsAre;
 using ::testing::Invoke;
 using ::testing::Return;
+using ::testing::SaveArg;
 
 history::URLRows CreateURLRows(const std::vector<GURL>& urls) {
   history::URLRows url_rows;
@@ -93,11 +96,39 @@
   MOCK_METHOD(base::CancelableTaskTracker::TaskId,
               AddVisitsToCluster,
               (int64_t,
-               const std::vector<history::VisitID>&,
+               const std::vector<history::ClusterVisit>&,
                base::CancelableTaskTracker*),
               (override));
 };
 
+class TestSiteEngagementScoreProvider
+    : public site_engagement::SiteEngagementScoreProvider {
+ public:
+  TestSiteEngagementScoreProvider() = default;
+  ~TestSiteEngagementScoreProvider() override = default;
+
+  double GetScore(const GURL& url) const override {
+    ++count_get_score_invocations_;
+    return 0;
+  }
+
+  double GetTotalEngagementPoints() const override { return 1; }
+
+ private:
+  mutable size_t count_get_score_invocations_ = 0;
+};
+
+// Gets the visit IDs in `visits`.
+std::vector<history::VisitID> GetClusterVisitIds(
+    const std::vector<history::ClusterVisit>& visits) {
+  std::vector<history::VisitID> visit_ids;
+  visit_ids.reserve(visits.size());
+  for (const auto& visit : visits) {
+    visit_ids.push_back(visit.annotated_visit.visit_row.visit_id);
+  }
+  return visit_ids;
+}
+
 }  // namespace
 
 class ContextClustererHistoryServiceObserverTest : public testing::Test {
@@ -109,17 +140,19 @@
     history_service_ =
         std::make_unique<testing::StrictMock<MockHistoryService>>();
 
-    // Set up a simple template URL service with a default search engine.
     template_url_service_ = std::make_unique<TemplateURLService>(
         kTemplateURLData, std::size(kTemplateURLData));
 
     optimization_guide_decider_ =
         std::make_unique<TestOptimizationGuideDecider>();
 
+    engagement_score_provider_ =
+        std::make_unique<TestSiteEngagementScoreProvider>();
+
     // Instantiate observer.
     observer_ = std::make_unique<ContextClustererHistoryServiceObserver>(
         history_service_.get(), template_url_service_.get(),
-        optimization_guide_decider_.get());
+        optimization_guide_decider_.get(), engagement_score_provider_.get());
     observer_->OverrideClockForTesting(task_environment_.GetMockClock());
   }
 
@@ -200,6 +233,7 @@
 
   std::unique_ptr<TemplateURLService> template_url_service_;
   std::unique_ptr<TestOptimizationGuideDecider> optimization_guide_decider_;
+  std::unique_ptr<TestSiteEngagementScoreProvider> engagement_score_provider_;
   std::unique_ptr<ContextClustererHistoryServiceObserver> observer_;
 
   history::HistoryService::ClusterIdCallback cluster_id_callback_;
@@ -215,12 +249,14 @@
                                  CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
 
-  std::vector<history::VisitID> visit_ids = {1};
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  std::vector<history::ClusterVisit> got_cluster_visits;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   RunLastClusterIdCallbackWithClusterId(cluster_id);
 
   EXPECT_EQ(1, GetNumClustersCreated());
+  EXPECT_THAT(GetClusterVisitIds(got_cluster_visits), ElementsAre(1));
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
@@ -237,12 +273,14 @@
            /*opener_visit=*/history::kInvalidVisitID, /*referring_visit=*/1);
 
   // Should persist all visits for the cluster when callback is run.
-  std::vector<history::VisitID> visit_ids = {1, 2};
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  std::vector<history::ClusterVisit> got_cluster_visits;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   RunLastClusterIdCallbackWithClusterId(cluster_id);
 
   EXPECT_EQ(1, GetNumClustersCreated());
+  EXPECT_THAT(GetClusterVisitIds(got_cluster_visits), ElementsAre(1, 2));
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
@@ -257,20 +295,24 @@
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
 
   // Should persist all visits for the cluster when callback is run.
-  std::vector<history::VisitID> visit_ids = {1};
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  std::vector<history::ClusterVisit> got_first_cluster_visits;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_first_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   RunLastClusterIdCallbackWithClusterId(cluster_id);
 
   // Should persist as is since we already have the persisted cluster id at this
-  // visit.
-  visit_ids = {2};
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  // visit.t
+  std::vector<history::ClusterVisit> got_second_cluster_visits;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_second_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(123),
            /*opener_visit=*/1, /*referring_visit=*/history::kInvalidVisitID);
 
   EXPECT_EQ(1, GetNumClustersCreated());
+  EXPECT_THAT(GetClusterVisitIds(got_first_cluster_visits), ElementsAre(1));
+  EXPECT_THAT(GetClusterVisitIds(got_second_cluster_visits), ElementsAre(2));
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
@@ -318,9 +360,10 @@
            base::Time::FromTimeT(123));
 
   int64_t cluster_id = 123;
-  std::vector<history::VisitID> visit_ids = {1};
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  std::vector<history::ClusterVisit> got_first_cluster_visits;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_first_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   RunLastClusterIdCallbackWithClusterId(cluster_id);
 
   EXPECT_CALL(*history_service_,
@@ -331,13 +374,16 @@
            base::Time::FromTimeT(123),
            /*opener_visit=*/1);
 
-  visit_ids = {2};
+  std::vector<history::ClusterVisit> got_second_cluster_visits;
   cluster_id = 124;
-  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
-      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, _, _))
+      .WillOnce(DoAll(SaveArg<1>(&got_second_cluster_visits),
+                      Return(base::CancelableTaskTracker::TaskId())));
   RunLastClusterIdCallbackWithClusterId(cluster_id);
 
   EXPECT_EQ(2, GetNumClustersCreated());
+  EXPECT_THAT(GetClusterVisitIds(got_first_cluster_visits), ElementsAre(1));
+  EXPECT_THAT(GetClusterVisitIds(got_second_cluster_visits), ElementsAre(2));
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index d192983..ba1407e2 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -67,7 +67,8 @@
       visit_deletion_observer_(this),
       context_clusterer_observer_(history_service,
                                   template_url_service,
-                                  optimization_guide_decider) {
+                                  optimization_guide_decider,
+                                  engagement_score_provider) {
   DCHECK(history_service_);
 
   visit_deletion_observer_.AttachToHistoryService(history_service);
diff --git a/components/history_clusters/core/on_device_clustering_backend_unittest.cc b/components/history_clusters/core/on_device_clustering_backend_unittest.cc
index 06a93b9..0d56d7cd 100644
--- a/components/history_clusters/core/on_device_clustering_backend_unittest.cc
+++ b/components/history_clusters/core/on_device_clustering_backend_unittest.cc
@@ -30,7 +30,7 @@
     : public site_engagement::SiteEngagementScoreProvider {
  public:
   TestSiteEngagementScoreProvider() = default;
-  ~TestSiteEngagementScoreProvider() = default;
+  ~TestSiteEngagementScoreProvider() override = default;
 
   double GetScore(const GURL& url) const override {
     ++count_get_score_invocations_;
diff --git a/components/infobars/core/infobar_delegate.h b/components/infobars/core/infobar_delegate.h
index 099e116..6b66e5a 100644
--- a/components/infobars/core/infobar_delegate.h
+++ b/components/infobars/core/infobar_delegate.h
@@ -169,7 +169,7 @@
     WEBOTP_SERVICE_INFOBAR_DELEGATE = 95,
     KNOWN_INTERCEPTION_DISCLOSURE_INFOBAR_DELEGATE = 96,
     // Removed: SYNC_ERROR_INFOBAR_DELEGATE_ANDROID = 97,
-    MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_ANDROID = 98,
+    INSECURE_DOWNLOAD_INFOBAR_DELEGATE_ANDROID = 98,
     // Removed: CONDITIONAL_TAB_STRIP_INFOBAR_ANDROID = 99,
     // Removed: LITE_MODE_HTTPS_IMAGE_COMPRESSION_INFOBAR_ANDROID = 100,
     // Removed: SYSTEM_INFOBAR_DELEGATE_MAC = 101,
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 708617a..ca02317 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
@@ -292,12 +292,18 @@
 
     @Override
     public void onSwipeStart() {
-        assert !mAnimatorSet.isStarted()
-            : "Swipe should impossibly be triggered while message is showing/hiding.";
+        // Message shouldn't consume swipe for now because animation is running, e.g.:
+        // the front message should not be swiped when back message is running showing animation.
+        assert isSwipeEnabled();
         mMessageQueueDelegate.onAnimationStart();
     }
 
     @Override
+    public boolean isSwipeEnabled() {
+        return !mAnimatorSet.isStarted();
+    }
+
+    @Override
     public void onSwipeEnd(@Nullable Animator animator) {
         if (animator == null) {
             mMessageQueueDelegate.onAnimationEnd();
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
index caae907..c992fca 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
@@ -265,7 +265,8 @@
 
     @Override
     public boolean isSwipeEnabled(@ScrollDirection int direction) {
-        return direction != ScrollDirection.UNKNOWN && mCurrentState == State.IDLE;
+        return direction != ScrollDirection.UNKNOWN && mCurrentState == State.IDLE
+                && mSwipeAnimationHandler.isSwipeEnabled();
     }
 
     // ---------------------------------------------------------------------------------------------
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/SwipeAnimationHandler.java b/components/messages/android/internal/java/src/org/chromium/components/messages/SwipeAnimationHandler.java
index 5d1f4c3c..f246a90 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/SwipeAnimationHandler.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/SwipeAnimationHandler.java
@@ -23,4 +23,9 @@
      *                 is required.
      */
     void onSwipeEnd(@Nullable Animator animator);
+
+    /**
+     * @return Whether the message view should consume the swipe gesture.
+     */
+    boolean isSwipeEnabled();
 }
diff --git a/components/metrics/component_metrics_provider.cc b/components/metrics/component_metrics_provider.cc
index 3dea6b7..8c3e304 100644
--- a/components/metrics/component_metrics_provider.cc
+++ b/components/metrics/component_metrics_provider.cc
@@ -72,6 +72,8 @@
            SystemProfileProto_ComponentId_WEBVIEW_APPS_PACKAGE_NAMES_ALLOWLIST},
           {"ggkkehgbnfjpeggfpleeakpidbkibbmn",
            SystemProfileProto_ComponentId_CROWD_DENY},
+          {"neifaoindggfcjicffkgpmnlppeffabd",
+           SystemProfileProto_ComponentId_MEDIA_FOUNDATION_WIDEVINE_CDM},
       });
 
   const auto* result = kComponentMap.find(app_id);
diff --git a/components/offline_pages/OWNERS b/components/offline_pages/OWNERS
index d43d826..00f3a26 100644
--- a/components/offline_pages/OWNERS
+++ b/components/offline_pages/OWNERS
@@ -1,10 +1,11 @@
+robertogden@chromium.org
+sclittle@chromium.org
+tbansal@chromium.org
+curranmax@chromium.org
 carlosk@chromium.org
 chili@chromium.org
 dewittj@chromium.org
 dimich@chromium.org
 harringtond@chromium.org
 jianli@chromium.org
-petewil@chromium.org
-robertogden@chromium.org
-sclittle@chromium.org
-tbansal@chromium.org
+petewil@chromium.org
\ No newline at end of file
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 9096a90..79b5379 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -930,10 +930,6 @@
   AutocompleteResult old_matches_to_reuse;
   old_matches_to_reuse.Swap(&result_);
 
-  // Add default static suggestion groups.
-  static auto prebuilt_suggestion_groups_map = omnibox::BuildDefaultGroups();
-  result_.MergeSuggestionGroupsMap(prebuilt_suggestion_groups_map);
-
   for (const auto& provider : providers_) {
     if (!ShouldRunProvider(provider.get()))
       continue;
diff --git a/components/omnibox/browser/autocomplete_provider_unittest.cc b/components/omnibox/browser/autocomplete_provider_unittest.cc
index 1bec24a0..4b6aa05 100644
--- a/components/omnibox/browser/autocomplete_provider_unittest.cc
+++ b/components/omnibox/browser/autocomplete_provider_unittest.cc
@@ -56,7 +56,7 @@
 
 class TestingSchemeClassifier : public AutocompleteSchemeClassifier {
  public:
-  TestingSchemeClassifier() {}
+  TestingSchemeClassifier() = default;
   TestingSchemeClassifier(const TestingSchemeClassifier&) = delete;
   TestingSchemeClassifier& operator=(const TestingSchemeClassifier&) = delete;
 
@@ -309,7 +309,7 @@
                            ACMatchClassifications matches)
     : text_(text), text_is_query_(text_is_query), matches_(matches) {}
 
-ClassifyTest::~ClassifyTest() {}
+ClassifyTest::~ClassifyTest() = default;
 
 ACMatchClassifications ClassifyTest::RunTest(const std::u16string& find_text) {
   return AutocompleteProvider::ClassifyAllMatchesInString(
@@ -386,10 +386,6 @@
   GURL GetDestinationURL(AutocompleteMatch& match,
                          base::TimeDelta query_formulation_time) const;
 
-  // Returns the image from the clipboard as it would be from
-  // AutocompleteController::GetImageFromClipboard().
-  absl::optional<gfx::Image> GetImageFromClipboard() const;
-
   void set_search_provider_field_trial_triggered_in_session(bool val) {
     controller_->search_provider_->set_field_trial_triggered_in_session(val);
   }
@@ -425,11 +421,11 @@
   raw_ptr<MockAutocompleteProviderClient> client_;
   // Used to ensure that |client_| ownership has been passed to |controller_|
   // exactly once.
-  bool client_owned_;
+  bool client_owned_{};
 };
 
 AutocompleteProviderTest::AutocompleteProviderTest()
-    : client_(new MockAutocompleteProviderClient()), client_owned_(false) {
+    : client_(new MockAutocompleteProviderClient()) {
   client_->set_template_url_service(
       std::make_unique<TemplateURLService>(nullptr, 0));
 }
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 008cd9e3..22dfd4a 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -42,6 +42,7 @@
 #include "third_party/metrics_proto/omnibox_event.pb.h"
 #include "third_party/metrics_proto/omnibox_focus_type.pb.h"
 #include "third_party/metrics_proto/omnibox_input_type.pb.h"
+#include "third_party/omnibox_proto/groups.pb.h"
 #include "ui/base/device_form_factor.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -165,6 +166,8 @@
   // Reserve enough space for the maximum number of matches we'll show in either
   // on-focus or prefix-suggest mode.
   matches_.reserve(std::max(GetMaxMatches(), GetMaxMatches(true)));
+  // Add default static suggestion groups.
+  MergeSuggestionGroupsMap(omnibox::BuildDefaultGroups());
 }
 
 AutocompleteResult::~AutocompleteResult() = default;
@@ -392,11 +395,11 @@
   const size_t num_matches =
       CalculateNumMatches(is_zero_suggest, matches_, comparing_object);
 
-  if (!is_zero_suggest) {
+  // TODO(manukh): Limiting should be done by the grouping framework.
+  if (!is_zero_suggest)
     matches_.resize(num_matches);
-  }
 
-  // Group search suggestions above URL suggestions.
+    // Group search suggestions above URL suggestions.
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   if (matches_.size() > 2 &&
       !base::FeatureList::IsEnabled(omnibox::kAdaptiveSuggestionsCount)) {
@@ -858,6 +861,7 @@
 void AutocompleteResult::Reset() {
   matches_.clear();
   suggestion_groups_map_.clear();
+  MergeSuggestionGroupsMap(omnibox::BuildDefaultGroups());
 #if BUILDFLAG(IS_ANDROID)
   java_result_.Reset();
 #endif
@@ -1252,21 +1256,19 @@
   if (begin == end)
     return;
 
-  base::ranges::stable_sort(
-      begin, end, [](int a, int b) { return a < b; },
-      [](const auto& m) {
-        if (AutocompleteMatch::IsStarterPackType(m.type))
-          return 0;
-        // Group history cluster suggestions with searches. If the
-        // `omnibox_history_cluster_provider_free_ranking` feature is disabled,
-        // they'll be pushed back to last position, and grouping here will have
-        // no effect.
-        if (m.type == AutocompleteMatchType::HISTORY_CLUSTER)
-          return 0;
-        if (AutocompleteMatch::IsSearchType(m.type))
-          return 1;
-        return 2;
-      });
+  base::ranges::stable_sort(begin, end, {}, [](const auto& m) {
+    if (AutocompleteMatch::IsStarterPackType(m.type))
+      return 0;
+    // Group history cluster suggestions with searches. If the
+    // `omnibox_history_cluster_provider_free_ranking` feature is disabled,
+    // they'll be pushed back to last position, and grouping here will have
+    // no effect.
+    if (m.type == AutocompleteMatchType::HISTORY_CLUSTER)
+      return 0;
+    if (AutocompleteMatch::IsSearchType(m.type))
+      return 1;
+    return 2;
+  });
 }
 
 // static
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index 81d1a4d..d0731de 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -16,6 +16,7 @@
 #include "components/omnibox/browser/match_compare.h"
 #include "components/omnibox/browser/search_suggestion_parser.h"
 #include "components/omnibox/browser/suggestion_group_util.h"
+#include "third_party/omnibox_proto/groups.pb.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index 39b3c2f..845b3ca 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -677,9 +677,10 @@
       {3, 4, 600},  // Suggestion for not-done provider
   };
   TestData result[] = {
-      {2, 3, 700},
-      {3, 4, 600},
-      {1, 2, 400},
+      {2, 3, 700},  // New suggestion from done provider
+      {3, 4, 600},  // New suggestion from not-done provider
+      // Skip suggestion `{0, 1, 500}`.
+      {1, 2, 400},  // Transferred suggestion from not-done provider
   };
 
   GetProvider(1)->done_ = true;
@@ -691,7 +692,7 @@
 }
 
 TEST_F(AutocompleteResultTest,
-       TransferOldMatchesSkipDoneProviders_DontCopyDoneProviders) {
+       TransferOldMatchesSkipDoneProviders_CopyDoneProviders) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeatureWithParameters(
       omnibox::kAutocompleteStability,
@@ -707,10 +708,10 @@
       {3, 4, 600},  // Suggestion for not-done provider
   };
   TestData result[] = {
-      {2, 3, 700},
-      {3, 4, 600},
-      {0, 1, 500},  // Suggestion from done provider
-      {1, 2, 400},
+      {2, 3, 700},  // New suggestion from done provider
+      {3, 4, 600},  // New suggestion from not-done provider
+      {0, 1, 500},  // Transferred suggestion from done provider
+      {1, 2, 400},  // Transferred suggestion from not-done provider
   };
 
   GetProvider(1)->done_ = true;
@@ -1913,6 +1914,7 @@
   suggestion_groups_map[group_2].set_header_text("2");
 
   {
+    SCOPED_TRACE("Input 'a'");
     AutocompleteInput typed_input(u"a", metrics::OmniboxEventProto::OTHER,
                                   TestSchemeClassifier());
     AutocompleteResult result;
@@ -1922,7 +1924,7 @@
 
     ASSERT_EQ(6U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false));
     const std::array<TestData, 6> expected_data{{
-        // default match unmoved
+        // Default suggestion comes 1st.
         {3, 2, 800, true, {}, AutocompleteMatchType::HISTORY_TITLE},
         // other types
         {6, 3, 1100, false, {}, AutocompleteMatchType::BOOKMARK_TITLE},
@@ -1936,6 +1938,7 @@
     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(
@@ -1946,8 +1949,9 @@
     result.SortAndCull(zero_prefix_input, template_url_service_.get());
 
     ASSERT_EQ(5U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
+    // Should include the top 5 scoring suggestions.
     const std::array<TestData, 5> expected_data{{
-        // default match unmoved
+        // Default match comes 1st.
         {3, 2, 800, true, {}, AutocompleteMatchType::HISTORY_TITLE},
         // other types
         {6, 3, 1100, false, {}, AutocompleteMatchType::BOOKMARK_TITLE},
@@ -1964,6 +1968,7 @@
   suggestion_groups_map[group_2].set_section(omnibox::SECTION_REMOTE_ZPS_2);
 
   {
+    SCOPED_TRACE("Input 'a', with explicit sections");
     AutocompleteInput typed_input(u"a", metrics::OmniboxEventProto::OTHER,
                                   TestSchemeClassifier());
     AutocompleteResult result;
@@ -1975,7 +1980,7 @@
     // then ordered based on group sections.
     ASSERT_EQ(6U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/false));
     const std::array<TestData, 6> expected_data{{
-        // default match unmoved
+        // Default match comes 1st.
         {3, 2, 800, true, {}, AutocompleteMatchType::HISTORY_TITLE},
         // other types
         {6, 3, 1100, false, {}, AutocompleteMatchType::BOOKMARK_TITLE},
@@ -1989,6 +1994,7 @@
     AssertResultMatches(result, expected_data.begin(), expected_data.size());
   }
   {
+    SCOPED_TRACE("Zero input, with explicit sections");
     AutocompleteInput zero_prefix_input(u"", metrics::OmniboxEventProto::NTP,
                                         TestSchemeClassifier());
     zero_prefix_input.set_focus_type(
@@ -2002,7 +2008,7 @@
     // based on group sections, then culled.
     ASSERT_EQ(5U, AutocompleteResult::GetMaxMatches(/*is_zero_suggest=*/true));
     const std::array<TestData, 5> expected_data{{
-        // default match unmoved
+        // Default match comes 1st.
         {3, 2, 800, true, {}, AutocompleteMatchType::HISTORY_TITLE},
         // other types
         {6, 3, 1100, false, {}, AutocompleteMatchType::BOOKMARK_TITLE},
@@ -2049,6 +2055,7 @@
   suggestion_groups_map[group_2].set_header_text("2");
 
   {
+    SCOPED_TRACE("Input 'a'");
     AutocompleteInput typed_input(u"a", metrics::OmniboxEventProto::OTHER,
                                   TestSchemeClassifier());
     AutocompleteResult result;
@@ -2065,6 +2072,7 @@
     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(
@@ -2077,6 +2085,7 @@
 
     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
@@ -2128,6 +2137,7 @@
   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;
@@ -2145,6 +2155,7 @@
     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(
@@ -2159,6 +2170,7 @@
     // of results not in groups is determined by the zero-suggest limit.
     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},
         // omnibox::SECTION_REMOTE_ZPS_1 comes first.
@@ -2319,7 +2331,7 @@
         AutocompleteMatchType::CLIPBOARD_URL,
     };
     for (size_t i = 0; i < result.size(); ++i)
-      EXPECT_EQ(result.match_at(i)->type, expected_types[i]);
+      EXPECT_EQ(result.match_at(i)->type, expected_types[i]) << i;
   }
 }
 
diff --git a/components/omnibox/browser/shortcuts_provider.cc b/components/omnibox/browser/shortcuts_provider.cc
index d4548ea5..fbda814 100644
--- a/components/omnibox/browser/shortcuts_provider.cc
+++ b/components/omnibox/browser/shortcuts_provider.cc
@@ -490,7 +490,15 @@
             !input.prevent_inline_autocomplete() ||
             match.inline_autocompletion.empty();
       }
+#if !BUILDFLAG(IS_IOS)
+    } else if (match.type != AutocompleteMatch::Type::HISTORY_CLUSTER ||
+               history_clusters::GetConfig()
+                   .omnibox_history_cluster_provider_allow_default) {
+      // Don't try to default history cluster suggestions unless
+      // `omnibox_history_cluster_provider_allow_default` is enabled.
+#else
     } else {
+#endif
       // Try rich autocompletion first. For document suggestions,
       // `match.contents` is the title, while `description` is something like
       // 'Google Docs' and shouldn't be autocompleted. For all other nav
diff --git a/components/omnibox/browser/shortcuts_provider_unittest.cc b/components/omnibox/browser/shortcuts_provider_unittest.cc
index 8cff4a9..4b45d38f 100644
--- a/components/omnibox/browser/shortcuts_provider_unittest.cc
+++ b/components/omnibox/browser/shortcuts_provider_unittest.cc
@@ -875,15 +875,23 @@
   const auto matches = provider_->matches();
 
   // Expect 3 (i.e. `provider_max_matches_`) non-cluster matches, and all
-  // cluster matches.
+  // cluster matches. Expect only the non-cluster matches to be allowed to be
+  // default.
   ASSERT_EQ(matches.size(), 7u);
   EXPECT_EQ(matches[0].type, AutocompleteMatchType::HISTORY_URL);
+  EXPECT_EQ(matches[0].allowed_to_be_default_match, true);
   EXPECT_EQ(matches[1].type, AutocompleteMatchType::HISTORY_URL);
+  EXPECT_EQ(matches[1].allowed_to_be_default_match, true);
   EXPECT_EQ(matches[2].type, AutocompleteMatchType::HISTORY_URL);
+  EXPECT_EQ(matches[2].allowed_to_be_default_match, true);
   EXPECT_EQ(matches[3].type, AutocompleteMatchType::HISTORY_CLUSTER);
+  EXPECT_EQ(matches[3].allowed_to_be_default_match, false);
   EXPECT_EQ(matches[4].type, AutocompleteMatchType::HISTORY_CLUSTER);
+  EXPECT_EQ(matches[4].allowed_to_be_default_match, false);
   EXPECT_EQ(matches[5].type, AutocompleteMatchType::HISTORY_CLUSTER);
+  EXPECT_EQ(matches[5].allowed_to_be_default_match, false);
   EXPECT_EQ(matches[6].type, AutocompleteMatchType::HISTORY_CLUSTER);
+  EXPECT_EQ(matches[6].allowed_to_be_default_match, false);
 
   // Expect only non-cluster matches to have capped decrementing scores.
   EXPECT_EQ(matches[1].relevance, matches[0].relevance - 1);
@@ -908,10 +916,20 @@
   history_clusters::SetConfigForTesting(config);
   provider_->Start(input, false);
   const auto matches_with_free_ranking = provider_->matches();
-  ASSERT_EQ(matches.size(), matches_with_free_ranking.size());
+  ASSERT_EQ(matches_with_free_ranking.size(), matches.size());
   for (size_t i = 0; i < matches.size(); ++i) {
-    EXPECT_EQ(matches[i].contents, matches_with_free_ranking[i].contents);
+    EXPECT_EQ(matches_with_free_ranking[i].contents, matches[i].contents);
     EXPECT_EQ(matches_with_free_ranking[i].suggestion_group_id, absl::nullopt);
   }
+
+  // With `omnibox_history_cluster_provider_allow_default`, should be allowed
+  // default.
+  config.omnibox_history_cluster_provider_allow_default = true;
+  history_clusters::SetConfigForTesting(config);
+  provider_->Start(input, false);
+  const auto matches_with_allow_default = provider_->matches();
+  ASSERT_EQ(matches.size(), matches.size());
+  for (const auto& m : matches_with_allow_default)
+    EXPECT_TRUE(m.allowed_to_be_default_match);
 }
 #endif  // !BUILDFLAG(IS_IOS)
diff --git a/components/omnibox/browser/suggestion_group_util.cc b/components/omnibox/browser/suggestion_group_util.cc
index f4a8a19..d4f3567d 100644
--- a/components/omnibox/browser/suggestion_group_util.cc
+++ b/components/omnibox/browser/suggestion_group_util.cc
@@ -6,18 +6,25 @@
 
 #include "third_party/omnibox_proto/groups.pb.h"
 
+namespace {
+omnibox::GroupConfig CreateGroup(omnibox::GroupSection section) {
+  omnibox::GroupConfig group;
+  group.set_section(section);
+  return group;
+}
+}  // namespace
+
 namespace omnibox {
 
-omnibox::GroupConfigMap BuildDefaultGroups() {
-  omnibox::GroupConfigMap groups;
-  groups[omnibox::GROUP_MOBILE_SEARCH_READY_OMNIBOX].set_section(
-      omnibox::SECTION_MOBILE_VERBATIM);
-  groups[omnibox::GROUP_MOBILE_MOST_VISITED].set_section(
-      omnibox::SECTION_MOBILE_MOST_VISITED);
-  groups[omnibox::GROUP_MOBILE_CLIPBOARD].set_section(
-      omnibox::SECTION_MOBILE_CLIPBOARD);
-  groups[omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST].set_section(
-      omnibox::SECTION_PERSONALIZED_ZERO_SUGGEST);
+const omnibox::GroupConfigMap& BuildDefaultGroups() {
+  static omnibox::GroupConfigMap groups = {
+      // clang-format off
+      {omnibox::GROUP_MOBILE_SEARCH_READY_OMNIBOX, CreateGroup(omnibox::SECTION_MOBILE_VERBATIM)},
+      {omnibox::GROUP_MOBILE_MOST_VISITED,         CreateGroup(omnibox::SECTION_MOBILE_MOST_VISITED)},
+      {omnibox::GROUP_MOBILE_CLIPBOARD,            CreateGroup(omnibox::SECTION_MOBILE_CLIPBOARD)},
+      {omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST,   CreateGroup(omnibox::SECTION_PERSONALIZED_ZERO_SUGGEST)},
+      // clang-format on
+  };
   return groups;
 }
 
diff --git a/components/omnibox/browser/suggestion_group_util.h b/components/omnibox/browser/suggestion_group_util.h
index 7349f06..1b797c9 100644
--- a/components/omnibox/browser/suggestion_group_util.h
+++ b/components/omnibox/browser/suggestion_group_util.h
@@ -12,7 +12,7 @@
 using GroupConfigMap = std::unordered_map<GroupId, GroupConfig>;
 
 // Builds the pre-defined static groups that are useful for sorting suggestions.
-omnibox::GroupConfigMap BuildDefaultGroups();
+const omnibox::GroupConfigMap& BuildDefaultGroups();
 
 // Returns the omnibox::GroupId enum object corresponding to |value|, or
 // omnibox::GROUP_INVALID when there is no corresponding enum object.
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index dd6e70f..63b1431 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -339,7 +339,7 @@
 // request should be served by the  ASO backend.
 BASE_FEATURE(kDocumentProviderAso,
              "OmniboxDocumentProviderAso",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Feature to determine if the HQP should double as a domain provider by
 // suggesting up to the provider limit for each of the user's highly visited
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 9327565b..15da9d9 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -927,9 +927,9 @@
 // any specific interface, or may be visible across multiple interfaces.
 message NetworkState {
   // The current state of this network.
-  // OFFLINE (7) is not used by the client.
   enum ConnectionState {
     reserved 1;   // CARRIER
+    reserved 6;   // OFFLINE
     reserved 10;  // ACTIVATION_FAILURE
 
     IDLE = 0;
@@ -937,7 +937,6 @@
     CONFIGURATION = 3;
     READY = 4;
     PORTAL = 5;
-    OFFLINE = 6;
     ONLINE = 7;
     DISCONNECT = 8;
     FAILURE = 9;
diff --git a/components/segmentation_platform/embedder/default_model/cross_device_user_segment.cc b/components/segmentation_platform/embedder/default_model/cross_device_user_segment.cc
index 68676fa..3e47c66 100644
--- a/components/segmentation_platform/embedder/default_model/cross_device_user_segment.cc
+++ b/components/segmentation_platform/embedder/default_model/cross_device_user_segment.cc
@@ -132,6 +132,7 @@
       base::Days(kCrossDeviceUserSegmentSelectionTTLDays);
   config->unknown_selection_ttl =
       base::Days(kCrossDeviceUserSegmentUnknownSelectionTTLDays);
+  config->is_boolean_segment = true;
   return config;
 }
 
diff --git a/components/segmentation_platform/embedder/default_model/feed_user_segment.cc b/components/segmentation_platform/embedder/default_model/feed_user_segment.cc
index e4363ecd..4293afa 100644
--- a/components/segmentation_platform/embedder/default_model/feed_user_segment.cc
+++ b/components/segmentation_platform/embedder/default_model/feed_user_segment.cc
@@ -175,6 +175,7 @@
           features::kSegmentationPlatformFeedSegmentFeature,
           kVariationsParamNameUnknownSelectionTTLDays,
           kFeedUserSegmentUnknownSelectionTTLDays));
+  config->is_boolean_segment = true;
   return config;
 }
 
diff --git a/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.cc b/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.cc
index aff48f7..f92cfbf 100644
--- a/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.cc
+++ b/components/segmentation_platform/embedder/default_model/frequent_feature_user_model.cc
@@ -69,6 +69,7 @@
                        std::make_unique<FrequentFeatureUserModel>());
   config->segment_selection_ttl = base::Days(7);
   config->unknown_selection_ttl = base::Days(7);
+  config->is_boolean_segment = true;
 
   return config;
 }
diff --git a/components/segmentation_platform/embedder/default_model/intentional_user_model.cc b/components/segmentation_platform/embedder/default_model/intentional_user_model.cc
index 83bca03..2fbbcb9a 100644
--- a/components/segmentation_platform/embedder/default_model/intentional_user_model.cc
+++ b/components/segmentation_platform/embedder/default_model/intentional_user_model.cc
@@ -62,6 +62,7 @@
                        std::make_unique<IntentionalUserModel>());
   config->segment_selection_ttl = base::Days(7);
   config->unknown_selection_ttl = base::Days(7);
+  config->is_boolean_segment = true;
 
   return config;
 }
diff --git a/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
index 81929db..bd58bdf 100644
--- a/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
+++ b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
@@ -6,9 +6,12 @@
 
 #include <array>
 
+#include "base/metrics/field_trial_params.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/segmentation_platform/internal/metadata/metadata_writer.h"
+#include "components/segmentation_platform/public/config.h"
 #include "components/segmentation_platform/public/constants.h"
+#include "components/segmentation_platform/public/features.h"
 
 namespace segmentation_platform {
 
@@ -37,6 +40,26 @@
 LowUserEngagementModel::LowUserEngagementModel()
     : ModelProvider(kChromeStartSegmentId) {}
 
+std::unique_ptr<Config> LowUserEngagementModel::GetConfig() {
+  auto config = std::make_unique<Config>();
+  config->segmentation_key = kChromeLowUserEngagementSegmentationKey;
+  config->segmentation_uma_name = kChromeLowUserEngagementUmaName;
+  config->AddSegmentId(kChromeStartSegmentId,
+                       std::make_unique<LowUserEngagementModel>());
+
+  int segment_selection_ttl_days = base::GetFieldTrialParamByFeatureAsInt(
+      features::kSegmentationPlatformLowEngagementFeature,
+      kVariationsParamNameSegmentSelectionTTLDays, 7);
+  int unknown_selection_ttl_days = base::GetFieldTrialParamByFeatureAsInt(
+      features::kSegmentationPlatformLowEngagementFeature,
+      kVariationsParamNameUnknownSelectionTTLDays, 7);
+  config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
+  config->unknown_selection_ttl = base::Days(unknown_selection_ttl_days);
+  config->is_boolean_segment = true;
+
+  return config;
+}
+
 void LowUserEngagementModel::InitAndFetchModel(
     const ModelUpdatedCallback& model_updated_callback) {
   proto::SegmentationModelMetadata chrome_start_metadata;
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 c13b40b..6bffe09 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
@@ -9,6 +9,8 @@
 
 namespace segmentation_platform {
 
+struct Config;
+
 // Segmentation low engagement model provider. Provides a default model and
 // metadata for the low user engagement optimization target.
 class LowUserEngagementModel : public ModelProvider {
@@ -20,6 +22,8 @@
   LowUserEngagementModel(LowUserEngagementModel&) = delete;
   LowUserEngagementModel& operator=(LowUserEngagementModel&) = delete;
 
+  static std::unique_ptr<Config> GetConfig();
+
   // ModelProvider implementation.
   void InitAndFetchModel(
       const ModelUpdatedCallback& model_updated_callback) override;
diff --git a/components/segmentation_platform/embedder/default_model/power_user_segment.cc b/components/segmentation_platform/embedder/default_model/power_user_segment.cc
index 325b8677..7339585 100644
--- a/components/segmentation_platform/embedder/default_model/power_user_segment.cc
+++ b/components/segmentation_platform/embedder/default_model/power_user_segment.cc
@@ -181,6 +181,7 @@
                        std::make_unique<PowerUserSegment>());
   config->segment_selection_ttl = base::Days(7);
   config->unknown_selection_ttl = base::Days(7);
+  config->is_boolean_segment = true;
 
   return config;
 }
diff --git a/components/segmentation_platform/embedder/default_model/query_tiles_model.cc b/components/segmentation_platform/embedder/default_model/query_tiles_model.cc
index 5e5d7c2..77299e36 100644
--- a/components/segmentation_platform/embedder/default_model/query_tiles_model.cc
+++ b/components/segmentation_platform/embedder/default_model/query_tiles_model.cc
@@ -77,6 +77,7 @@
       kNumDaysMVCkicksBelowThreshold, kQueryTilesDefaultUnknownTTLDays);
   config->segment_selection_ttl = base::Days(segment_selection_ttl_days);
   config->unknown_selection_ttl = base::Days(unknown_selection_ttl_days);
+  config->is_boolean_segment = true;
   return config;
 }
 
diff --git a/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.cc b/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.cc
index 476065e..20ae12d2 100644
--- a/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.cc
+++ b/components/segmentation_platform/embedder/default_model/resume_heavy_user_model.cc
@@ -59,6 +59,8 @@
           features::kResumeHeavyUserSegmentFeature,
           kVariationsParamNameUnknownSelectionTTLDays,
           kResumeHeavyUserSegmentUnknownSelectionTTLDays));
+  config->is_boolean_segment = true;
+
   return config;
 }
 
diff --git a/components/segmentation_platform/embedder/default_model/shopping_user_model.cc b/components/segmentation_platform/embedder/default_model/shopping_user_model.cc
index 1e2eb6e..9620063a 100644
--- a/components/segmentation_platform/embedder/default_model/shopping_user_model.cc
+++ b/components/segmentation_platform/embedder/default_model/shopping_user_model.cc
@@ -75,6 +75,7 @@
           features::kShoppingUserSegmentFeature,
           kVariationsParamNameUnknownSelectionTTLDays,
           kShoppingUserDefaultUnknownSelectionTTLDays));
+  config->is_boolean_segment = true;
   return config;
 }
 
diff --git a/components/segmentation_platform/internal/stats.cc b/components/segmentation_platform/internal/stats.cc
index 6ac3e31..fde6630 100644
--- a/components/segmentation_platform/internal/stats.cc
+++ b/components/segmentation_platform/internal/stats.cc
@@ -234,7 +234,7 @@
     base::UmaHistogramEnumeration(
         switched_hist,
         GetAdaptiveToolbarSegmentSwitch(new_selection, prev_segment));
-  } else if (config.IsBooleanSegment()) {
+  } else if (config.is_boolean_segment) {
     base::UmaHistogramEnumeration(
         switched_hist, GetBooleanSegmentSwitch(new_selection, prev_segment));
   }
diff --git a/components/segmentation_platform/internal/stats_unittest.cc b/components/segmentation_platform/internal/stats_unittest.cc
index a6697407..a52f9bdbb 100644
--- a/components/segmentation_platform/internal/stats_unittest.cc
+++ b/components/segmentation_platform/internal/stats_unittest.cc
@@ -107,6 +107,7 @@
   config.segmentation_key = kChromeStartAndroidSegmentationKey;
   config.segmentation_uma_name =
       SegmentationKeyToUmaName(config.segmentation_key);
+  config.is_boolean_segment = true;
 
   // Start to none.
   RecordSegmentSelectionComputed(
diff --git a/components/segmentation_platform/public/config.cc b/components/segmentation_platform/public/config.cc
index b5ee085..2ab2635 100644
--- a/components/segmentation_platform/public/config.cc
+++ b/components/segmentation_platform/public/config.cc
@@ -58,22 +58,4 @@
   return it->second->uma_name;
 }
 
-bool Config::IsBooleanSegment() const {
-  // TODO(haileywang): Make this a boolean member in Config and set this from
-  // each model.
-  // Please keep in sync with BooleanModel variant in
-  // //tools/metrics/histograms/metadata/segmentation_platform/histograms.xml.
-  return segmentation_key == kChromeStartAndroidSegmentationKey ||
-         segmentation_key == kChromeStartAndroidV2SegmentationKey ||
-         segmentation_key == kQueryTilesSegmentationKey ||
-         segmentation_key == kChromeLowUserEngagementSegmentationKey ||
-         segmentation_key == kFeedUserSegmentationKey ||
-         segmentation_key == kPowerUserKey ||
-         segmentation_key == kShoppingUserSegmentationKey ||
-         segmentation_key == kCrossDeviceUserKey ||
-         segmentation_key == kFrequentFeatureUserKey ||
-         segmentation_key == kIntentionalUserKey ||
-         segmentation_key == kResumeHeavyUserKey;
-}
-
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/public/config.h b/components/segmentation_platform/public/config.h
index facbff0..83335e6 100644
--- a/components/segmentation_platform/public/config.h
+++ b/components/segmentation_platform/public/config.h
@@ -97,8 +97,9 @@
   // Returns the segment name for the `segment` used by the metrics.
   std::string GetSegmentUmaName(proto::SegmentId segment) const;
 
-  // Returns whether the segment is a boolean model.
-  bool IsBooleanSegment() const;
+  // Whether the segment is a boolean model.
+  // TODO(haileywang): update config_parser to include this field.
+  bool is_boolean_segment = false;
 };
 
 }  // namespace segmentation_platform
diff --git a/components/session_proto_db/BUILD.gn b/components/session_proto_db/BUILD.gn
index ba2bcb1..6106048 100644
--- a/components/session_proto_db/BUILD.gn
+++ b/components/session_proto_db/BUILD.gn
@@ -24,23 +24,27 @@
   ]
 }
 
-source_set("unit_tests") {
-  testonly = true
-  sources = [ "session_proto_db_unittest.cc" ]
+# TODO(crbug.com/1399914) Make tests more general and run on iOS trybot.
+if (!is_ios) {
+  source_set("unit_tests") {
+    testonly = true
+    sources = [ "session_proto_db_unittest.cc" ]
 
-  deps = [
-    ":core",
-    ":session_proto_db",
-    ":session_proto_db_test_proto",
-    "//base:base",
-    "//base/test:test_support",
-    "//components/commerce/core:persisted_state_db_content_proto",
-    "//components/leveldb_proto:test_support",
-    "//testing/gmock",
-    "//testing/gtest",
-    "//third_party/leveldatabase",
-    "//third_party/protobuf:protobuf_lite",
-  ]
+    deps = [
+      ":core",
+      ":session_proto_db",
+      ":session_proto_db_test_proto",
+      "//base:base",
+      "//base/test:test_support",
+      "//components/commerce/core:persisted_state_db_content_proto",
+      "//components/leveldb_proto:test_support",
+      "//content/test:test_support",
+      "//testing/gmock",
+      "//testing/gtest",
+      "//third_party/leveldatabase",
+      "//third_party/protobuf:protobuf_lite",
+    ]
+  }
 }
 
 proto_library("session_proto_db_test_proto") {
diff --git a/components/session_proto_db/DEPS b/components/session_proto_db/DEPS
index fb58562..675cc33 100644
--- a/components/session_proto_db/DEPS
+++ b/components/session_proto_db/DEPS
@@ -3,5 +3,6 @@
   "+components/keyed_service",
   "+components/leveldb_proto",
   "+content/public/browser",
+  "+content/public/test",
   "+third_party/leveldatabase",
 ]
diff --git a/components/session_proto_db/session_proto_db.h b/components/session_proto_db/session_proto_db.h
index 71d465e..22f0c0b7c 100644
--- a/components/session_proto_db/session_proto_db.h
+++ b/components/session_proto_db/session_proto_db.h
@@ -67,9 +67,11 @@
   using ContentEntry = typename leveldb_proto::ProtoDatabase<T>::KeyEntryVector;
 
   // Initializes the database.
-  SessionProtoDB(leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
-                 const base::FilePath& database_dir,
-                 leveldb_proto::ProtoDbType proto_db_type);
+  SessionProtoDB(
+      leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
+      const base::FilePath& database_dir,
+      leveldb_proto::ProtoDbType proto_db_type,
+      scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner);
 
   SessionProtoDB(const SessionProtoDB&) = delete;
   SessionProtoDB& operator=(const SessionProtoDB&) = delete;
@@ -109,7 +111,8 @@
   // Used for testing.
   SessionProtoDB(
       std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
-      scoped_refptr<base::SequencedTaskRunner> task_runner);
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner);
 
   // Passes back database status following database initialization.
   void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
@@ -153,6 +156,9 @@
   // |deferred_operations_| is flushed and all operations are executed.
   std::vector<base::OnceClosure> deferred_operations_;
 
+  // Task Runner for posting tasks to UI thread.
+  scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_;
+
   base::WeakPtrFactory<SessionProtoDB> weak_ptr_factory_{this};
 };
 
@@ -160,14 +166,16 @@
 SessionProtoDB<T>::SessionProtoDB(
     leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
     const base::FilePath& database_dir,
-    leveldb_proto::ProtoDbType proto_db_type)
+    leveldb_proto::ProtoDbType proto_db_type,
+    scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
     : SessionProtoStorage<T>(),
       database_status_(absl::nullopt),
       storage_database_(proto_database_provider->GetDB<T>(
           proto_db_type,
           database_dir,
           base::ThreadPool::CreateSequencedTaskRunner(
-              {base::MayBlock(), base::TaskPriority::USER_VISIBLE}))) {
+              {base::MayBlock(), base::TaskPriority::USER_VISIBLE}))),
+      ui_thread_task_runner_(ui_thread_task_runner) {
   static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
                 "T must implement 'google::protobuf::MessageLite'");
   storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
@@ -185,7 +193,7 @@
         &SessionProtoDB::LoadOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
         std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(
+    ui_thread_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
   } else {
@@ -203,7 +211,7 @@
         base::BindOnce(&SessionProtoDB::LoadAllEntries,
                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(
+    ui_thread_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
   } else {
@@ -221,7 +229,7 @@
         &SessionProtoDB::LoadContentWithPrefix, weak_ptr_factory_.GetWeakPtr(),
         key_prefix, std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(
+    ui_thread_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
   } else {
@@ -244,8 +252,8 @@
         &SessionProtoDB::PerformMaintenance, weak_ptr_factory_.GetWeakPtr(),
         keys_to_keep, key_substring_to_match, std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(FROM_HERE,
-                               base::BindOnce(std::move(callback), false));
+    ui_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
   } else {
     // The following could be achieved with UpdateEntriesWithRemoveFilter rather
     // than LoadEntriesWithFilter followed by UpdateEntries, however, that would
@@ -279,8 +287,8 @@
         &SessionProtoDB::InsertContent, weak_ptr_factory_.GetWeakPtr(), key,
         std::move(value), std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(FROM_HERE,
-                               base::BindOnce(std::move(callback), false));
+    ui_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
   } else {
     auto contents_to_save = std::make_unique<ContentEntry>();
     contents_to_save->emplace_back(key, value);
@@ -300,8 +308,8 @@
         &SessionProtoDB::DeleteOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
         std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(FROM_HERE,
-                               base::BindOnce(std::move(callback), false));
+    ui_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
   } else {
     auto keys = std::make_unique<std::vector<std::string>>();
     keys->push_back(key);
@@ -322,8 +330,9 @@
         &SessionProtoDB::DeleteContentWithPrefix,
         weak_ptr_factory_.GetWeakPtr(), key_prefix, std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(FROM_HERE,
-                               base::BindOnce(std::move(callback), false));
+    ui_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
+
   } else {
     storage_database_->UpdateEntriesWithRemoveFilter(
         std::make_unique<ContentEntry>(),
@@ -341,8 +350,8 @@
         base::BindOnce(&SessionProtoDB::DeleteAllContent,
                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   } else if (FailedToInit()) {
-    base::ThreadPool::PostTask(FROM_HERE,
-                               base::BindOnce(std::move(callback), false));
+    ui_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
   } else {
     storage_database_->Destroy(std::move(callback));
   }
@@ -359,10 +368,12 @@
 template <typename T>
 SessionProtoDB<T>::SessionProtoDB(
     std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
-    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
     : SessionProtoStorage<T>(),
       database_status_(absl::nullopt),
-      storage_database_(std::move(storage_database)) {
+      storage_database_(std::move(storage_database)),
+      ui_thread_task_runner_(ui_thread_task_runner) {
   static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
                 "T must implement 'google::protobuf::MessageLite'");
   storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
diff --git a/components/session_proto_db/session_proto_db_unittest.cc b/components/session_proto_db/session_proto_db_unittest.cc
index f590840b..3b2b0c2 100644
--- a/components/session_proto_db/session_proto_db_unittest.cc
+++ b/components/session_proto_db/session_proto_db_unittest.cc
@@ -11,9 +11,11 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/task/thread_pool.h"
-#include "base/test/task_environment.h"
 #include "components/leveldb_proto/testing/fake_db.h"
 #include "components/session_proto_db/session_proto_db_test_proto.pb.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -111,7 +113,8 @@
         new SessionProtoDB<persisted_state_db::PersistedStateContentProto>(
             std::move(storage_db),
             base::ThreadPool::CreateSequencedTaskRunner(
-                {base::MayBlock(), base::TaskPriority::USER_VISIBLE})));
+                {base::MayBlock(), base::TaskPriority::USER_VISIBLE}),
+            content::GetUIThreadTaskRunner({})));
   }
 
   void MockInitCallbackPersistedStateDB(
@@ -193,7 +196,8 @@
         new SessionProtoDB<session_proto_db::SessionProtoDBTestProto>(
             std::move(storage_db),
             base::ThreadPool::CreateSequencedTaskRunner(
-                {base::MayBlock(), base::TaskPriority::USER_VISIBLE})));
+                {base::MayBlock(), base::TaskPriority::USER_VISIBLE}),
+            content::GetUIThreadTaskRunner({})));
   }
 
   void GetTestEvaluationTestProtoDB(
@@ -307,7 +311,7 @@
       test_content_db_;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  content::BrowserTaskEnvironment task_environment_;
 
   // For persisted_state_db::PersistedStateContentProto database
   raw_ptr<leveldb_proto::test::FakeDB<
diff --git a/components/signin/ios/browser/features.cc b/components/signin/ios/browser/features.cc
index a734b1f..0ee35350 100644
--- a/components/signin/ios/browser/features.cc
+++ b/components/signin/ios/browser/features.cc
@@ -24,6 +24,6 @@
 
 BASE_FEATURE(kEnableUnicornAccountSupport,
              "EnableUnicornAccountSupport",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 }  // namespace signin
diff --git a/components/site_engagement/core/site_engagement_score_provider.h b/components/site_engagement/core/site_engagement_score_provider.h
index e22422bd..e13dfba 100644
--- a/components/site_engagement/core/site_engagement_score_provider.h
+++ b/components/site_engagement/core/site_engagement_score_provider.h
@@ -17,6 +17,10 @@
 
   // Returns the sum of engagement points awarded to all sites.
   virtual double GetTotalEngagementPoints() const = 0;
+
+ protected:
+  SiteEngagementScoreProvider() = default;
+  virtual ~SiteEngagementScoreProvider() = default;
 };
 
 }  // namespace site_engagement
diff --git a/components/soda/soda_installer.cc b/components/soda/soda_installer.cc
index a565af2..63564910 100644
--- a/components/soda/soda_installer.cc
+++ b/components/soda/soda_installer.cc
@@ -15,6 +15,7 @@
 #include "components/soda/constants.h"
 #include "components/soda/pref_names.h"
 #include "media/base/media_switches.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_features.h"
@@ -232,12 +233,14 @@
 }
 
 void SodaInstaller::NotifyOnSodaInstalled(LanguageCode language_code) {
+  error_codes_.erase(language_code);
   for (Observer& observer : observers_)
     observer.OnSodaInstalled(language_code);
 }
 
 void SodaInstaller::NotifyOnSodaInstallError(LanguageCode language_code,
                                              ErrorCode error_code) {
+  error_codes_[language_code] = error_code;
   for (Observer& observer : observers_)
     observer.OnSodaInstallError(language_code, error_code);
 }
@@ -268,6 +271,17 @@
          base::Contains(language_pack_progress_, language_code);
 }
 
+absl::optional<SodaInstaller::ErrorCode> SodaInstaller::GetSodaInstallErrorCode(
+    LanguageCode language_code) const {
+  if (IsSodaDownloading(language_code))
+    return absl::nullopt;
+
+  const auto error_code = error_codes_.find(language_code);
+  if (error_code != error_codes_.end())
+    return error_code->second;
+  return absl::nullopt;
+}
+
 bool SodaInstaller::IsAnyFeatureUsingSodaEnabled(PrefService* prefs) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   return prefs->GetBoolean(prefs::kLiveCaptionEnabled) ||
diff --git a/components/soda/soda_installer.h b/components/soda/soda_installer.h
index 7b918b40..e019307f 100644
--- a/components/soda/soda_installer.h
+++ b/components/soda/soda_installer.h
@@ -110,6 +110,11 @@
   // Method for checking in-progress downloads.
   bool IsSodaDownloading(LanguageCode language_code) const;
 
+  // Returns the error encountered while installing soda for the language code
+  // or soda binary.
+  absl::optional<ErrorCode> GetSodaInstallErrorCode(
+      LanguageCode language_code) const;
+
   // TODO(crbug.com/1237462): Consider creating a MockSodaInstaller class that
   // implements these test-specific methods.
   void NeverDownloadSodaForTesting() {
@@ -178,6 +183,9 @@
   // Maps language codes to their install progress.
   base::flat_map<LanguageCode, double> language_pack_progress_;
 
+  // The error state for the language code.
+  base::flat_map<LanguageCode, ErrorCode> error_codes_;
+
  private:
   friend class SodaInstallerImplChromeOSTest;
   friend class SodaInstallerImplTest;
diff --git a/components/ukm/BUILD.gn b/components/ukm/BUILD.gn
index 5bff617..fbdddb9d 100644
--- a/components/ukm/BUILD.gn
+++ b/components/ukm/BUILD.gn
@@ -94,6 +94,7 @@
     "//components/sync/base",
     "//components/sync/driver",
     "//google_apis",
+    "//services/metrics/public/cpp:metrics_cpp",
   ]
 
   public_deps = [ "//components/unified_consent" ]
diff --git a/components/ukm/debug/ukm_debug_data_extractor.cc b/components/ukm/debug/ukm_debug_data_extractor.cc
index e52a95dc..809a719 100644
--- a/components/ukm/debug/ukm_debug_data_extractor.cc
+++ b/components/ukm/debug/ukm_debug_data_extractor.cc
@@ -87,6 +87,9 @@
   base::Value::Dict ukm_data;
 
   ukm_data.Set("state", ukm_service->recording_enabled_);
+  ukm_data.Set("msbb_state", ukm_service->recording_enabled(MSBB));
+  ukm_data.Set("extension_state", ukm_service->recording_enabled(EXTENSIONS));
+  ukm_data.Set("app_state", ukm_service->recording_enabled(APPS));
   ukm_data.Set("client_id",
                base::StringPrintf("%016" PRIx64, ukm_service->client_id_));
   ukm_data.Set("session_id", static_cast<int>(ukm_service->session_id_));
diff --git a/components/ukm/debug/ukm_internals.html b/components/ukm/debug/ukm_internals.html
index da485de..7ee1da72 100644
--- a/components/ukm/debug/ukm_internals.html
+++ b/components/ukm/debug/ukm_internals.html
@@ -10,12 +10,14 @@
   <title>UKM Debug Page</title>
   <div class="ukm-collection-status">
     <div>
-      <!-- TODO: show more detailed consent states, e.g. MSBB, Extensions, Apps. -->
-      <div>Metrics Collection is <span id="state"/></div>
-      <div>Session ID: <span id="sessionid"/></div>
-      <div>Client ID: <span id="clientid"/></div>
-      <div>Event Sampling Enabled: <span id="is_sampling_enabled"/></div>
-      <div>NOTE FOR MOBILE: This only shows events since the most recent foregrounding.</div>
+      <div> Metrics Collection is <span id="state"></div>
+      <div> MSBB consent is <span id="msbb_state"></div>
+      <div> Extension consent is <span id="extension_state"></div>
+      <div> App consent is <span id="app_state"></div>
+      <div> Session: <span id="sessionid"></div>
+      <div> Client_id: <span id="clientid"></div>
+      <div> Event Sampling Enabled: <span id="is_sampling_enabled"></div>
+      <div> NOTE FOR MOBILE: This only shows events since the most recent foregrounding.</div>
     </div>
   </div>
   <div>
diff --git a/components/ukm/debug/ukm_internals.ts b/components/ukm/debug/ukm_internals.ts
index d38d063..6bfc0c0 100644
--- a/components/ukm/debug/ukm_internals.ts
+++ b/components/ukm/debug/ukm_internals.ts
@@ -33,6 +33,9 @@
  */
 interface UkmSession {
   state: boolean;
+  msbb_state: boolean;
+  extension_state: boolean;
+  app_state: boolean;
   client_id: number[];
   session_id: string;
   sources: UkmSource[];
@@ -398,6 +401,12 @@
       data.sources = [...cachedSources.values()];
     }
     getRequiredElement('state').innerText = data.state ? 'ENABLED' : 'DISABLED';
+    getRequiredElement('msbb_state').innerText =
+        data.msbb_state ? 'ENABLED' : 'DISABLED';
+    getRequiredElement('extension_state').innerText =
+        data.extension_state ? 'ENABLED' : 'DISABLED';
+    getRequiredElement('app_state').innerText =
+        data.app_state ? 'ENABLED' : 'DISABLED';
     getRequiredElement('clientid').innerText = '0x' + data.client_id;
     getRequiredElement('sessionid').innerText = data.session_id;
     getRequiredElement('is_sampling_enabled').innerText =
diff --git a/components/ukm/observers/ukm_consent_state_observer.cc b/components/ukm/observers/ukm_consent_state_observer.cc
index 6ecdba4..c1d0b44 100644
--- a/components/ukm/observers/ukm_consent_state_observer.cc
+++ b/components/ukm/observers/ukm_consent_state_observer.cc
@@ -9,7 +9,7 @@
 
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_service_utils.h"
@@ -19,7 +19,6 @@
 using unified_consent::UrlKeyedDataCollectionConsentHelper;
 
 namespace ukm {
-
 namespace {
 
 bool CanUploadUkmForType(syncer::SyncService* sync_service,
@@ -35,9 +34,12 @@
       return true;
   }
 }
-
 }  // namespace
 
+BASE_FEATURE(kAppMetricsOnlyRelyOnAppSync,
+             "AppMetricsOnlyRelyOnAppSync",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 UkmConsentStateObserver::UkmConsentStateObserver() = default;
 
 UkmConsentStateObserver::~UkmConsentStateObserver() {
@@ -47,7 +49,11 @@
 }
 
 bool UkmConsentStateObserver::ProfileState::IsUkmConsented() const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  return consent_state.Has(MSBB) || consent_state.Has(APPS);
+#else
   return consent_state.Has(MSBB);
+#endif
 }
 
 void UkmConsentStateObserver::ProfileState::SetConsentType(
@@ -64,16 +70,20 @@
   ProfileState state;
 
   const bool msbb_consent = consent_helper->IsEnabled();
+
   if (msbb_consent)
     state.SetConsentType(MSBB);
 
   if (msbb_consent &&
-      CanUploadUkmForType(sync_service, syncer::ModelType::EXTENSIONS))
+      CanUploadUkmForType(sync_service, syncer::ModelType::EXTENSIONS)) {
     state.SetConsentType(EXTENSIONS);
+  }
 
-  if (msbb_consent &&
-      CanUploadUkmForType(sync_service, syncer::ModelType::APPS))
+  if ((msbb_consent ||
+       base::FeatureList::IsEnabled(kAppMetricsOnlyRelyOnAppSync)) &&
+      CanUploadUkmForType(sync_service, syncer::ModelType::APPS)) {
     state.SetConsentType(APPS);
+  }
 
   return state;
 }
@@ -94,16 +104,22 @@
 }
 
 void UkmConsentStateObserver::UpdateUkmAllowedForAllProfiles(bool total_purge) {
-  const UkmConsentState previous_state = GetPreviousStatesForAllProfiles();
+  const UkmConsentState new_state = GetPreviousStatesForAllProfiles();
 
-  UMA_HISTOGRAM_BOOLEAN("UKM.ConsentObserver.AllowedForAllProfiles",
-                        previous_state.Has(MSBB));
+  base::UmaHistogramBoolean("UKM.ConsentObserver.AllowedForAllProfiles",
+                            new_state.Has(MSBB));
 
   // Any change in profile states needs to call OnUkmAllowedStateChanged so that
   // the new settings take effect.
-  if (total_purge || previous_state != ukm_consent_state_) {
-    ukm_consent_state_ = previous_state;
-    OnUkmAllowedStateChanged(total_purge);
+  if (total_purge || new_state != ukm_consent_state_) {
+    // Records whether the App sync consent changed when the consent state is
+    // updated. This is to see how often App sync is changed by users.
+    base::UmaHistogramBoolean(
+        "UKM.ConsentObserver.AppSyncConsentChanged",
+        ukm_consent_state_.Has(APPS) != new_state.Has(APPS));
+    const auto previous_consent_state = ukm_consent_state_;
+    ukm_consent_state_ = new_state;
+    OnUkmAllowedStateChanged(total_purge, previous_consent_state);
   }
 }
 
@@ -160,7 +176,7 @@
   // allows tracking UKM.
   bool total_purge = previous_state.IsUkmConsented() && !state.IsUkmConsented();
 
-  UMA_HISTOGRAM_BOOLEAN("UKM.ConsentObserver.Purge", total_purge);
+  base::UmaHistogramBoolean("UKM.ConsentObserver.Purge", total_purge);
 
   previous_states_[sync] = state;
   UpdateUkmAllowedForAllProfiles(total_purge);
@@ -180,7 +196,11 @@
 }
 
 bool UkmConsentStateObserver::IsUkmAllowedForAllProfiles() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  return ukm_consent_state_.Has(MSBB) || ukm_consent_state_.Has(APPS);
+#else
   return ukm_consent_state_.Has(MSBB);
+#endif
 }
 
 UkmConsentState UkmConsentStateObserver::GetUkmConsentState() {
diff --git a/components/ukm/observers/ukm_consent_state_observer.h b/components/ukm/observers/ukm_consent_state_observer.h
index 8234e18..28b573e 100644
--- a/components/ukm/observers/ukm_consent_state_observer.h
+++ b/components/ukm/observers/ukm_consent_state_observer.h
@@ -14,11 +14,15 @@
 #include "components/sync/driver/sync_service_observer.h"
 #include "components/ukm/ukm_consent_state.h"
 #include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+#include "services/metrics/public/cpp/metrics_export.h"
 
 class PrefService;
 
 namespace ukm {
 
+// This feature controls whether App Sync relies on MSBB to be enabled.
+BASE_DECLARE_FEATURE(kAppMetricsOnlyRelyOnAppSync);
+
 // Observer that monitors whether UKM is allowed for all profiles.
 //
 // For one profile, UKM is allowed iff URL-keyed anonymized data collection is
@@ -53,7 +57,9 @@
   // local data must be purged. Otherwise, more specific consents are checked
   // for individual sync settings, and recorded data may be partially purged if
   // we no longer have the corresponding sync consent.
-  virtual void OnUkmAllowedStateChanged(bool total_purge) = 0;
+  virtual void OnUkmAllowedStateChanged(
+      bool total_purge,
+      UkmConsentState previous_consent_state) = 0;
 
  private:
   // syncer::SyncServiceObserver:
@@ -133,4 +139,4 @@
 
 }  // namespace ukm
 
-#endif  // COMPONENTS_UKM_OBSERVERS_UKM_CONSENT_STATE_OBSERVER_H_
+#endif  // COMPONENTS_UKM_OBSERVERS_UKM_CONSENT_STATE_OBSERVER_H_
\ No newline at end of file
diff --git a/components/ukm/observers/ukm_consent_state_observer_unittest.cc b/components/ukm/observers/ukm_consent_state_observer_unittest.cc
index 0bcbe8b..eb43e99 100644
--- a/components/ukm/observers/ukm_consent_state_observer_unittest.cc
+++ b/components/ukm/observers/ukm_consent_state_observer_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/ukm/observers/ukm_consent_state_observer.h"
 
 #include "base/observer_list.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/sync/driver/sync_token_status.h"
 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
 #include "components/sync/test/test_sync_service.h"
@@ -49,6 +50,10 @@
         sync_pb::SyncEnums::UNKNOWN_ORIGIN, base::Minutes(1), false));
 
     NotifyObserversOfStateChanged();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    SetAppSync(false);
+#endif
   }
 
   void Shutdown() override {
@@ -57,6 +62,21 @@
     }
   }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  void SetAppSync(bool enabled) {
+    auto selected_os_types = GetUserSettings()->GetSelectedOsTypes();
+
+    if (enabled)
+      selected_os_types.Put(syncer::UserSelectableOsType::kOsApps);
+    else
+      selected_os_types.Remove(syncer::UserSelectableOsType::kOsApps);
+
+    GetUserSettings()->SetSelectedOsTypes(false, selected_os_types);
+
+    NotifyObserversOfStateChanged();
+  }
+#endif
+
  private:
   // syncer::TestSyncService:
   void AddObserver(syncer::SyncServiceObserver* observer) override {
@@ -78,13 +98,13 @@
 
 class TestUkmConsentStateObserver : public UkmConsentStateObserver {
  public:
-  TestUkmConsentStateObserver() : purged_(false), notified_(false) {}
+  TestUkmConsentStateObserver() = default;
 
   TestUkmConsentStateObserver(const TestUkmConsentStateObserver&) = delete;
   TestUkmConsentStateObserver& operator=(const TestUkmConsentStateObserver&) =
       delete;
 
-  ~TestUkmConsentStateObserver() override {}
+  ~TestUkmConsentStateObserver() override = default;
 
   bool ResetPurged() {
     bool was_purged = purged_;
@@ -100,17 +120,17 @@
 
  private:
   // UkmConsentStateObserver:
-  void OnUkmAllowedStateChanged(bool must_purge) override {
+  void OnUkmAllowedStateChanged(bool must_purge, UkmConsentState) override {
     notified_ = true;
     purged_ = purged_ || must_purge;
   }
-  bool purged_;
-  bool notified_;
+  bool purged_ = false;
+  bool notified_ = false;
 };
 
 class UkmConsentStateObserverTest : public testing::Test {
  public:
-  UkmConsentStateObserverTest() {}
+  UkmConsentStateObserverTest() = default;
 
   UkmConsentStateObserverTest(const UkmConsentStateObserverTest&) = delete;
   UkmConsentStateObserverTest& operator=(const UkmConsentStateObserverTest&) =
@@ -127,6 +147,9 @@
         unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
         enabled);
   }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 }  // namespace
@@ -238,4 +261,125 @@
   EXPECT_FALSE(observer.ResetPurged());
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Tests for when AppSync is not dependent on MSBB.
+class MsbbAppOptInUkmConsentStateObserverTest
+    : public UkmConsentStateObserverTest {
+ public:
+  MsbbAppOptInUkmConsentStateObserverTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        ukm::kAppMetricsOnlyRelyOnAppSync);
+  }
+};
+
+TEST_F(MsbbAppOptInUkmConsentStateObserverTest, VerifyConsentStates) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  TestUkmConsentStateObserver observer;
+  MockSyncService sync;
+  // Disable app sync consent.
+  sync.SetAppSync(false);
+
+  // Enable MSBB consent.
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, /*enabled=*/true);
+  observer.StartObserving(&sync, &prefs);
+
+  UkmConsentState state = observer.GetUkmConsentState();
+
+  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
+  // MSBB and Extensions are enabled while App Sync is disabled.
+  EXPECT_TRUE(state.Has(MSBB));
+  EXPECT_TRUE(state.Has(EXTENSIONS));
+  EXPECT_FALSE(state.Has(APPS));
+
+  // UKM is enabled and the consent state was changed. Purge will not happen
+  // because UKM was enabled not disabled.
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+
+  // Turn on app sync.
+  sync.SetAppSync(true);
+  state = observer.GetUkmConsentState();
+
+  // Verify that the these values remain unchanged with App-sync enablement.
+  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
+  EXPECT_TRUE(state.Has(MSBB));
+  EXPECT_TRUE(state.Has(EXTENSIONS));
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+
+  // Check for the updated consent propagated correctly.
+  EXPECT_TRUE(state.Has(APPS));
+
+  // Turn off MSBB.
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, /*enabled=*/false);
+
+  state = observer.GetUkmConsentState();
+
+  // UKM will remain allowed.
+  EXPECT_TRUE(observer.IsUkmAllowedForAllProfiles());
+  // MSBB should be off.
+  EXPECT_FALSE(state.Has(MSBB));
+  // Extensions should be off, implicitly.
+  EXPECT_FALSE(state.Has(EXTENSIONS));
+  // App sync will stay on.
+  EXPECT_TRUE(state.Has(APPS));
+  EXPECT_TRUE(observer.ResetNotified());
+  // UKM is still allowed, total purge is not triggered.
+  EXPECT_FALSE(observer.ResetPurged());
+
+  // Finally, turn off app sync.
+  sync.SetAppSync(false);
+
+  state = observer.GetUkmConsentState();
+  // All consents should be off.
+  EXPECT_FALSE(state.Has(MSBB));
+  EXPECT_FALSE(state.Has(EXTENSIONS));
+  EXPECT_FALSE(state.Has(APPS));
+  EXPECT_TRUE(observer.ResetNotified());
+
+  // All UKM consent is turned off, total purge will be triggered.
+  EXPECT_TRUE(observer.ResetPurged());
+}
+
+TEST_F(MsbbAppOptInUkmConsentStateObserverTest,
+       VerifyConflictingProfilesRevokesConsent) {
+  sync_preferences::TestingPrefServiceSyncable prefs1;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs1);
+  sync_preferences::TestingPrefServiceSyncable prefs2;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);
+
+  // Add Profile 1 with MSBB consent but not App Sync.
+  TestUkmConsentStateObserver observer;
+  MockSyncService sync1;
+  sync1.SetAppSync(false);
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs1, /*enabled=*/true);
+  observer.StartObserving(&sync1, &prefs1);
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  const UkmConsentState consent_state = observer.GetUkmConsentState();
+  EXPECT_TRUE(consent_state.Has(MSBB));
+  EXPECT_FALSE(consent_state.Has(APPS));
+
+  // Add Profile 2 with App Sync consent but not MSBB.
+  MockSyncService sync2;
+  sync2.SetAppSync(true);
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, /*enabled=*/false);
+  observer.StartObserving(&sync2, &prefs2);
+  EXPECT_TRUE(observer.ResetNotified());
+
+  // Consents of MSBB and App-sync for each profile conflicts with either other
+  // resulting in all types of consent being false.
+  // MSBB is off because Profile 1 consents but Profile 2 doesn't.
+  // APP sync is off because Profile 2 consents but Profile 1 doesn't.
+  UkmConsentState state = observer.GetUkmConsentState();
+  EXPECT_FALSE(observer.IsUkmAllowedForAllProfiles());
+  EXPECT_FALSE(state.Has(MSBB));
+  EXPECT_FALSE(state.Has(EXTENSIONS));
+  EXPECT_FALSE(state.Has(APPS));
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace ukm
diff --git a/components/ukm/test_ukm_recorder.cc b/components/ukm/test_ukm_recorder.cc
index ffecfbd..1190b38 100644
--- a/components/ukm/test_ukm_recorder.cc
+++ b/components/ukm/test_ukm_recorder.cc
@@ -36,8 +36,7 @@
 }  // namespace
 
 TestUkmRecorder::TestUkmRecorder() {
-  UpdateRecording(
-      UkmConsentState(UkmConsentType::MSBB, UkmConsentType::EXTENSIONS));
+  UpdateRecording(UkmConsentState::All());
   InitDecodeMap();
   SetSamplingForTesting(1);  // 1-in-1 == unsampled
 }
diff --git a/components/ukm/ukm_consent_state.h b/components/ukm/ukm_consent_state.h
index d82b58c..3f70fd33 100644
--- a/components/ukm/ukm_consent_state.h
+++ b/components/ukm/ukm_consent_state.h
@@ -9,7 +9,7 @@
 
 namespace ukm {
 
-// Different types of consent's that control what types of data is recorded by
+// Different types of consents that control what types of data is recorded by
 // UKM.
 enum UkmConsentType {
   // "Make searches and browsing better" (MSBB) is consented.
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index 12aa1d1..d840457 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -33,6 +33,8 @@
 #include "third_party/metrics_proto/ukm/entry.pb.h"
 #include "third_party/metrics_proto/ukm/report.pb.h"
 #include "third_party/metrics_proto/ukm/source.pb.h"
+#include "ukm_consent_state.h"
+#include "ukm_recorder_impl.h"
 #include "url/gurl.h"
 
 namespace ukm {
@@ -89,6 +91,8 @@
   EMPTY_URL = 9,
   REJECTED_BY_FILTER = 10,
   SAMPLING_UNCONFIGURED = 11,
+  MSBB_CONSENT_DISABLED = 12,
+  APPS_CONSENT_DISABLED = 13,
   NUM_DROPPED_DATA_REASONS
 };
 
@@ -249,6 +253,10 @@
   event_sampling_rates_.clear();
 }
 
+bool UkmRecorderImpl::ShouldDropEntryForTesting(mojom::UkmEntry* entry) {
+  return ShouldDropEntry(entry);
+}
+
 bool UkmRecorderImpl::IsSamplingConfigured() const {
   return sampling_forced_for_testing_ ||
          base::FeatureList::IsEnabled(kUkmSamplingRateFeature);
@@ -297,6 +305,20 @@
   recording_is_continuous_ = false;
 }
 
+void UkmRecorderImpl::PurgeRecordingsWithMsbbSources() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::unordered_set<SourceId> relevant_source_ids;
+
+  for (const auto& kv : recordings_.sources) {
+    if (GetConsentType(GetSourceIdType(kv.first)) == MSBB) {
+      relevant_source_ids.insert(kv.first);
+    }
+  }
+
+  PurgeSourcesAndEventsBySourceIds(relevant_source_ids);
+  recording_is_continuous_ = false;
+}
+
 void UkmRecorderImpl::PurgeSourcesAndEventsBySourceIds(
     const std::unordered_set<SourceId>& source_ids) {
   for (const auto source_id : source_ids) {
@@ -671,6 +693,37 @@
   return pruned_sources_age_sec;
 }
 
+bool UkmRecorderImpl::ShouldDropEntry(mojom::UkmEntry* entry) {
+  if (!recording_enabled()) {
+    RecordDroppedEntry(entry->event_hash,
+                       DroppedDataReason::RECORDING_DISABLED);
+    return true;
+  }
+
+  const auto required_consent =
+      GetConsentType(GetSourceIdType(entry->source_id));
+
+  if (!recording_enabled(required_consent)) {
+    if (required_consent == UkmConsentType::MSBB) {
+      RecordDroppedEntry(entry->event_hash,
+                         DroppedDataReason::MSBB_CONSENT_DISABLED);
+
+    } else {
+      RecordDroppedEntry(entry->event_hash,
+                         DroppedDataReason::APPS_CONSENT_DISABLED);
+    }
+    return true;
+  }
+
+  if (!ApplyEntryFilter(entry)) {
+    RecordDroppedEntry(entry->event_hash,
+                       DroppedDataReason::REJECTED_BY_FILTER);
+    return true;
+  }
+
+  return false;
+}
+
 bool UkmRecorderImpl::ApplyEntryFilter(mojom::UkmEntry* entry) {
   base::flat_set<uint64_t> dropped_metric_hashes;
 
@@ -805,6 +858,26 @@
       std::make_unique<UkmSource>(source_id, sanitized_navigation_data));
 }
 
+// static:
+UkmConsentType UkmRecorderImpl::GetConsentType(SourceIdType type) {
+  switch (type) {
+    case SourceIdType::APP_ID:
+      return UkmConsentType::APPS;
+    case SourceIdType::DEFAULT:
+    case SourceIdType::NAVIGATION_ID:
+    case SourceIdType::HISTORY_ID:
+    case SourceIdType::WEBAPK_ID:
+    case SourceIdType::PAYMENT_APP_ID:
+    case SourceIdType::DESKTOP_WEB_APP_ID:
+    case SourceIdType::WORKER_ID:
+    case SourceIdType::NO_URL_ID:
+    case SourceIdType::REDIRECT_ID:
+    case SourceIdType::WEB_IDENTITY_ID:
+      return UkmConsentType::MSBB;
+  }
+  return UkmConsentType::MSBB;
+}
+
 UkmRecorderImpl::EventAggregate::EventAggregate() = default;
 UkmRecorderImpl::EventAggregate::~EventAggregate() = default;
 
@@ -879,6 +952,20 @@
     has_recorded_reason = true;
   }
 
+  const auto required_consent = GetConsentType(GetSourceIdType(source_id));
+
+  if (!recording_enabled(required_consent)) {
+    if (required_consent == UkmConsentType::MSBB) {
+      RecordDroppedSource(has_recorded_reason,
+                          DroppedDataReason::MSBB_CONSENT_DISABLED);
+
+    } else {
+      RecordDroppedSource(has_recorded_reason,
+                          DroppedDataReason::APPS_CONSENT_DISABLED);
+    }
+    return ShouldRecordUrlResult::kDropped;
+  }
+
   if (recordings_.sources.size() >= max_sources_) {
     RecordDroppedSource(has_recorded_reason, DroppedDataReason::MAX_HIT);
     return ShouldRecordUrlResult::kDropped;
@@ -926,6 +1013,12 @@
     return;
   }
 
+  const auto required_consent = GetConsentType(GetSourceIdType(source_id));
+
+  if (!recording_enabled(required_consent)) {
+    return;
+  }
+
   if (GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID)
     recordings_.source_counts.navigation_sources++;
   recordings_.source_counts.observed++;
@@ -938,17 +1031,8 @@
 
   NotifyObserversWithNewEntry(*entry);
 
-  if (!recording_enabled()) {
-    RecordDroppedEntry(entry->event_hash,
-                       DroppedDataReason::RECORDING_DISABLED);
+  if (ShouldDropEntry(entry.get()))
     return;
-  }
-
-  if (!ApplyEntryFilter(entry.get())) {
-    RecordDroppedEntry(entry->event_hash,
-                       DroppedDataReason::REJECTED_BY_FILTER);
-    return;
-  }
 
   EventAggregate& event_aggregate =
       recordings_.event_aggregations[entry->event_hash];
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index 0648f04..535a35a 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -28,6 +28,7 @@
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/metrics/public/mojom/ukm_interface.mojom-forward.h"
+#include "ukm_consent_state.h"
 
 namespace metrics {
 class UkmBrowserTestBase;
@@ -82,6 +83,10 @@
   // attributed with these Sources.
   void PurgeRecordingsWithSourceIdType(ukm::SourceIdType source_id_type);
 
+  // Deletes stored Sources with any Source Id related to MSBB. This included
+  // all SourceIds that are not of type APP_ID.
+  void PurgeRecordingsWithMsbbSources();
+
   // Marks a source as no longer needed to be kept alive in memory. The source
   // with given id will be removed from in-memory recordings at the next
   // reporting cycle.
@@ -129,6 +134,8 @@
     return recording_state_.Has(type);
   }
 
+  bool ShouldDropEntryForTesting(mojom::UkmEntry* entry);
+
  protected:
   // Calculates sampled in/out for a specific source/event based on internal
   // configuration. This function is guaranteed to always return the same
@@ -179,6 +186,9 @@
       const UkmSource::NavigationData& navigation_data) override;
   using UkmRecorder::RecordOtherURL;
 
+  // Get the UkmConsentType associated for a given SourceIdType.
+  static UkmConsentType GetConsentType(SourceIdType type);
+
  private:
   friend ::metrics::UkmBrowserTestBase;
   friend ::ukm::debug::UkmDebugDataExtractor;
@@ -239,6 +249,9 @@
 
   void RecordSource(std::unique_ptr<UkmSource> source);
 
+  // Determines if an UkmEntry should be dropped and records reason if so.
+  bool ShouldDropEntry(mojom::UkmEntry* entry);
+
   // Applies UkmEntryFilter if there is one registered.
   bool ApplyEntryFilter(mojom::UkmEntry* entry);
 
diff --git a/components/ukm/ukm_recorder_impl_unittest.cc b/components/ukm/ukm_recorder_impl_unittest.cc
index 48bf2eb..9fcb015 100644
--- a/components/ukm/ukm_recorder_impl_unittest.cc
+++ b/components/ukm/ukm_recorder_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "url/gurl.h"
 
 namespace ukm {
+namespace {
 
 using TestEvent1 = builders::PageLoad;
 
@@ -28,6 +29,12 @@
 const char kTestEntryName[] = "TestEntry";
 const char kTestMetrics[] = "TestMetrics";
 
+// Builds a blank UkmEntry with given SourceId.
+mojom::UkmEntryPtr BlankUkmEntry(SourceId source_id) {
+  return mojom::UkmEntry::New(source_id, 0ull,
+                              base::flat_map<uint64_t, int64_t>());
+}
+
 std::map<uint64_t, builders::EntryDecoder> CreateTestingDecodeMap() {
   return {
       {kTestEntryHash,
@@ -114,6 +121,8 @@
   ukm::UkmConsentState expected_state_;
 };
 
+}  // namespace
+
 TEST(UkmRecorderImplTest, IsSampledIn) {
   UkmRecorderImpl impl;
 
@@ -203,7 +212,7 @@
   TestEvent1(id2).Record(&recorder);
 
   // All sources and events have been recorded.
-  EXPECT_TRUE(recorder.recording_state_.Has(UkmConsentType::EXTENSIONS));
+  EXPECT_TRUE(recorder.recording_enabled(EXTENSIONS));
   EXPECT_TRUE(recorder.recording_is_continuous_);
   EXPECT_EQ(4U, recorder.sources().size());
   EXPECT_EQ(2U, recorder.entries().size());
@@ -385,4 +394,42 @@
   }
 }
 
+TEST(UkmRecorderImplTest, VerifyShouldDropEntry) {
+  UkmRecorderImpl impl;
+
+  // Enable Recording, if recording was disabled everything
+  // would be dropped.
+  impl.EnableRecording();
+
+  auto msbb_entry =
+      BlankUkmEntry(ConvertToSourceId(1, SourceIdType::NAVIGATION_ID));
+  auto app_entry = BlankUkmEntry(ConvertToSourceId(1, SourceIdType::APP_ID));
+
+  // Neither MSBB nor App-Sync is consented too, both will be dropped.
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(msbb_entry.get()));
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(app_entry.get()));
+
+  // Update service with MSBB consent.
+  impl.UpdateRecording(UkmConsentState(MSBB));
+  EXPECT_FALSE(impl.ShouldDropEntryForTesting(msbb_entry.get()));
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(app_entry.get()));
+
+  // Update service with App-sync consent as well.
+  impl.UpdateRecording(UkmConsentState(MSBB, APPS));
+  EXPECT_FALSE(impl.ShouldDropEntryForTesting(msbb_entry.get()));
+  EXPECT_FALSE(impl.ShouldDropEntryForTesting(app_entry.get()));
+
+  // Update service with only App-sync consent.
+  // Only applicable to ASH builds but will not affect the test.
+  impl.UpdateRecording(UkmConsentState(APPS));
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(msbb_entry.get()));
+  EXPECT_FALSE(impl.ShouldDropEntryForTesting(app_entry.get()));
+
+  // Disabling recording will supersede any consent state.
+  impl.UpdateRecording(UkmConsentState(MSBB, APPS));
+  impl.DisableRecording();
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(msbb_entry.get()));
+  EXPECT_TRUE(impl.ShouldDropEntryForTesting(app_entry.get()));
+}
+
 }  // namespace ukm
diff --git a/components/ukm/ukm_service.cc b/components/ukm/ukm_service.cc
index 7f8c7b8..9ed91ddc 100644
--- a/components/ukm/ukm_service.cc
+++ b/components/ukm/ukm_service.cc
@@ -368,6 +368,20 @@
   UkmRecorderImpl::PurgeRecordingsWithSourceIdType(SourceIdType::APP_ID);
 }
 
+void UkmService::PurgeMsbbData() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Filter out any MSBB-related data from the serialized logs in the
+  // UnsentLogStore for uploading.
+  PurgeDataFromUnsentLogStore(
+      reporting_service_.ukm_log_store(), [&](const Source& source) {
+        return UkmRecorderImpl::GetConsentType(GetSourceIdType(source.id())) ==
+               MSBB;
+      });
+
+  // Purge data currently in the recordings intended for the next ukm::Report.
+  UkmRecorderImpl::PurgeRecordingsWithMsbbSources();
+}
+
 void UkmService::ResetClientState(ResetReason reason) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/components/ukm/ukm_service.h b/components/ukm/ukm_service.h
index 3de54c62..c0e3eb25 100644
--- a/components/ukm/ukm_service.h
+++ b/components/ukm/ukm_service.h
@@ -28,11 +28,12 @@
 FORWARD_DECLARE_TEST(ChromeMetricsServiceClientTest, TestRegisterUKMProviders);
 FORWARD_DECLARE_TEST(IOSChromeMetricsServiceClientTest,
                      TestRegisterUkmProvidersWhenUKMFeatureEnabled);
+class ChromeMetricsServiceClientTestIgnoredForAppMetrics;
 
 namespace metrics {
 class MetricsServiceClient;
 class UkmBrowserTestBase;
-}
+}  // namespace metrics
 
 namespace ukm {
 class Report;
@@ -103,6 +104,9 @@
   // Deletes all unsent local data related to Apps.
   void PurgeAppsData();
 
+  // Deletes all unsent local data related to MSBB.
+  void PurgeMsbbData();
+
   // Resets the client prefs (client_id/session_id). |reason| should be passed
   // to provide the reason of the reset - this is only used for UMA logging.
   void ResetClientState(ResetReason reason);
@@ -124,6 +128,10 @@
 
   uint64_t client_id() const { return client_id_; }
 
+  ukm::UkmReportingService& reporting_service_for_testing() {
+    return reporting_service_;
+  }
+
   // Makes sure that the serialized UKM report can be parsed.
   static bool LogCanBeParsed(const std::string& serialized_data);
 
@@ -135,13 +143,16 @@
   friend ::ukm::UkmTestHelper;
   friend ::ukm::debug::UkmDebugDataExtractor;
   friend ::ukm::UkmUtilsForTest;
+
   FRIEND_TEST_ALL_PREFIXES(::ChromeMetricsServiceClientTest,
                            TestRegisterUKMProviders);
+  friend ::ChromeMetricsServiceClientTestIgnoredForAppMetrics;
   FRIEND_TEST_ALL_PREFIXES(::IOSChromeMetricsServiceClientTest,
                            TestRegisterUkmProvidersWhenUKMFeatureEnabled);
   FRIEND_TEST_ALL_PREFIXES(UkmServiceTest,
                            PurgeExtensionDataFromUnsentLogStore);
   FRIEND_TEST_ALL_PREFIXES(UkmServiceTest, PurgeAppDataFromUnsentLogStore);
+  FRIEND_TEST_ALL_PREFIXES(UkmServiceTest, PurgeMsbbDataFromUnsentLogStore);
 
   // Starts metrics client initialization.
   void StartInitTask();
diff --git a/components/ukm/ukm_service_unittest.cc b/components/ukm/ukm_service_unittest.cc
index cf47c09ec..f6129cf 100644
--- a/components/ukm/ukm_service_unittest.cc
+++ b/components/ukm/ukm_service_unittest.cc
@@ -31,6 +31,7 @@
 #include "components/metrics/test/test_metrics_service_client.h"
 #include "components/metrics/ukm_demographic_metrics_provider.h"
 #include "components/prefs/testing_pref_service.h"
+#include "components/ukm/observers/ukm_consent_state_observer.h"
 #include "components/ukm/ukm_entry_filter.h"
 #include "components/ukm/ukm_pref_names.h"
 #include "components/ukm/ukm_recorder_impl.h"
@@ -95,6 +96,29 @@
   return (id == "bhcnanendmgjjeghamaccjnochlnhcgj");
 }
 
+int GetPersistedLogCount(TestingPrefServiceSimple& prefs) {
+  return prefs.GetList(prefs::kUkmUnsentLogStore).size();
+}
+
+Report GetPersistedReport(TestingPrefServiceSimple& prefs) {
+  EXPECT_GE(GetPersistedLogCount(prefs), 1);
+  metrics::UnsentLogStore result_unsent_log_store(
+      std::make_unique<UnsentLogStoreMetricsImpl>(), &prefs,
+      prefs::kUkmUnsentLogStore, /*metadata_pref_name=*/nullptr,
+      /*min_log_count=*/3, /*min_log_bytes=*/1000,
+      /*max_log_size=*/0,
+      /*signing_key=*/std::string(),
+      /*logs_event_manager=*/nullptr);
+
+  result_unsent_log_store.LoadPersistedUnsentLogs();
+  result_unsent_log_store.StageNextLog();
+
+  Report report;
+  EXPECT_TRUE(metrics::DecodeLogDataToProto(
+      result_unsent_log_store.staged_log(), &report));
+  return report;
+}
+
 class ScopedUkmFeatureParams {
  public:
   explicit ScopedUkmFeatureParams(const base::FieldTrialParams& params) {
@@ -158,30 +182,9 @@
     prefs_.ClearPref(prefs::kUkmUnsentLogStore);
   }
 
-  int GetPersistedLogCount() {
-    const base::Value::List& list_value =
-        prefs_.GetList(prefs::kUkmUnsentLogStore);
-    return list_value.size();
-  }
+  int GetPersistedLogCount() { return ukm::GetPersistedLogCount(prefs_); }
 
-  Report GetPersistedReport() {
-    EXPECT_GE(GetPersistedLogCount(), 1);
-    metrics::UnsentLogStore result_unsent_log_store(
-        std::make_unique<UnsentLogStoreMetricsImpl>(), &prefs_,
-        prefs::kUkmUnsentLogStore, /*metadata_pref_name=*/nullptr,
-        /*min_log_count=*/3, /*min_log_bytes=*/1000,
-        /*max_log_size=*/0,
-        /*signing_key=*/std::string(),
-        /*logs_event_manager=*/nullptr);
-
-    result_unsent_log_store.LoadPersistedUnsentLogs();
-    result_unsent_log_store.StageNextLog();
-
-    Report report;
-    EXPECT_TRUE(metrics::DecodeLogDataToProto(
-        result_unsent_log_store.staged_log(), &report));
-    return report;
-  }
+  Report GetPersistedReport() { return ukm::GetPersistedReport(prefs_); }
 
   static SourceId GetAllowlistedSourceId(int64_t id) {
     return ConvertToSourceId(id, SourceIdType::NAVIGATION_ID);
@@ -205,7 +208,9 @@
 
 }  // namespace
 
-TEST_F(UkmServiceTest, ClientIdMigration) {
+INSTANTIATE_TEST_SUITE_P(All, UkmServiceTest, testing::Bool());
+
+TEST_P(UkmServiceTest, ClientIdMigration) {
   prefs_.SetInt64(prefs::kUkmClientId, -1);
   UkmService service(&prefs_, &client_,
                      std::make_unique<MockDemographicMetricsProvider>());
@@ -434,7 +439,102 @@
   EXPECT_EQ(source_id_1, filtered_report.entries(0).source_id());
 }
 
-TEST_F(UkmServiceTest, SourceSerialization) {
+TEST_P(UkmServiceTest, PurgeMsbbDataFromUnsentLogStore) {
+  UkmService service(&prefs_, &client_,
+                     std::make_unique<MockDemographicMetricsProvider>());
+  auto* unsent_log_store = service.reporting_service_.ukm_log_store();
+
+  // Initialize a Report to be saved to the log store.
+  Report report;
+  report.set_client_id(1);
+  report.set_session_id(1);
+  report.set_report_id(1);
+
+  // A URL from browser navigation.
+  std::string non_app_url = "https://www.google.ca";
+  std::string non_app_url2 = "https://www.google.com";
+  // A URL with app:// scheme.
+  std::string app_url = "app://mgndgikekgjfcpckkfioiadnlibdjbkf";
+  // OS Settings is an app on ChromeOS without the app:// scheme.
+  std::string os_settings_url = "chrome://os-settings";
+
+  // Add sources to the Report.
+  // Non-app source.
+  Source* proto_source_1 = report.add_sources();
+  SourceId source_id_1 = ConvertToSourceId(1, SourceIdType::NAVIGATION_ID);
+  proto_source_1->set_id(source_id_1);
+  proto_source_1->add_urls()->set_url(non_app_url);
+  // Non-app source 2.
+  Source* proto_source_2 = report.add_sources();
+  SourceId source_id_2 = ConvertToSourceId(2, SourceIdType::NAVIGATION_ID);
+  proto_source_2->set_id(source_id_2);
+  proto_source_2->add_urls()->set_url(non_app_url);
+  // App source with app:// URL.
+  Source* proto_source_3 = report.add_sources();
+  SourceId source_id_3 = ConvertToSourceId(3, SourceIdType::APP_ID);
+  proto_source_3->set_id(source_id_3);
+  proto_source_3->add_urls()->set_url(app_url);
+  // App source with non-app:// URL.
+  Source* proto_source_4 = report.add_sources();
+  SourceId source_id_4 = ConvertToSourceId(4, SourceIdType::APP_ID);
+  proto_source_4->set_id(source_id_4);
+  proto_source_4->add_urls()->set_url(os_settings_url);
+  // Non-app source with app:// URL. This shouldn't happen in practice, but
+  // if it does, this source should be purged when app data are purged.
+  Source* proto_source_5 = report.add_sources();
+  SourceId source_id_5 = ConvertToSourceId(5, SourceIdType::NAVIGATION_ID);
+  proto_source_5->set_id(source_id_5);
+  proto_source_5->add_urls()->set_url(app_url);
+
+  // Add entries to each of the sources.
+  Entry* entry_1 = report.add_entries();
+  entry_1->set_source_id(source_id_2);
+  Entry* entry_2 = report.add_entries();
+  entry_2->set_source_id(source_id_1);
+  Entry* entry_3 = report.add_entries();
+  entry_3->set_source_id(source_id_2);
+  Entry* entry_4 = report.add_entries();
+  entry_4->set_source_id(source_id_3);
+  Entry* entry_5 = report.add_entries();
+  entry_5->set_source_id(source_id_4);
+  Entry* entry_6 = report.add_entries();
+  entry_6->set_source_id(source_id_5);
+
+  // Save the Report to the store.
+  std::string serialized_log;
+  report.SerializeToString(&serialized_log);
+  // Make sure that the serialized ukm report can be parsed.
+  ASSERT_TRUE(UkmService::LogCanBeParsed(serialized_log));
+  metrics::LogMetadata log_metadata;
+  unsent_log_store->StoreLog(serialized_log, log_metadata);
+
+  // Purge MSBB data.
+  service.PurgeMsbbData();
+
+  // Get the Report in the log store and verify non-app-related data have been
+  // filtered.
+  unsent_log_store->StageNextLog();
+  const std::string& compressed_log_data = unsent_log_store->staged_log();
+
+  Report filtered_report;
+  ASSERT_TRUE(
+      metrics::DecodeLogDataToProto(compressed_log_data, &filtered_report));
+
+  // Source proto_source_1 with app URL is kept.
+  EXPECT_EQ(2, filtered_report.sources_size());
+  EXPECT_EQ(source_id_4, filtered_report.sources(0).id());
+  EXPECT_EQ(os_settings_url, filtered_report.sources(0).urls(0).url());
+
+  EXPECT_EQ(source_id_3, filtered_report.sources(1).id());
+  EXPECT_EQ(app_url, filtered_report.sources(1).urls(0).url());
+
+  // Entry entry_2 from the app source is kept.
+  EXPECT_EQ(2, filtered_report.entries_size());
+  EXPECT_EQ(source_id_4, filtered_report.entries(0).source_id());
+  EXPECT_EQ(source_id_3, filtered_report.entries(1).source_id());
+}
+
+TEST_P(UkmServiceTest, SourceSerialization) {
   UkmService service(&prefs_, &client_,
                      std::make_unique<MockDemographicMetricsProvider>());
   TestRecordingHelper recorder(&service);
@@ -499,7 +599,7 @@
   EXPECT_FALSE(provider->provide_system_profile_metrics_called());
 
   task_runner_->RunUntilIdle();
-  service.UpdateRecording(UkmConsentState(UkmConsentType::MSBB));
+  service.UpdateRecording(UkmConsentState(MSBB));
   service.EnableReporting();
 
   service.Flush();
@@ -1066,7 +1166,8 @@
     EXPECT_EQ(0, GetPersistedLogCount());
     service.Initialize();
     task_runner_->RunUntilIdle();
-    service.UpdateRecording(UkmConsentState(UkmConsentType::MSBB));
+    service.UpdateRecording(
+        UkmConsentState(UkmConsentType::MSBB, UkmConsentType::APPS));
     service.EnableReporting();
 
     SourceId id = ConvertSourceIdToAllowlistedType(
@@ -1338,7 +1439,8 @@
   EXPECT_EQ(0, GetPersistedLogCount());
   service.Initialize();
   task_runner_->RunUntilIdle();
-  service.UpdateRecording(UkmConsentState(UkmConsentType::MSBB));
+  service.UpdateRecording(
+      UkmConsentState(UkmConsentType::MSBB, UkmConsentType::APPS));
   service.EnableReporting();
 
   // Seed some fake sources.
@@ -1640,7 +1742,8 @@
     EXPECT_EQ(0, GetPersistedLogCount());
     service.Initialize();
     task_runner_->RunUntilIdle();
-    service.UpdateRecording(UkmConsentState(UkmConsentType::MSBB));
+    service.UpdateRecording(
+        UkmConsentState(UkmConsentType::MSBB, UkmConsentType::APPS));
     service.EnableReporting();
 
     // Create 5 sources. We set source 0 and 4 to be APP_ID Sources, where
@@ -1731,4 +1834,197 @@
   EXPECT_EQ(external_client_id, prefs_.GetUint64(prefs::kUkmClientId));
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+namespace {
+
+class UkmServiceTestWithIndependentAppKM
+    : public testing::TestWithParam<UkmConsentType> {
+ public:
+  UkmServiceTestWithIndependentAppKM()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_) {
+    UkmService::RegisterPrefs(prefs_.registry());
+
+    prefs_.ClearPref(prefs::kUkmClientId);
+    prefs_.ClearPref(prefs::kUkmSessionId);
+    prefs_.ClearPref(prefs::kUkmUnsentLogStore);
+
+    scoped_feature_list_.InitAndEnableFeature({kAppMetricsOnlyRelyOnAppSync});
+  }
+
+  int GetPersistedLogCount() { return ukm::GetPersistedLogCount(prefs_); }
+
+  Report GetPersistedReport() { return ukm::GetPersistedReport(prefs_); }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+  metrics::TestMetricsServiceClient client_;
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+}  // namespace
+
+TEST_P(UkmServiceTestWithIndependentAppKM, RejectWhenNotConsented) {
+  const GURL kURL("https://google.com/foobar");
+  const GURL kAppURL("app://google.com/foobar");
+
+  // Setup test constants from param.
+  const auto consent = GetParam();
+  const std::vector<int> app_indices = {1, 4};
+  const std::vector<int> url_indices = {0, 2, 3};
+  const std::vector<int>& expected_source_indices =
+      (consent == UkmConsentType::APPS ? app_indices : url_indices);
+
+  const int expected_result = expected_source_indices.size();
+
+  UkmService service(&prefs_, &client_,
+                     std::make_unique<MockDemographicMetricsProvider>());
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.UpdateRecording(UkmConsentState(consent));
+  service.EnableReporting();
+
+  std::vector<SourceId> source_ids;
+  for (int i = 0; i < 5; ++i) {
+    if (std::find(app_indices.begin(), app_indices.end(), i) !=
+        app_indices.end()) {
+      source_ids.push_back(UkmServiceTest::GetAppIDSourceId(i));
+      recorder.UpdateSourceURL(source_ids.back(), kAppURL);
+    } else {
+      source_ids.push_back(UkmServiceTest::GetAllowlistedSourceId(i));
+      recorder.UpdateSourceURL(source_ids.back(), kURL);
+    }
+
+    TestEvent1(source_ids.back()).Record(&service);
+  }
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+
+  // Has the sources and entries associated with AppIDs.
+  Report report = GetPersistedReport();
+  EXPECT_EQ(expected_result, report.sources_size());
+  EXPECT_EQ(expected_result, report.entries_size());
+
+  for (int i = 0; i < expected_result; ++i) {
+    EXPECT_EQ(source_ids[expected_source_indices[i]], report.sources(i).id());
+    EXPECT_EQ(source_ids[expected_source_indices[i]],
+              report.entries(i).source_id());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    UkmServiceTestWithIndependentAppKMGroup,
+    UkmServiceTestWithIndependentAppKM,
+    testing::Values(UkmConsentType::APPS, UkmConsentType::MSBB),
+    [](const testing::TestParamInfo<
+        UkmServiceTestWithIndependentAppKM::ParamType>& info) {
+      if (info.param == UkmConsentType::APPS)
+        return "TestApps";
+      else
+        return "TestMSBB";
+    });
+
+namespace {
+
+class UkmServiceTestWithIndependentAppKMFullConsent
+    : public testing::TestWithParam<bool> {
+ public:
+  UkmServiceTestWithIndependentAppKMFullConsent()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_) {
+    UkmService::RegisterPrefs(prefs_.registry());
+
+    prefs_.ClearPref(prefs::kUkmClientId);
+    prefs_.ClearPref(prefs::kUkmSessionId);
+    prefs_.ClearPref(prefs::kUkmUnsentLogStore);
+
+    scoped_feature_list_.InitAndEnableFeature({kAppMetricsOnlyRelyOnAppSync});
+  }
+
+  int GetPersistedLogCount() { return ukm::GetPersistedLogCount(prefs_); }
+
+  Report GetPersistedReport() { return ukm::GetPersistedReport(prefs_); }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+  metrics::TestMetricsServiceClient client_;
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+}  // namespace
+
+TEST_P(UkmServiceTestWithIndependentAppKMFullConsent, VerifyAllAndNoneConsent) {
+  const GURL kURL("https://google.com/foobar");
+  const GURL kAppURL("app://google.com/foobar");
+  const int kExpectedResultWithConsent = 5;
+
+  // Setup test constants from param.
+  const auto has_consent = GetParam();
+  const auto consent_state =
+      (has_consent ? UkmConsentState::All() : UkmConsentState());
+
+  UkmService service(&prefs_, &client_,
+                     std::make_unique<MockDemographicMetricsProvider>());
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.UpdateRecording(consent_state);
+  service.EnableReporting();
+
+  const std::vector<int> app_indices = {1, 4};
+  const std::vector<int> url_indices = {0, 2, 3};
+
+  std::vector<SourceId> source_ids;
+  for (int i = 0; i < 5; ++i) {
+    if (std::find(app_indices.begin(), app_indices.end(), i) !=
+        app_indices.end()) {
+      source_ids.push_back(UkmServiceTest::GetAppIDSourceId(i));
+      recorder.UpdateSourceURL(source_ids.back(), kAppURL);
+    } else {
+      source_ids.push_back(UkmServiceTest::GetAllowlistedSourceId(i));
+      recorder.UpdateSourceURL(source_ids.back(), kURL);
+    }
+
+    TestEvent1(source_ids.back()).Record(&service);
+  }
+
+  service.Flush();
+
+  EXPECT_EQ(GetPersistedLogCount(), static_cast<int>(has_consent));
+
+  if (has_consent) {
+    const auto report = GetPersistedReport();
+
+    EXPECT_EQ(report.sources_size(), kExpectedResultWithConsent);
+    EXPECT_EQ(report.entries_size(), kExpectedResultWithConsent);
+
+    for (int i = 0; i < kExpectedResultWithConsent; ++i) {
+      EXPECT_EQ(source_ids[i], report.sources(i).id());
+      EXPECT_EQ(source_ids[i], report.entries(i).source_id());
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    UkmServiceTestWithIndependentAppKMFullConsentGroup,
+    UkmServiceTestWithIndependentAppKMFullConsent,
+    testing::Values(true, false),
+    [](const testing::TestParamInfo<
+        UkmServiceTestWithIndependentAppKMFullConsent::ParamType>& info) {
+      if (info.param)
+        return "TestAllConsent";
+      else
+        return "TestNoConsent";
+    });
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace ukm
diff --git a/components/ukm/ukm_test_helper.cc b/components/ukm/ukm_test_helper.cc
index 8ca7cb3..1ba3f40 100644
--- a/components/ukm/ukm_test_helper.cc
+++ b/components/ukm/ukm_test_helper.cc
@@ -12,6 +12,7 @@
 #include "base/run_loop.h"
 #include "components/metrics/log_decoder.h"
 #include "components/metrics/unsent_log_store.h"
+#include "ukm_test_helper.h"
 
 namespace ukm {
 
@@ -96,4 +97,9 @@
          ukm_service_->reporting_service_.ukm_log_store()->has_unsent_logs();
 }
 
+void UkmTestHelper::SetMsbbConsent() {
+  DCHECK(ukm_service_);
+  ukm_service_->UpdateRecording(ukm::MSBB);
+}
+
 }  // namespace ukm
diff --git a/components/ukm/ukm_test_helper.h b/components/ukm/ukm_test_helper.h
index 4fa1f5f..d0bb168 100644
--- a/components/ukm/ukm_test_helper.h
+++ b/components/ukm/ukm_test_helper.h
@@ -55,9 +55,12 @@
   // Creates a log and stores it in |ukm_service_|'s UnsentLogStore.
   void BuildAndStoreLog();
 
-  // Reeturns true if |ukm_service_| has logs to send.
+  // Returns true if |ukm_service_| has logs to send.
   bool HasUnsentLogs();
 
+  // Adds MSBB consent to the UkmService.
+  void SetMsbbConsent();
+
  private:
   const raw_ptr<UkmService, DanglingUntriaged> ukm_service_;
 };
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 767c24d..b6032e0 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -222,7 +222,7 @@
 // render pass, instead of SkiaOutputDeviceBufferQueue itself.
 BASE_FEATURE(kRendererAllocatesImages,
              "RendererAllocatesImages",
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_CHROMEOS)
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
index 164d90c5..79ebb0f2 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -1068,19 +1068,11 @@
   support_->SetNeedsBeginFrame(false);
 }
 
-#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
-// https://crbug.com/1223023
-#define MAYBE_NeedsBeginFrameResetAfterPresentationFeedback \
-  DISABLED_NeedsBeginFrameResetAfterPresentationFeedback
-#else
-#define MAYBE_NeedsBeginFrameResetAfterPresentationFeedback \
-  NeedsBeginFrameResetAfterPresentationFeedback
-#endif
 // Validates that if a client asked to stop receiving begin-frames, then it
 // stops receiving begin-frames after receiving the presentation-feedback from
 // the last submitted frame.
 TEST_F(CompositorFrameSinkSupportTest,
-       MAYBE_NeedsBeginFrameResetAfterPresentationFeedback) {
+       NeedsBeginFrameResetAfterPresentationFeedback) {
   // Request BeginFrames.
   support_->SetNeedsBeginFrame(true);
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 37a64f64..302c7a8e 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1448,6 +1448,9 @@
     "preloading/prefetch/prefetch_serving_page_metrics_container.cc",
     "preloading/prefetch/prefetch_serving_page_metrics_container.h",
     "preloading/prefetch/prefetch_status.h",
+    "preloading/prefetch/prefetch_streaming_url_loader.cc",
+    "preloading/prefetch/prefetch_streaming_url_loader.h",
+    "preloading/prefetch/prefetch_streaming_url_loader_status.h",
     "preloading/prefetch/prefetch_type.cc",
     "preloading/prefetch/prefetch_type.h",
     "preloading/prefetch/prefetch_url_loader_interceptor.cc",
@@ -2230,8 +2233,8 @@
     "webid/federated_auth_request_impl.h",
     "webid/federated_auth_request_page_data.cc",
     "webid/federated_auth_request_page_data.h",
-    "webid/federated_manifest_requester.cc",
-    "webid/federated_manifest_requester.h",
+    "webid/federated_provider_fetcher.cc",
+    "webid/federated_provider_fetcher.h",
     "webid/flags.cc",
     "webid/flags.h",
     "webid/idp_network_request_manager.cc",
diff --git a/content/browser/devtools/protocol/devtools_download_manager_delegate.cc b/content/browser/devtools/protocol/devtools_download_manager_delegate.cc
index 7c3d67d..bad5daf 100644
--- a/content/browser/devtools/protocol/devtools_download_manager_delegate.cc
+++ b/content/browser/devtools/protocol/devtools_download_manager_delegate.cc
@@ -79,7 +79,7 @@
     std::move(*callback).Run(
         empty_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        download::DownloadItem::MixedContentStatus::UNKNOWN, empty_path,
+        download::DownloadItem::InsecureDownloadStatus::UNKNOWN, empty_path,
         empty_path, std::string() /*mime_type*/,
         download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
     return true;
@@ -160,7 +160,7 @@
   std::move(callback).Run(
       suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT,
-      download::DownloadItem::MixedContentStatus::UNKNOWN,
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN,
       suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")),
       suggested_path.BaseName(), std::string(),
       download::DOWNLOAD_INTERRUPT_REASON_NONE);
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 8d809e7d..d43d16a6 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -487,7 +487,7 @@
     std::move(callback).Run(
         target_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        download::DownloadItem::MixedContentStatus::UNKNOWN, target_path,
+        download::DownloadItem::InsecureDownloadStatus::UNKNOWN, target_path,
         base::FilePath(), std::string() /*mime_type*/,
         download::DOWNLOAD_INTERRUPT_REASON_NONE);
   }
diff --git a/content/browser/download/download_manager_impl_unittest.cc b/content/browser/download/download_manager_impl_unittest.cc
index 8115bc0a2..da9fcea 100644
--- a/content/browser/download/download_manager_impl_unittest.cc
+++ b/content/browser/download/download_manager_impl_unittest.cc
@@ -537,7 +537,7 @@
       const base::FilePath& target_path,
       download::DownloadItem::TargetDisposition disposition,
       download::DownloadDangerType danger_type,
-      download::DownloadItem::MixedContentStatus mixed_content_status,
+      download::DownloadItem::InsecureDownloadStatus insecure_download_status,
       const base::FilePath& intermediate_path,
       const base::FilePath& display_name,
       const std::string& mime_type,
diff --git a/content/browser/fenced_frame/fenced_frame_config.cc b/content/browser/fenced_frame/fenced_frame_config.cc
index b13ffd24..dcab669 100644
--- a/content/browser/fenced_frame/fenced_frame_config.cc
+++ b/content/browser/fenced_frame/fenced_frame_config.cc
@@ -24,15 +24,22 @@
   std::vector<std::pair<GURL, FencedFrameConfig>> nested_urn_config_pairs;
   DCHECK_LE(nested_configs.size(), blink::kMaxAdAuctionAdComponents);
   for (const FencedFrameConfig& config : nested_configs) {
-    nested_urn_config_pairs.emplace_back(GenerateUrnUuid(), config);
+    // Give each config its own urn:uuid. This ensures that if the same config
+    // is loaded into multiple fenced frames, they will not share the same
+    // urn:uuid across processes.
+    GURL urn_uuid = GenerateUrnUuid();
+    auto config_with_urn = config;
+    config_with_urn.urn_ = urn_uuid;
+    nested_urn_config_pairs.emplace_back(urn_uuid, config_with_urn);
   }
 
   // Pad `component_ads_` to contain exactly kMaxAdAuctionAdComponents ads, to
   // avoid leaking any data to the fenced frame the component ads array is
   // exposed to.
   while (nested_urn_config_pairs.size() < blink::kMaxAdAuctionAdComponents) {
+    GURL urn_uuid = GenerateUrnUuid();
     nested_urn_config_pairs.emplace_back(
-        GenerateUrnUuid(), FencedFrameConfig(GURL(url::kAboutBlankURL)));
+        urn_uuid, FencedFrameConfig(urn_uuid, GURL(url::kAboutBlankURL)));
   }
   return nested_urn_config_pairs;
 }
@@ -45,11 +52,20 @@
                   VisibilityToEmbedder::kOpaque,
                   VisibilityToContent::kTransparent) {}
 
+FencedFrameConfig::FencedFrameConfig(GURL urn, const GURL& mapped_url)
+    : urn_(urn),
+      mapped_url_(absl::in_place,
+                  mapped_url,
+                  VisibilityToEmbedder::kOpaque,
+                  VisibilityToContent::kTransparent) {}
+
 FencedFrameConfig::FencedFrameConfig(
+    GURL urn,
     const GURL& mapped_url,
     const SharedStorageBudgetMetadata& shared_storage_budget_metadata,
     const ReportingMetadata& reporting_metadata)
-    : mapped_url_(absl::in_place,
+    : urn_(urn),
+      mapped_url_(absl::in_place,
                   mapped_url,
                   VisibilityToEmbedder::kOpaque,
                   VisibilityToContent::kTransparent),
@@ -76,6 +92,8 @@
 blink::FencedFrame::RedactedFencedFrameConfig FencedFrameConfig::RedactFor(
     FencedFrameEntity entity) const {
   blink::FencedFrame::RedactedFencedFrameConfig redacted_config;
+  if (urn_.has_value())
+    redacted_config.urn_ = urn_;
   if (mapped_url_.has_value()) {
     redacted_config.mapped_url_ =
         blink::FencedFrame::RedactedFencedFrameProperty(
@@ -125,7 +143,8 @@
                        VisibilityToContent::kOpaque) {}
 
 FencedFrameProperties::FencedFrameProperties(const FencedFrameConfig& config)
-    : mapped_url_(config.mapped_url_),
+    : urn_(config.urn_),
+      mapped_url_(config.mapped_url_),
       ad_auction_data_(config.ad_auction_data_),
       on_navigate_callback_(config.on_navigate_callback_),
       nested_urn_config_pairs_(absl::nullopt),
@@ -163,6 +182,8 @@
 blink::FencedFrame::RedactedFencedFrameProperties
 FencedFrameProperties::RedactFor(FencedFrameEntity entity) const {
   blink::FencedFrame::RedactedFencedFrameProperties redacted_properties;
+  if (urn_.has_value())
+    redacted_properties.urn_ = urn_;
   if (mapped_url_.has_value()) {
     redacted_properties.mapped_url_ =
         blink::FencedFrame::RedactedFencedFrameProperty(
diff --git a/content/browser/fenced_frame/fenced_frame_config.h b/content/browser/fenced_frame/fenced_frame_config.h
index 6108470..c49b0f9 100644
--- a/content/browser/fenced_frame/fenced_frame_config.h
+++ b/content/browser/fenced_frame/fenced_frame_config.h
@@ -191,8 +191,10 @@
 // selectURL return urns as handles to `FencedFrameConfig`s.
 struct CONTENT_EXPORT FencedFrameConfig {
   FencedFrameConfig();
-  explicit FencedFrameConfig(const GURL& url);
+  explicit FencedFrameConfig(const GURL& mapped_url);
+  FencedFrameConfig(GURL urn, const GURL& url);
   FencedFrameConfig(
+      GURL urn,
       const GURL& url,
       const SharedStorageBudgetMetadata& shared_storage_budget_metadata,
       const ReportingMetadata& reporting_metadata = ReportingMetadata());
@@ -206,6 +208,8 @@
   blink::FencedFrame::RedactedFencedFrameConfig RedactFor(
       FencedFrameEntity entity) const;
 
+  absl::optional<GURL> urn_;
+
   absl::optional<FencedFrameProperty<GURL>> mapped_url_;
 
   // Extra data set if `mapped_url` is the result of a FLEDGE auction. Used
@@ -269,6 +273,8 @@
   blink::FencedFrame::RedactedFencedFrameProperties RedactFor(
       FencedFrameEntity entity) const;
 
+  absl::optional<GURL> urn_;
+
   absl::optional<FencedFrameProperty<GURL>> mapped_url_;
 
   absl::optional<FencedFrameProperty<AdAuctionData>> ad_auction_data_;
diff --git a/content/browser/fenced_frame/fenced_frame_url_mapping.cc b/content/browser/fenced_frame/fenced_frame_url_mapping.cc
index 025ce8b2..98e5346 100644
--- a/content/browser/fenced_frame/fenced_frame_url_mapping.cc
+++ b/content/browser/fenced_frame/fenced_frame_url_mapping.cc
@@ -120,7 +120,9 @@
   GURL urn_uuid = GenerateUrnUuid();
   DCHECK(!IsMapped(urn_uuid));
 
-  return urn_uuid_to_url_map_.emplace(urn_uuid, FencedFrameConfig(url)).first;
+  return urn_uuid_to_url_map_
+      .emplace(urn_uuid, FencedFrameConfig(urn_uuid, url))
+      .first;
 }
 
 void FencedFrameURLMapping::AssignFencedFrameURLAndInterestGroupInfo(
@@ -142,6 +144,7 @@
   auto& config = urn_uuid_to_url_map_[urn_uuid];
 
   // Assign mapped URL and interest group info.
+  config.urn_.emplace(urn_uuid);
   config.mapped_url_.emplace(url, VisibilityToEmbedder::kOpaque,
                              VisibilityToContent::kTransparent);
   config.ad_auction_data_.emplace(std::move(ad_auction_data),
@@ -152,6 +155,8 @@
   std::vector<FencedFrameConfig> nested_configs;
   nested_configs.reserve(ad_component_urls.size());
   for (auto& ad_component_url : ad_component_urls) {
+    // This config has no urn:uuid. It will later be set when being read into
+    // `nested_urn_config_pairs` in `GenerateURNConfigVectorForConfigs()`.
     nested_configs.emplace_back(ad_component_url);
   }
   config.nested_configs_.emplace(std::move(nested_configs),
@@ -236,7 +241,7 @@
     reporting_metadata.metadata = std::move(reporting_metadata_map);
 
     config =
-        FencedFrameConfig(mapping_result.mapped_url,
+        FencedFrameConfig(urn_uuid, mapping_result.mapped_url,
                           mapping_result.budget_metadata, reporting_metadata);
     urn_uuid_to_url_map_.emplace(urn_uuid, *config);
   }
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.cc b/content/browser/first_party_sets/database/first_party_sets_database.cc
index effb5cf..c2b58bb 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database.cc
@@ -22,6 +22,7 @@
 #include "content/browser/first_party_sets/first_party_set_parser.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "net/first_party_sets/global_first_party_sets.h"
@@ -306,7 +307,7 @@
 
   return config.ForEachCustomizationEntry(
       [&](const net::SchemefulSite& site,
-          const absl::optional<net::FirstPartySetEntry>& entry) -> bool {
+          const net::FirstPartySetEntryOverride& entry_override) -> bool {
         DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
         DCHECK(!site.opaque());
         static constexpr char kInsertSql[] =
@@ -317,8 +318,9 @@
             db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
         insert_statement.BindString(0, browser_context_id);
         insert_statement.BindString(1, site.Serialize());
-        if (entry.has_value()) {
-          insert_statement.BindString(2, entry.value().primary().Serialize());
+        if (!entry_override.IsDeletion()) {
+          insert_statement.BindString(
+              2, entry_override.GetEntry().primary().Serialize());
         } else {
           insert_statement.BindNull(2);
         }
@@ -343,7 +345,7 @@
 
   global_first_party_sets.ForEachManualConfigEntry(
       [&](const net::SchemefulSite& site,
-          const absl::optional<net::FirstPartySetEntry>& entry) -> bool {
+          const net::FirstPartySetEntryOverride& entry_override) -> bool {
         DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
         DCHECK(!site.opaque());
         static constexpr char kInsertSql[] =
@@ -354,9 +356,11 @@
             db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
         insert_statement.BindString(0, browser_context_id);
         insert_statement.BindString(1, site.Serialize());
-        if (entry.has_value()) {
-          insert_statement.BindString(2, entry->primary().Serialize());
-          insert_statement.BindInt(3, static_cast<int>(entry->site_type()));
+        if (!entry_override.IsDeletion()) {
+          insert_statement.BindString(
+              2, entry_override.GetEntry().primary().Serialize());
+          insert_statement.BindInt(
+              3, static_cast<int>(entry_override.GetEntry().site_type()));
         } else {
           insert_statement.BindNull(2);
           insert_statement.BindNull(3);
@@ -570,8 +574,7 @@
   DCHECK_EQ(db_status_, InitStatus::kSuccess);
   DCHECK(!browser_context_id.empty());
 
-  std::vector<
-      std::pair<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>>
+  std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntryOverride>>
       results;
   static constexpr char kSelectSql[] =
       // clang-format off
@@ -596,17 +599,18 @@
     // TODO(crbug/1314039): Invalid sites should be rare case but possible.
     // Consider deleting them from DB.
     if (site.has_value()) {
-      results.emplace_back(
-          std::move(site.value()),
-          maybe_primary_site.has_value()
-              ? absl::make_optional(net::FirstPartySetEntry(
-                    maybe_primary_site.value(),
-                    // TODO(https://crbug.com/1219656): May change to use the
-                    // real site_type and site_index in the future, depending on
-                    // the design details. Use kAssociated as default site type
-                    // and null site index for now.
-                    net::SiteType::kAssociated, absl::nullopt))
-              : absl::nullopt);
+      net::FirstPartySetEntryOverride entry_override;
+      if (maybe_primary_site.has_value()) {
+        entry_override =
+            net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                maybe_primary_site.value(),
+                // TODO(https://crbug.com/1219656): May change to use the
+                // real site_type and site_index in the future, depending on
+                // the design details. Use kAssociated as default site type
+                // and null site index for now.
+                net::SiteType::kAssociated, absl::nullopt));
+      }
+      results.emplace_back(std::move(site.value()), std::move(entry_override));
     }
   }
   if (!statement.Succeeded())
@@ -643,8 +647,7 @@
   DCHECK_EQ(db_status_, InitStatus::kSuccess);
   DCHECK(!browser_context_id.empty());
 
-  std::vector<
-      std::pair<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>>
+  std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntryOverride>>
       results;
   static constexpr char kSelectSql[] =
       // clang-format off
@@ -675,16 +678,17 @@
     // TODO(crbug.com/1314039): Invalid entries should be rare case but
     // possible. Consider deleting them from DB.
     if (site.has_value()) {
-      results.emplace_back(
-          std::move(site.value()),
-          maybe_primary_site.has_value() && maybe_site_type.has_value()
-              ? absl::make_optional(net::FirstPartySetEntry(
-                    maybe_primary_site.value(),
-                    // TODO(https://crbug.com/1219656): May change to use the
-                    // real site_index in the future, depending on the design
-                    // details. Use null site index for now.
-                    maybe_site_type.value(), absl::nullopt))
-              : absl::nullopt);
+      net::FirstPartySetEntryOverride entry_override;
+      if (maybe_primary_site.has_value() && maybe_site_type.has_value()) {
+        entry_override =
+            net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                maybe_primary_site.value(),
+                // TODO(https://crbug.com/1219656): May change to use the
+                // real site_index in the future, depending on the design
+                // details. Use null site index for now.
+                maybe_site_type.value(), absl::nullopt));
+      }
+      results.emplace_back(std::move(site.value()), std::move(entry_override));
     }
   }
 
diff --git a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
index f5449a0..f6df142 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/version.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "net/first_party_sets/global_first_party_sets.h"
@@ -321,9 +322,11 @@
 
   net::FirstPartySetsContextConfig config(
       {{net::SchemefulSite(GURL(site_member1)),
-        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary_site)),
-                                net::SiteType::kAssociated, absl::nullopt)},
-       {net::SchemefulSite(GURL(site_member2)), absl::nullopt}});
+        net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+            net::SchemefulSite(GURL(primary_site)), net::SiteType::kAssociated,
+            absl::nullopt))},
+       {net::SchemefulSite(GURL(site_member2)),
+        net::FirstPartySetEntryOverride()}});
 
   OpenDatabase();
   // Trigger the lazy-initialization.
@@ -433,9 +436,11 @@
 
   net::FirstPartySetsContextConfig config(
       {{net::SchemefulSite(GURL(site_member1)),
-        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary_site)),
-                                net::SiteType::kAssociated, absl::nullopt)},
-       {net::SchemefulSite(GURL(site_member2)), absl::nullopt}});
+        net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+            net::SchemefulSite(GURL(primary_site)), net::SiteType::kAssociated,
+            absl::nullopt))},
+       {net::SchemefulSite(GURL(site_member2)),
+        net::FirstPartySetEntryOverride()}});
 
   OpenDatabase();
   // Trigger the lazy-initialization.
@@ -577,9 +582,11 @@
 
   net::FirstPartySetsContextConfig config(
       {{net::SchemefulSite(GURL(site_member1)),
-        net::FirstPartySetEntry(net::SchemefulSite(GURL(primary_site)),
-                                net::SiteType::kAssociated, absl::nullopt)},
-       {net::SchemefulSite(GURL(site_member2)), absl::nullopt}});
+        net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+            net::SchemefulSite(GURL(primary_site)), net::SiteType::kAssociated,
+            absl::nullopt))},
+       {net::SchemefulSite(GURL(site_member2)),
+        net::FirstPartySetEntryOverride()}});
 
   OpenDatabase();
   // Trigger the lazy-initialization.
@@ -1083,9 +1090,9 @@
 
   net::FirstPartySetsContextConfig config(
       {{config_site_member1,
-        net::FirstPartySetEntry(config_primary_site, net::SiteType::kAssociated,
-                                absl::nullopt)},
-       {config_site_member2, absl::nullopt}});
+        net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+            config_primary_site, net::SiteType::kAssociated, absl::nullopt))},
+       {config_site_member2, net::FirstPartySetEntryOverride()}});
 
   OpenDatabase();
   // Trigger the lazy-initialization.
diff --git a/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
index 49f6bb7..79345af 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_database_helper_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/version.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "net/first_party_sets/global_first_party_sets.h"
@@ -268,9 +269,10 @@
   net::SchemefulSite member2(GURL("https://member2.test"));
 
   net::FirstPartySetsContextConfig current_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member2, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
   });
 
   // "https://example.test" and "https://member2.test" joined FPSs via
@@ -300,18 +302,21 @@
 
   // "https://example.test" was removed from FPSs by policy modifications.
   net::FirstPartySetsContextConfig old_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
-      {example, absl::nullopt},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
+      {example, net::FirstPartySetEntryOverride()},
   });
 
   // "https://example.test" added back to FPSs.
   net::FirstPartySetsContextConfig current_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
-      {example, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
+      {example, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
   });
 
   // We don't clear site data upon joining, so the computed diff should be
@@ -328,17 +333,20 @@
   net::SchemefulSite member2(GURL("https://member2.test"));
 
   net::FirstPartySetsContextConfig old_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
-      {member2, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
   });
 
   // "https://member2.test" left FPSs via enterprise policy.
   net::FirstPartySetsContextConfig current_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
   });
 
   EXPECT_THAT(FirstPartySetsHandlerDatabaseHelper::ComputeSetsDiff(
@@ -353,21 +361,19 @@
   net::SchemefulSite member2(GURL("https://member2.test"));
 
   net::FirstPartySetsContextConfig old_config({
-      {example,
-       {net::FirstPartySetEntry(example, net::SiteType::kPrimary,
-                                absl::nullopt)}},
-      {member1,
-       {net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
-      {member2,
-       {net::FirstPartySetEntry(example, net::SiteType::kAssociated, 0)}},
+      {example, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    example, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    example, net::SiteType::kAssociated, 0))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    example, net::SiteType::kAssociated, 0))},
   });
 
   net::FirstPartySetsContextConfig current_config({
-      {member1,
-       {net::FirstPartySetEntry(member1, net::SiteType::kPrimary,
-                                absl::nullopt)}},
-      {member2,
-       {net::FirstPartySetEntry(member1, net::SiteType::kAssociated, 0)}},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    member1, net::SiteType::kPrimary, absl::nullopt))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    member1, net::SiteType::kAssociated, 0))},
   });
 
   // Expected diff: "https://example.test" left FPSs, "https://member1.test" and
@@ -390,21 +396,25 @@
   net::SchemefulSite member2(GURL("https://member2.test"));
 
   net::FirstPartySetsContextConfig old_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
-      {bar,
-       {net::FirstPartySetEntry(bar, net::SiteType::kPrimary, absl::nullopt)}},
-      {member2, {net::FirstPartySetEntry(bar, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
+      {bar, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                bar, net::SiteType::kPrimary, absl::nullopt))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    bar, net::SiteType::kAssociated, 0))},
   });
 
   net::FirstPartySetsContextConfig current_config({
-      {foo,
-       {net::FirstPartySetEntry(foo, net::SiteType::kPrimary, absl::nullopt)}},
-      {member2, {net::FirstPartySetEntry(foo, net::SiteType::kAssociated, 0)}},
-      {bar,
-       {net::FirstPartySetEntry(bar, net::SiteType::kPrimary, absl::nullopt)}},
-      {member1, {net::FirstPartySetEntry(bar, net::SiteType::kAssociated, 0)}},
+      {foo, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                foo, net::SiteType::kPrimary, absl::nullopt))},
+      {member2, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    foo, net::SiteType::kAssociated, 0))},
+      {bar, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                bar, net::SiteType::kPrimary, absl::nullopt))},
+      {member1, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                    bar, net::SiteType::kAssociated, 0))},
   });
 
   EXPECT_THAT(FirstPartySetsHandlerDatabaseHelper::ComputeSetsDiff(
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 7a5bbb2..945144c 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -12,9 +12,12 @@
 #include "content/browser/preloading/prefetch/prefetch_cookie_listener.h"
 #include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 #include "content/browser/preloading/prefetch/prefetch_network_context.h"
+#include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
+#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
 #include "content/browser/preloading/prefetch/prefetch_status.h"
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
 #include "content/browser/preloading/prefetch/proxy_lookup_client_impl.h"
@@ -209,14 +212,41 @@
 void PrefetchContainer::TakeURLLoader(
     std::unique_ptr<network::SimpleURLLoader> loader) {
   DCHECK(!loader_);
+  DCHECK(!PrefetchUseStreamingURLLoader());
   loader_ = std::move(loader);
 }
 
 void PrefetchContainer::ResetURLLoader() {
   DCHECK(loader_);
+  DCHECK(!PrefetchUseStreamingURLLoader());
   loader_.reset();
 }
 
+void PrefetchContainer::TakeStreamingURLLoader(
+    std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader) {
+  DCHECK(!streaming_loader_);
+  DCHECK(PrefetchUseStreamingURLLoader());
+  streaming_loader_ = std::move(streaming_loader);
+}
+
+std::unique_ptr<PrefetchStreamingURLLoader>
+PrefetchContainer::ReleaseStreamingLoader() {
+  DCHECK(streaming_loader_);
+  DCHECK(PrefetchUseStreamingURLLoader());
+  return std::move(streaming_loader_);
+}
+
+void PrefetchContainer::ResetStreamingLoader() {
+  DCHECK(streaming_loader_);
+  DCHECK(PrefetchUseStreamingURLLoader());
+
+  // The streaming URL loader can be deleted in one of its callbacks, so instead
+  // of deleting it immediately, it is made self owned and then deletes itself.
+  PrefetchStreamingURLLoader* raw_streaming_loader = streaming_loader_.get();
+  raw_streaming_loader->MakeSelfOwnedAndDeleteSoon(
+      std::move(streaming_loader_));
+}
+
 void PrefetchContainer::OnPrefetchProbeResult(
     PrefetchProbeResult probe_result) {
   probe_result_ = probe_result;
@@ -243,10 +273,22 @@
 }
 
 void PrefetchContainer::OnPrefetchComplete() {
-  if (!loader_)
+  if (!loader_ && !streaming_loader_) {
     return;
-  UpdatePrefetchRequestMetrics(loader_->CompletionStatus(),
-                               loader_->ResponseInfo());
+  }
+
+  absl::optional<network::URLLoaderCompletionStatus> completion_status;
+  const network::mojom::URLResponseHead* head;
+  if (loader_) {
+    completion_status = loader_->CompletionStatus();
+    head = loader_->ResponseInfo();
+  } else if (streaming_loader_) {
+    completion_status = streaming_loader_->GetCompletionStatus();
+    head = streaming_loader_->GetHead();
+  }
+
+  UpdatePrefetchRequestMetrics(completion_status, head);
+  UpdateServingPageMetrics();
 }
 
 void PrefetchContainer::UpdatePrefetchRequestMetrics(
@@ -269,12 +311,20 @@
         completion_status->completion_time - head->load_timing.request_start;
 }
 
-bool PrefetchContainer::HasValidPrefetchedResponse(
+bool PrefetchContainer::IsPrefetchServable(
     base::TimeDelta cacheable_duration) const {
-  return prefetched_response_ != nullptr &&
-         prefetch_received_time_.has_value() &&
-         base::TimeTicks::Now() <
-             prefetch_received_time_.value() + cacheable_duration;
+  // Whether or not the response (either full or partial) from the streaming URL
+  // loader is servable.
+  bool streaming_loader_servable =
+      streaming_loader_ && streaming_loader_->Servable(cacheable_duration);
+
+  // Whether or not there is a valid response from the non-streaming URL loader.
+  bool valid_response =
+      prefetched_response_ != nullptr && prefetch_received_time_.has_value() &&
+      base::TimeTicks::Now() <
+          prefetch_received_time_.value() + cacheable_duration;
+
+  return streaming_loader_servable || valid_response;
 }
 
 void PrefetchContainer::TakePrefetchedResponse(
@@ -285,7 +335,6 @@
   prefetch_received_time_ = base::TimeTicks::Now();
   prefetched_response_ = std::move(prefetched_response);
 
-  OnPrefetchedResponseHeadReceived();
   if (prefetch_document_manager_) {
     prefetch_document_manager_->OnPrefetchSuccessful();
   }
@@ -298,7 +347,35 @@
 }
 
 const network::mojom::URLResponseHead* PrefetchContainer::GetHead() {
-  return prefetched_response_ ? prefetched_response_->GetHead() : nullptr;
+  if (prefetched_response_) {
+    return prefetched_response_->GetHead();
+  }
+
+  if (streaming_loader_) {
+    return streaming_loader_->GetHead();
+  }
+
+  return nullptr;
+}
+
+void PrefetchContainer::SetServingPageMetrics(
+    base::WeakPtr<PrefetchServingPageMetricsContainer>
+        serving_page_metrics_container) {
+  serving_page_metrics_container_ = serving_page_metrics_container;
+}
+
+void PrefetchContainer::UpdateServingPageMetrics() {
+  if (!serving_page_metrics_container_) {
+    return;
+  }
+
+  serving_page_metrics_container_->SetRequiredPrivatePrefetchProxy(
+      GetPrefetchType().IsProxyRequired());
+  serving_page_metrics_container_->SetPrefetchHeaderLatency(
+      GetPrefetchHeaderLatency());
+  if (HasPrefetchStatus()) {
+    serving_page_metrics_container_->SetPrefetchStatus(GetPrefetchStatus());
+  }
 }
 
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h
index 7f10f8a..f5b9686 100644
--- a/content/browser/preloading/prefetch/prefetch_container.h
+++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -33,6 +33,8 @@
 class PrefetchDocumentManager;
 class PrefetchNetworkContext;
 class PrefetchService;
+class PrefetchServingPageMetricsContainer;
+class PrefetchStreamingURLLoader;
 class PrefetchedMainframeResponseContainer;
 class ProxyLookupClientImpl;
 
@@ -136,6 +138,17 @@
   network::SimpleURLLoader* GetLoader() { return loader_.get(); }
   void ResetURLLoader();
 
+  // The streaming URL loader used to make the network requests for this
+  // prefetch, and then serve the results. Only used if
+  // |PrefetchUseStreamingURLLoader| is true.
+  void TakeStreamingURLLoader(
+      std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader);
+  PrefetchStreamingURLLoader* GetStreamingLoader() {
+    return streaming_loader_.get();
+  }
+  std::unique_ptr<PrefetchStreamingURLLoader> ReleaseStreamingLoader();
+  void ResetStreamingLoader();
+
   // The |PrefetchDocumentManager| that requested |this|.
   PrefetchDocumentManager* GetPrefetchDocumentManager() const;
 
@@ -157,8 +170,8 @@
   // resource.
   void OnPrefetchComplete();
 
-  // Whether or not |this| has a prefetched response.
-  bool HasValidPrefetchedResponse(base::TimeDelta cacheable_duration) const;
+  // Whether or not |this| is servable.
+  bool IsPrefetchServable(base::TimeDelta cacheable_duration) const;
 
   // Called when |this| has received prefetched response's head.
   // Once this is called, we should be able to call GetHead() and receive a
@@ -186,6 +199,11 @@
     return header_latency_;
   }
 
+  // Allow for the serving page to metrics when changes to the prefetch occur.
+  void SetServingPageMetrics(base::WeakPtr<PrefetchServingPageMetricsContainer>
+                                 serving_page_metrics_container);
+  void UpdateServingPageMetrics();
+
   // Returns request id to be used by DevTools
   const std::string& RequestId() const { return request_id_; }
 
@@ -263,6 +281,10 @@
   // The URL loader used to prefetch |url_|.
   std::unique_ptr<network::SimpleURLLoader> loader_;
 
+  // The streaming URL loader used to prefetch and serve |url_|. Only used if
+  // |PrefetchUseStreamingURLLoader| is true.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader_;
+
   // The prefetched response for |url_|.
   std::unique_ptr<PrefetchedMainframeResponseContainer> prefetched_response_;
 
@@ -306,6 +328,11 @@
   // A callback that runs once |cookie_copy_status_| is set to |kCompleted|.
   base::OnceClosure on_cookie_copy_complete_callback_;
 
+  // Reference to metrics related to the page that considered using this
+  // prefetch.
+  base::WeakPtr<PrefetchServingPageMetricsContainer>
+      serving_page_metrics_container_;
+
   // Request identifier used by DevTools
   std::string request_id_;
 
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index c81321f..4c0fa6a 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -139,7 +139,7 @@
   EXPECT_TRUE(prefetch_container.IsDecoy());
 }
 
-TEST_F(PrefetchContainerTest, ValidResponse) {
+TEST_F(PrefetchContainerTest, Servable) {
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
       PrefetchType(/*use_isolated_network_context=*/true,
@@ -153,8 +153,8 @@
 
   task_environment()->FastForwardBy(base::Minutes(2));
 
-  EXPECT_FALSE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(1)));
-  EXPECT_TRUE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(3)));
+  EXPECT_FALSE(prefetch_container.IsPrefetchServable(base::Minutes(1)));
+  EXPECT_TRUE(prefetch_container.IsPrefetchServable(base::Minutes(3)));
   EXPECT_TRUE(prefetch_container.GetHead());
 }
 
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index aea0044..a713c15 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -102,14 +102,9 @@
   if (prefetch_iter->second->HasPrefetchBeenConsideredToServe())
     return;
 
-  serving_page_metrics_container->SetRequiredPrivatePrefetchProxy(
-      prefetch_iter->second->GetPrefetchType().IsProxyRequired());
-  serving_page_metrics_container->SetPrefetchHeaderLatency(
-      prefetch_iter->second->GetPrefetchHeaderLatency());
-  if (prefetch_iter->second->HasPrefetchStatus()) {
-    serving_page_metrics_container->SetPrefetchStatus(
-        prefetch_iter->second->GetPrefetchStatus());
-  }
+  prefetch_iter->second->SetServingPageMetrics(
+      serving_page_metrics_container->GetWeakPtr());
+  prefetch_iter->second->UpdateServingPageMetrics();
 
   // Inform |PrefetchService| of the navigation to the prefetch.
   // |navigation_handle->GetURL()| and |prefetched_iter->second->GetURL()|
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 002337f6..4a9905b 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -159,6 +159,7 @@
     auto response = std::make_unique<PrefetchedMainframeResponseContainer>(
         info, std::move(head), std::move(body));
     GetPrefetches()[0]->TakePrefetchedResponse(std::move(response));
+    GetPrefetches()[0]->OnPrefetchedResponseHeadReceived();
 
     const auto* urls_with_no_vary_search =
         helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
@@ -199,6 +200,7 @@
     auto response = std::make_unique<PrefetchedMainframeResponseContainer>(
         info, std::move(head), std::move(body));
     GetPrefetches().back()->TakePrefetchedResponse(std::move(response));
+    GetPrefetches().back()->OnPrefetchedResponseHeadReceived();
 
     const auto& helper = prefetch_document_manager->GetNoVarySearchHelper();
     const auto* urls_with_no_vary_search =
diff --git a/content/browser/preloading/prefetch/prefetch_params.cc b/content/browser/preloading/prefetch/prefetch_params.cc
index 8a1f3d4..b7decd4 100644
--- a/content/browser/preloading/prefetch/prefetch_params.cc
+++ b/content/browser/preloading/prefetch/prefetch_params.cc
@@ -176,13 +176,11 @@
 bool PrefetchCanaryCheckEnabled() {
   return base::GetFieldTrialParamByFeatureAsBool(
       features::kPrefetchUseContentRefactor, "do_canary", true);
-  ;
 }
 
 bool PrefetchTLSCanaryCheckEnabled() {
   return base::GetFieldTrialParamByFeatureAsBool(
       features::kPrefetchUseContentRefactor, "do_tls_canary", false);
-  ;
 }
 
 GURL PrefetchTLSCanaryCheckURL(const GURL& default_tls_canary_check_url) {
@@ -219,4 +217,9 @@
       features::kPrefetchUseContentRefactor, "canary_check_retries", 1);
 }
 
+bool PrefetchUseStreamingURLLoader() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      features::kPrefetchUseContentRefactor, "use_streaming_url_loader", true);
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_params.h b/content/browser/preloading/prefetch/prefetch_params.h
index df19ef4..00b0e9b4 100644
--- a/content/browser/preloading/prefetch/prefetch_params.h
+++ b/content/browser/preloading/prefetch/prefetch_params.h
@@ -107,6 +107,10 @@
 // The number of retries to allow for canary checks.
 int PrefetchCanaryCheckRetries();
 
+// Whether or not to use |PrefetchStreamingURLLoader| to fetch and serve
+// prefetches.
+bool PrefetchUseStreamingURLLoader();
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_PARAMS_H_
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index ba63eaa..99a73c6 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -21,6 +21,7 @@
 #include "content/browser/preloading/prefetch/prefetch_origin_prober.h"
 #include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_proxy_configurator.h"
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
 #include "content/browser/preloading/prefetch/proxy_lookup_client_impl.h"
 #include "content/public/browser/browser_context.h"
@@ -139,28 +140,50 @@
   return true;
 }
 
-absl::optional<base::TimeDelta> GetTotalPrefetchTime(
+void RecordPrefetchProxyPrefetchMainframeTotalTime(
     network::mojom::URLResponseHead* head) {
   DCHECK(head);
 
   base::Time start = head->request_time;
   base::Time end = head->response_time;
 
-  if (start.is_null() || end.is_null())
-    return absl::nullopt;
-  return end - start;
+  if (start.is_null() || end.is_null()) {
+    return;
+  }
+
+  UMA_HISTOGRAM_CUSTOM_TIMES("PrefetchProxy.Prefetch.Mainframe.TotalTime",
+                             end - start, base::Milliseconds(10),
+                             base::Seconds(30), 100);
 }
 
-absl::optional<base::TimeDelta> GetPrefetchConnectTime(
+void RecordPrefetchProxyPrefetchMainframeConnectTime(
     network::mojom::URLResponseHead* head) {
   DCHECK(head);
 
   base::TimeTicks start = head->load_timing.connect_timing.connect_start;
   base::TimeTicks end = head->load_timing.connect_timing.connect_end;
 
-  if (start.is_null() || end.is_null())
-    return absl::nullopt;
-  return end - start;
+  if (start.is_null() || end.is_null()) {
+    return;
+  }
+
+  UMA_HISTOGRAM_TIMES("PrefetchProxy.Prefetch.Mainframe.ConnectTime",
+                      end - start);
+}
+
+void RecordPrefetchProxyPrefetchMainframeRespCode(int response_code) {
+  base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                           response_code);
+}
+
+void RecordPrefetchProxyPrefetchMainframeNetError(int net_error) {
+  base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.NetError",
+                           std::abs(net_error));
+}
+
+void RecordPrefetchProxyPrefetchMainframeBodyLength(int64_t body_length) {
+  UMA_HISTOGRAM_COUNTS_10M("PrefetchProxy.Prefetch.Mainframe.BodyLength",
+                           body_length);
 }
 
 void RecordPrefetchProxyPrefetchMainframeCookiesToCopy(
@@ -745,24 +768,43 @@
             policy_exception_justification: "Not implemented."
         })");
 
-  std::unique_ptr<network::SimpleURLLoader> loader =
-      network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
+  if (PrefetchUseStreamingURLLoader()) {
+    std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+        std::make_unique<PrefetchStreamingURLLoader>(
+            GetURLLoaderFactory(prefetch_container), std::move(request),
+            traffic_annotation, PrefetchTimeoutDuration(),
+            base::BindOnce(&PrefetchService::OnPrefetchResponseStarted,
+                           base::Unretained(this), prefetch_container),
+            base::BindOnce(
+                &PrefetchService::OnStreamingPrefetchResponseCompleted,
+                base::Unretained(this), prefetch_container),
+            base::BindRepeating(&PrefetchService::OnPrefetchRedirect,
+                                base::Unretained(this), prefetch_container));
 
-  loader->SetOnRedirectCallback(
-      base::BindRepeating(&PrefetchService::OnPrefetchRedirect,
-                          base::Unretained(this), prefetch_container));
-  loader->SetAllowHttpErrorResults(true);
-  loader->SetTimeoutDuration(PrefetchTimeoutDuration());
-  loader->SetURLLoaderFactoryOptions(
-      network::mojom::kURLLoadOptionSendSSLInfoWithResponse |
-      network::mojom::kURLLoadOptionSniffMimeType |
-      network::mojom::kURLLoadOptionSendSSLInfoForCertificateError);
-  loader->DownloadToString(GetURLLoaderFactory(prefetch_container),
-                           base::BindOnce(&PrefetchService::OnPrefetchComplete,
-                                          base::Unretained(this),
-                                          prefetch_container, isolation_info),
-                           PrefetchMainframeBodyLengthLimit());
-  prefetch_container->TakeURLLoader(std::move(loader));
+    prefetch_container->TakeStreamingURLLoader(std::move(streaming_loader));
+  } else {
+    std::unique_ptr<network::SimpleURLLoader> loader =
+        network::SimpleURLLoader::Create(std::move(request),
+                                         traffic_annotation);
+
+    loader->SetOnRedirectCallback(
+        base::BindRepeating(&PrefetchService::OnPrefetchRedirect,
+                            base::Unretained(this), prefetch_container));
+    loader->SetAllowHttpErrorResults(true);
+    loader->SetTimeoutDuration(PrefetchTimeoutDuration());
+    loader->SetURLLoaderFactoryOptions(
+        network::mojom::kURLLoadOptionSendSSLInfoWithResponse |
+        network::mojom::kURLLoadOptionSniffMimeType |
+        network::mojom::kURLLoadOptionSendSSLInfoForCertificateError);
+    loader->DownloadToString(
+        GetURLLoaderFactory(prefetch_container),
+        base::BindOnce(&PrefetchService::OnPrefetchComplete,
+                       base::Unretained(this), prefetch_container,
+                       isolation_info),
+        PrefetchMainframeBodyLengthLimit());
+    prefetch_container->TakeURLLoader(std::move(loader));
+  }
+
   active_prefetches_.insert(prefetch_container->GetPrefetchContainerKey());
 
   PrefetchDocumentManager* prefetch_document_manager =
@@ -820,7 +862,11 @@
       PrefetchStatus::kPrefetchFailedRedirectsDisabled);
 
   // Cancels current request.
-  prefetch_container->ResetURLLoader();
+  if (PrefetchUseStreamingURLLoader()) {
+    prefetch_container->ResetStreamingLoader();
+  } else {
+    prefetch_container->ResetURLLoader();
+  }
 
   // Send DevTools event
   const auto& devtools_observer = prefetch_container->GetDevToolsObserver();
@@ -843,6 +889,7 @@
     const net::IsolationInfo& isolation_info,
     std::unique_ptr<std::string> body) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!PrefetchUseStreamingURLLoader());
 
   if (!prefetch_container)
     return;
@@ -861,9 +908,8 @@
     return;
   }
 
-  base::UmaHistogramSparse(
-      "PrefetchProxy.Prefetch.Mainframe.NetError",
-      std::abs(prefetch_container->GetLoader()->NetError()));
+  RecordPrefetchProxyPrefetchMainframeNetError(
+      prefetch_container->GetLoader()->NetError());
 
   const auto& devtools_observer = prefetch_container->GetDevToolsObserver();
   if (devtools_observer) {
@@ -918,31 +964,17 @@
     std::unique_ptr<std::string> body) {
   DCHECK(prefetch_container);
   DCHECK(!head->was_fetched_via_cache);
+  DCHECK(!PrefetchUseStreamingURLLoader());
 
   if (!head->headers)
     return;
 
-  UMA_HISTOGRAM_COUNTS_10M("PrefetchProxy.Prefetch.Mainframe.BodyLength",
-                           body->size());
-
-  absl::optional<base::TimeDelta> total_time = GetTotalPrefetchTime(head.get());
-  if (total_time) {
-    UMA_HISTOGRAM_CUSTOM_TIMES("PrefetchProxy.Prefetch.Mainframe.TotalTime",
-                               *total_time, base::Milliseconds(10),
-                               base::Seconds(30), 100);
-  }
-
-  absl::optional<base::TimeDelta> connect_time =
-      GetPrefetchConnectTime(head.get());
-  if (connect_time) {
-    UMA_HISTOGRAM_TIMES("PrefetchProxy.Prefetch.Mainframe.ConnectTime",
-                        *connect_time);
-  }
+  RecordPrefetchProxyPrefetchMainframeBodyLength(body->size());
+  RecordPrefetchProxyPrefetchMainframeTotalTime(head.get());
+  RecordPrefetchProxyPrefetchMainframeConnectTime(head.get());
 
   int response_code = head->headers->response_code();
-
-  base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.RespCode",
-                           response_code);
+  RecordPrefetchProxyPrefetchMainframeRespCode(response_code);
   if (response_code < 200 | response_code >= 300) {
     prefetch_container->SetPrefetchStatus(
         PrefetchStatus::kPrefetchFailedNon2XX);
@@ -973,22 +1005,152 @@
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
           isolation_info, std::move(head), std::move(body)));
+  prefetch_container->OnPrefetchedResponseHeadReceived();
   prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchSuccessful);
 }
 
+PrefetchStreamingURLLoaderStatus PrefetchService::OnPrefetchResponseStarted(
+    base::WeakPtr<PrefetchContainer> prefetch_container,
+    network::mojom::URLResponseHead* head) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(PrefetchUseStreamingURLLoader());
+
+  if (!prefetch_container || prefetch_container->IsDecoy()) {
+    return PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy;
+  }
+
+  if (!head) {
+    return PrefetchStreamingURLLoaderStatus::kFailedInvalidHead;
+  }
+
+  const auto& devtools_observer = prefetch_container->GetDevToolsObserver();
+  if (devtools_observer) {
+    devtools_observer->OnPrefetchResponseReceived(
+        prefetch_container->GetURL(), prefetch_container->RequestId(), *head);
+  }
+
+  if (!head->headers) {
+    return PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders;
+  }
+
+  RecordPrefetchProxyPrefetchMainframeTotalTime(head);
+  RecordPrefetchProxyPrefetchMainframeConnectTime(head);
+
+  int response_code = head->headers->response_code();
+  RecordPrefetchProxyPrefetchMainframeRespCode(response_code);
+  if (response_code < 200 || response_code >= 300) {
+    prefetch_container->SetPrefetchStatus(
+        PrefetchStatus::kPrefetchFailedNon2XX);
+
+    if (response_code == net::HTTP_SERVICE_UNAVAILABLE) {
+      base::TimeDelta retry_after;
+      std::string retry_after_string;
+      if (head->headers->EnumerateHeader(nullptr, "Retry-After",
+                                         &retry_after_string) &&
+          net::HttpUtil::ParseRetryAfterHeader(
+              retry_after_string, base::Time::Now(), &retry_after) &&
+          delegate_) {
+        delegate_->ReportOriginRetryAfter(prefetch_container->GetURL(),
+                                          retry_after);
+      }
+    }
+    return PrefetchStreamingURLLoaderStatus::kFailedNon2XX;
+  }
+
+  if (PrefetchServiceHTMLOnly() && head->mime_type != "text/html") {
+    prefetch_container->SetPrefetchStatus(
+        PrefetchStatus::kPrefetchFailedMIMENotSupported);
+    return PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported;
+  }
+
+  prefetch_container->OnPrefetchedResponseHeadReceived();
+  return PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody;
+}
+
+void PrefetchService::OnStreamingPrefetchResponseCompleted(
+    base::WeakPtr<PrefetchContainer> prefetch_container,
+    const network::URLLoaderCompletionStatus& completion_status) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(PrefetchUseStreamingURLLoader());
+
+  if (!prefetch_container) {
+    return;
+  }
+
+  DCHECK(
+      active_prefetches_.find(prefetch_container->GetPrefetchContainerKey()) !=
+      active_prefetches_.end());
+  active_prefetches_.erase(prefetch_container->GetPrefetchContainerKey());
+
+  prefetch_container->OnPrefetchComplete();
+
+  if (prefetch_container->IsDecoy()) {
+    prefetch_container->ResetStreamingLoader();
+    Prefetch();
+    return;
+  }
+
+  // TODO(https://crbug.com/1399956): Call
+  // SpeculationHostDevToolsObserver::OnPrefetchBodyDataReceived with body of
+  // the response.
+  const auto& devtools_observer = prefetch_container->GetDevToolsObserver();
+  if (devtools_observer) {
+    devtools_observer->OnPrefetchRequestComplete(
+        prefetch_container->RequestId(), completion_status);
+  }
+
+  int net_error = completion_status.error_code;
+  int64_t body_length = completion_status.decoded_body_length;
+
+  RecordPrefetchProxyPrefetchMainframeNetError(net_error);
+
+  // Updates the prefetch's status if it hasn't been updated since the request
+  // first started.
+  if (!prefetch_container->HasPrefetchStatus() ||
+      prefetch_container->GetPrefetchStatus() ==
+          PrefetchStatus::kPrefetchNotFinishedInTime) {
+    prefetch_container->SetPrefetchStatus(
+        net_error == net::OK ? PrefetchStatus::kPrefetchSuccessful
+                             : PrefetchStatus::kPrefetchFailedNetError);
+    prefetch_container->UpdateServingPageMetrics();
+  }
+
+  if (net_error == net::OK) {
+    RecordPrefetchProxyPrefetchMainframeBodyLength(body_length);
+  }
+
+  if (prefetch_container->GetStreamingLoader()) {
+    // If the prefetch from the streaming URL loader cannot be served at this
+    // point, then it can be discarded.
+    if (!prefetch_container->GetStreamingLoader()->Servable(
+            PrefetchCacheableDuration())) {
+      prefetch_container->ResetStreamingLoader();
+    } else {
+      PrefetchDocumentManager* prefetch_document_manager =
+          prefetch_container->GetPrefetchDocumentManager();
+      if (prefetch_document_manager) {
+        prefetch_document_manager->OnPrefetchSuccessful();
+      }
+    }
+  }
+
+  Prefetch();
+}
+
 void PrefetchService::PrepareToServe(
     const GURL& url,
     base::WeakPtr<PrefetchContainer> prefetch_container) {
   // Ensure |this| has this prefetch.
   if (all_prefetches_.find(prefetch_container->GetPrefetchContainerKey()) ==
-      all_prefetches_.end())
+      all_prefetches_.end()) {
     return;
+  }
 
   // If the prefetch isn't ready to be served, then stop.
   if (prefetch_container->HaveDefaultContextCookiesChanged() ||
-      !prefetch_container->HasValidPrefetchedResponse(
-          PrefetchCacheableDuration()))
+      !prefetch_container->IsPrefetchServable(PrefetchCacheableDuration())) {
     return;
+  }
 
   // If the prefetch has a valid response, then it must be in
   // |owned_prefetches_|.
@@ -1121,7 +1283,7 @@
         num_matching_eligible_prefetch++;
       }
 
-      if (prefetch_iter.second->HasValidPrefetchedResponse(
+      if (prefetch_iter.second->IsPrefetchServable(
               PrefetchCacheableDuration()) &&
           !prefetch_iter.second->HasPrefetchBeenConsideredToServe()) {
         num_matching_servable_prefetch++;
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h
index 71d0af7d..a557d9b 100644
--- a/content/browser/preloading/prefetch/prefetch_service.h
+++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
 #include "content/browser/preloading/prefetch/prefetch_status.h"
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/global_routing_id.h"
 #include "net/cookies/canonical_cookie.h"
@@ -202,6 +203,21 @@
       network::mojom::URLResponseHeadPtr head,
       std::unique_ptr<std::string> body);
 
+  // Called when the response for |prefetch_container| has started. Based on
+  // |head|, returns a status to inform the |PrefetchStreamingURLLoader| whether
+  // the prefetch is servable. If servable, then |kHeadReceivedWaitingOnBody|
+  // will be returned, otherwise a valid failure status is returned.
+  PrefetchStreamingURLLoaderStatus OnPrefetchResponseStarted(
+      base::WeakPtr<PrefetchContainer> prefetch_container,
+      network::mojom::URLResponseHead* head);
+
+  // Called when the response for |prefetch_container| has completed when using
+  // the streaming URL loader. Only used if |PrefetchUseStreamingURLLoader| is
+  // true.
+  void OnStreamingPrefetchResponseCompleted(
+      base::WeakPtr<PrefetchContainer> prefetch_container,
+      const network::URLLoaderCompletionStatus& completion_status);
+
   // Copies any cookies in the isolated network context associated with
   // |prefetch_container| to the default network context.
   void CopyIsolatedCookies(base::WeakPtr<PrefetchContainer> prefetch_container);
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index 58bda80..ef0fb01f 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -285,17 +285,12 @@
     EXPECT_FALSE(isolation_info.site_for_cookies().IsNull());
   }
 
-  void MakeResponseAndWait(
+  network::mojom::URLResponseHeadPtr CreateURLResponseHeadForPrefetch(
       net::HttpStatusCode http_status,
-      net::Error net_error,
       const std::string mime_type,
       bool use_prefetch_proxy,
-      std::vector<std::pair<std::string, std::string>> headers,
-      const std::string& body) {
-    network::TestURLLoaderFactory::PendingRequest* request =
-        test_url_loader_factory_.GetPendingRequest(0);
-    ASSERT_TRUE(request);
-
+      const std::vector<std::pair<std::string, std::string>>& headers,
+      const GURL& request_url) {
     auto head = network::CreateURLResponseHead(http_status);
 
     head->response_time = base::Time::Now();
@@ -325,10 +320,27 @@
       head->headers->AddHeader(header.first, header.second);
     }
     if (!head->parsed_headers) {
-      head->parsed_headers = network::PopulateParsedHeaders(
-          head->headers.get(), request->request.url);
+      head->parsed_headers =
+          network::PopulateParsedHeaders(head->headers.get(), request_url);
     }
 
+    return head;
+  }
+
+  void MakeResponseAndWait(
+      net::HttpStatusCode http_status,
+      net::Error net_error,
+      const std::string mime_type,
+      bool use_prefetch_proxy,
+      std::vector<std::pair<std::string, std::string>> headers,
+      const std::string& body) {
+    network::TestURLLoaderFactory::PendingRequest* request =
+        test_url_loader_factory_.GetPendingRequest(0);
+    ASSERT_TRUE(request);
+
+    auto head = CreateURLResponseHeadForPrefetch(http_status, mime_type,
+                                                 use_prefetch_proxy, headers,
+                                                 request->request.url);
     network::URLLoaderCompletionStatus status(net_error);
     test_url_loader_factory_.AddResponse(request->request.url, std::move(head),
                                          body, status);
@@ -338,6 +350,62 @@
     test_url_loader_factory_.ClearResponses();
   }
 
+  void SendHeadOfResponseAndWait(
+      net::HttpStatusCode http_status,
+      const std::string mime_type,
+      bool use_prefetch_proxy,
+      std::vector<std::pair<std::string, std::string>> headers,
+      uint32_t expected_total_body_size) {
+    ASSERT_FALSE(producer_handle_);
+
+    network::TestURLLoaderFactory::PendingRequest* request =
+        test_url_loader_factory_.GetPendingRequest(0);
+    ASSERT_TRUE(request);
+    ASSERT_TRUE(request->client);
+
+    auto head = CreateURLResponseHeadForPrefetch(http_status, mime_type,
+                                                 use_prefetch_proxy, headers,
+                                                 request->request.url);
+
+    mojo::ScopedDataPipeConsumerHandle body;
+    EXPECT_EQ(
+        mojo::CreateDataPipe(expected_total_body_size, producer_handle_, body),
+        MOJO_RESULT_OK);
+
+    request->client->OnReceiveResponse(std::move(head), std::move(body),
+                                       absl::nullopt);
+    task_environment()->RunUntilIdle();
+  }
+
+  void SendBodyContentOfResponseAndWait(const std::string& body) {
+    ASSERT_TRUE(producer_handle_);
+
+    uint32_t bytes_written = body.size();
+    EXPECT_EQ(producer_handle_->WriteData(body.data(), &bytes_written,
+                                          MOJO_WRITE_DATA_FLAG_ALL_OR_NONE),
+              MOJO_RESULT_OK);
+    task_environment()->RunUntilIdle();
+  }
+
+  void CompleteResponseAndWait(net::Error net_error,
+                               uint32_t expected_total_body_size) {
+    ASSERT_TRUE(producer_handle_);
+
+    network::TestURLLoaderFactory::PendingRequest* request =
+        test_url_loader_factory_.GetPendingRequest(0);
+    ASSERT_TRUE(request);
+    ASSERT_TRUE(request->client);
+
+    producer_handle_.reset();
+
+    network::URLLoaderCompletionStatus completion_status(net_error);
+    completion_status.decoded_body_length = expected_total_body_size;
+    request->client->OnComplete(completion_status);
+    task_environment()->RunUntilIdle();
+
+    test_url_loader_factory_.ClearResponses();
+  }
+
   bool SetCookie(const GURL& url, const std::string& value) {
     std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
         url, value, base::Time::Now(), /*server_time=*/absl::nullopt,
@@ -411,6 +479,8 @@
 
   std::unique_ptr<ScopedPrefetchServiceContentBrowserClient>
       test_content_browser_client_;
+
+  mojo::ScopedDataPipeProducerHandle producer_handle_;
 };
 
 TEST_F(PrefetchServiceTest, CreateServiceWhenFeatureEnabled) {
@@ -489,12 +559,10 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
-  auto prefetched_response =
-      serveable_prefetch_container->ReleasePrefetchedResponse();
-  EXPECT_TRUE(prefetched_response->GetHead());
-  EXPECT_TRUE(prefetched_response->ReleaseHead()->was_in_prefetch_cache);
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+  ASSERT_TRUE(serveable_prefetch_container->GetHead());
+  EXPECT_TRUE(serveable_prefetch_container->GetHead()->was_in_prefetch_cache);
 }
 
 TEST_F(PrefetchServiceTest, NoPrefetchingPreloadingDisabled) {
@@ -673,8 +741,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 class PrefetchServiceAllowAllDomainsForExtendedPreloadingTest
@@ -754,8 +822,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceAllowAllDomainsForExtendedPreloadingTest,
@@ -876,8 +944,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleHostnameNonUnique) {
@@ -1146,8 +1214,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleOriginWithinRetryAfterWindow) {
@@ -1258,8 +1326,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleServiceWorkerRegistered) {
@@ -1370,8 +1438,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleUserHasCookies) {
@@ -1480,8 +1548,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, EligibleSameOriginPrefetchCanHaveExistingCookies) {
@@ -1540,8 +1608,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleExistingConnectProxy) {
@@ -1662,8 +1730,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 
   PrefetchService::SetNetworkContextForProxyLookupForTesting(nullptr);
 }
@@ -1893,8 +1961,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotServeableNavigationInDifferentRenderFrameHost) {
@@ -2032,7 +2100,7 @@
   EXPECT_TRUE(serveable_prefetch_container1->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container1->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container1->HasValidPrefetchedResponse(
+  EXPECT_TRUE(serveable_prefetch_container1->IsPrefetchServable(
       base::TimeDelta::Max()));
 
   Navigate(GURL("https://example2.com"), main_rfh()->GetGlobalId());
@@ -2055,7 +2123,7 @@
   EXPECT_TRUE(serveable_prefetch_container2->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container2->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container2->HasValidPrefetchedResponse(
+  EXPECT_TRUE(serveable_prefetch_container2->IsPrefetchServable(
       base::TimeDelta::Max()));
 
   Navigate(GURL("https://example3.com"), main_rfh()->GetGlobalId());
@@ -2152,6 +2220,8 @@
 };
 
 TEST_F(PrefetchServiceAlwaysMakeDecoyRequestTest, DecoyRequest) {
+  base::HistogramTester histogram_tester;
+
   MakePrefetchService(
       std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
 
@@ -2170,6 +2240,17 @@
 
   Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
 
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
   absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
       PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
   EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
@@ -2195,6 +2276,8 @@
 
 TEST_F(PrefetchServiceAlwaysMakeDecoyRequestTest,
        NoDecoyRequestDisableDecoysBasedOnUserSettings) {
+  base::HistogramTester histogram_tester;
+
   std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
       std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
 
@@ -2215,6 +2298,17 @@
 
   Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
 
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
   absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
       PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
   EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
@@ -2237,6 +2331,137 @@
   EXPECT_FALSE(serveable_prefetch_container);
 }
 
+class PrefetchServiceStreamingURLLoaderTest : public PrefetchServiceTest {
+ public:
+  void InitScopedFeatureList() override {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        content::features::kPrefetchUseContentRefactor,
+        {{"ineligible_decoy_request_probability", "0"},
+         {"prefetch_container_lifetime_s", "-1"},
+         {"use_streaming_url_loader", "true"}});
+  }
+};
+
+// TODO(crbug.com/1396460): Test flaky on lacros trybots.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_StreamingURLLoaderSuccessCase \
+  DISABLED_StreamingURLLoaderSuccessCase
+#else
+#define MAYBE_StreamingURLLoaderSuccessCase StreamingURLLoaderSuccessCase
+#endif
+TEST_F(PrefetchServiceStreamingURLLoaderTest,
+       MAYBE_StreamingURLLoaderSuccessCase) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example.com"),
+                           /*use_prefetch_proxy=*/true);
+
+  // Send the head of the navigation. The prefetch should be servable after this
+  // point. The body of the response will be streaming to the serving URL loader
+  // as its received.
+  SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
+                            /*use_prefetch_proxy=*/true,
+                            {{"X-Testing", "Hello World"}},
+                            std::size(kHTMLBody));
+
+  // Navigate to the URL before the prefetch response is complete.
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  // Check the metrics while the prefetch is still in progress.
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
+            static_cast<int>(PrefetchStatus::kPrefetchNotFinishedInTime));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_FALSE(serving_page_metrics->prefetch_header_latency);
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
+            PrefetchStatus::kPrefetchNotFinishedInTime);
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+  EXPECT_TRUE(serveable_prefetch_container->GetHead());
+  EXPECT_TRUE(serveable_prefetch_container->GetHead()->was_in_prefetch_cache);
+
+  // Send the body and completion status of the request, then recheck all of the
+  // metrics.
+  SendBodyContentOfResponseAndWait(kHTMLBody);
+  CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
+
+  // Check the metrics now that the prefetch is complete.
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
+
+  referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1);
+
+  serving_page_metrics = GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
+            static_cast<int>(PrefetchStatus::kPrefetchSuccessful));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_TRUE(serving_page_metrics->prefetch_header_latency);
+  EXPECT_EQ(serving_page_metrics->prefetch_header_latency.value(),
+            base::Milliseconds(kHeaderLatency));
+
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
+            PrefetchStatus::kPrefetchSuccessful);
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+  ASSERT_TRUE(serveable_prefetch_container->GetHead());
+  EXPECT_TRUE(serveable_prefetch_container->GetHead()->was_in_prefetch_cache);
+}
+
 // TODO(https://crbug.com/1299059): Add test for incognito mode.
 
 class PrefetchServiceNoVarySearchTest : public PrefetchServiceTest {
@@ -2282,8 +2507,8 @@
   EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
   EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
-      base::TimeDelta::Max()));
+  EXPECT_TRUE(
+      serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 }
 
 }  // namespace
diff --git a/content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h b/content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h
index e191d5a..9878679 100644
--- a/content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h
+++ b/content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SERVING_PAGE_METRICS_CONTAINER_H_
 #define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SERVING_PAGE_METRICS_CONTAINER_H_
 
+#include "base/memory/weak_ptr.h"
 #include "content/browser/preloading/prefetch/prefetch_status.h"
 #include "content/public/browser/navigation_handle_user_data.h"
 #include "content/public/browser/prefetch_metrics.h"
@@ -35,6 +36,10 @@
     return serving_page_metrics_;
   }
 
+  base::WeakPtr<PrefetchServingPageMetricsContainer> GetWeakPtr() {
+    return weak_method_factory_.GetWeakPtr();
+  }
+
  private:
   explicit PrefetchServingPageMetricsContainer(
       NavigationHandle& navigation_handle);
@@ -44,6 +49,9 @@
   // to.
   PrefetchServingPageMetrics serving_page_metrics_;
 
+  base::WeakPtrFactory<PrefetchServingPageMetricsContainer>
+      weak_method_factory_{this};
+
   NAVIGATION_HANDLE_USER_DATA_KEY_DECL();
 };
 
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
new file mode 100644
index 0000000..287c99f
--- /dev/null
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
@@ -0,0 +1,318 @@
+// 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 "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
+#include "content/browser/preloading/prefetch/prefetch_container.h"
+#include "services/network/public/mojom/early_hints.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace content {
+
+PrefetchStreamingURLLoader::PrefetchStreamingURLLoader(
+    network::mojom::URLLoaderFactory* url_loader_factory,
+    std::unique_ptr<network::ResourceRequest> request,
+    const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
+    base::TimeDelta timeout_duration,
+    OnPrefetchResponseStartedCallback on_prefetch_response_started_callback,
+    OnPrefetchResponseCompletedCallback on_prefetch_response_completed_callback,
+    OnPrefetchRedirectCallback on_prefetch_redirect_callback)
+    : on_prefetch_response_started_callback_(
+          std::move(on_prefetch_response_started_callback)),
+      on_prefetch_response_completed_callback_(
+          std::move(on_prefetch_response_completed_callback)),
+      on_prefetch_redirect_callback_(std::move(on_prefetch_redirect_callback)) {
+  url_loader_factory->CreateLoaderAndStart(
+      prefetch_url_loader_.BindNewPipeAndPassReceiver(), /*request_id=*/0,
+      network::mojom::kURLLoadOptionSendSSLInfoWithResponse |
+          network::mojom::kURLLoadOptionSniffMimeType |
+          network::mojom::kURLLoadOptionSendSSLInfoForCertificateError,
+      *request,
+      prefetch_url_loader_client_receiver_.BindNewPipeAndPassRemote(
+          base::ThreadTaskRunnerHandle::Get()),
+      net::MutableNetworkTrafficAnnotationTag(network_traffic_annotation));
+  prefetch_url_loader_client_receiver_.set_disconnect_handler(base::BindOnce(
+      &PrefetchStreamingURLLoader::DisconnectPrefetchURLLoaderMojo,
+      weak_ptr_factory_.GetWeakPtr()));
+
+  if (!timeout_duration.is_zero()) {
+    timeout_timer_.Start(
+        FROM_HERE, timeout_duration,
+        base::BindOnce(&PrefetchStreamingURLLoader::OnComplete,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       network::URLLoaderCompletionStatus(net::ERR_TIMED_OUT)));
+  }
+}
+
+PrefetchStreamingURLLoader::~PrefetchStreamingURLLoader() {
+  base::UmaHistogramEnumeration(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus", status_);
+}
+
+bool PrefetchStreamingURLLoader::Servable(
+    base::TimeDelta cacheable_duration) const {
+  // If the response hasn't been received yet (meaning response_complete_time_
+  // is absl::nullopt), we can still serve the prefetch (depending on |head_|).
+  return servable_ &&
+         (!response_complete_time_.has_value() ||
+          base::TimeTicks::Now() <
+              response_complete_time_.value() + cacheable_duration);
+}
+
+void PrefetchStreamingURLLoader::DisconnectPrefetchURLLoaderMojo() {
+  prefetch_url_loader_.reset();
+  prefetch_url_loader_client_receiver_.reset();
+  prefetch_url_loader_disconnected_ = true;
+
+  if (serving_url_loader_disconnected_) {
+    PostTaskToDeleteSelf();
+  }
+}
+
+void PrefetchStreamingURLLoader::OnServingURLLoaderMojoDisconnect() {
+  serving_url_loader_receiver_.reset();
+  serving_url_loader_client_.reset();
+  serving_url_loader_disconnected_ = true;
+
+  if (prefetch_url_loader_disconnected_) {
+    PostTaskToDeleteSelf();
+  }
+}
+
+void PrefetchStreamingURLLoader::MakeSelfOwnedAndDeleteSoon(
+    std::unique_ptr<PrefetchStreamingURLLoader> self) {
+  self_pointer_ = std::move(self);
+  PostTaskToDeleteSelf();
+}
+
+void PrefetchStreamingURLLoader::PostTaskToDeleteSelf() {
+  if (!self_pointer_) {
+    return;
+  }
+
+  // To avoid UAF bugs, post a separate task to delete this object.
+  base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
+                                                     std::move(self_pointer_));
+}
+
+void PrefetchStreamingURLLoader::OnReceiveEarlyHints(
+    network::mojom::EarlyHintsPtr early_hints) {
+  if (serving_url_loader_client_) {
+    serving_url_loader_client_->OnReceiveEarlyHints(std::move(early_hints));
+    return;
+  }
+
+  event_queue_.push_back(
+      base::BindOnce(&PrefetchStreamingURLLoader::OnReceiveEarlyHints,
+                     base::Unretained(this), std::move(early_hints)));
+}
+
+void PrefetchStreamingURLLoader::OnReceiveResponse(
+    network::mojom::URLResponseHeadPtr head,
+    mojo::ScopedDataPipeConsumerHandle body,
+    absl::optional<mojo_base::BigBuffer> cached_metadata) {
+  // Cached metadata is not supported for prefetch.
+  cached_metadata.reset();
+
+  head_ = std::move(head);
+  head_->was_in_prefetch_cache = true;
+
+  // Checks head to determine if the prefetch can be served.
+  DCHECK(on_prefetch_response_started_callback_);
+  status_ = std::move(on_prefetch_response_started_callback_).Run(head_.get());
+
+  // Update servable_ based on the returned status_
+  switch (status_) {
+    case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody:
+      servable_ = true;
+      break;
+    case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
+    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
+    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
+    case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
+    case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
+      servable_ = false;
+      break;
+    case PrefetchStreamingURLLoaderStatus::kWaitingOnHead:
+    case PrefetchStreamingURLLoaderStatus::kRedirected:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion:
+    case PrefetchStreamingURLLoaderStatus::kFailedNetError:
+    case PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed:
+      NOTREACHED();
+      break;
+  }
+
+  if (!servable_) {
+    return;
+  }
+
+  head_->navigation_delivery_type =
+      network::mojom::NavigationDeliveryType::kNavigationalPrefetch;
+  body_ = std::move(body);
+}
+
+void PrefetchStreamingURLLoader::OnReceiveRedirect(
+    const net::RedirectInfo& redirect_info,
+    network::mojom::URLResponseHeadPtr head) {
+  servable_ = false;
+  status_ = PrefetchStreamingURLLoaderStatus::kRedirected;
+
+  // Redirects are currently not supported by prefetch, so is just to inform
+  // the owner of the callback of the redirect.
+  std::vector<std::string> removed_headers;
+  DCHECK(on_prefetch_redirect_callback_);
+  on_prefetch_redirect_callback_.Run(redirect_info, *head.get(),
+                                     &removed_headers);
+}
+
+void PrefetchStreamingURLLoader::OnUploadProgress(
+    int64_t current_position,
+    int64_t total_size,
+    OnUploadProgressCallback callback) {
+  // Only handle GETs.
+  NOTREACHED();
+}
+
+void PrefetchStreamingURLLoader::OnTransferSizeUpdated(
+    int32_t transfer_size_diff) {
+  if (serving_url_loader_client_) {
+    serving_url_loader_client_->OnTransferSizeUpdated(transfer_size_diff);
+    return;
+  }
+
+  event_queue_.push_back(
+      base::BindOnce(&PrefetchStreamingURLLoader::OnTransferSizeUpdated,
+                     base::Unretained(this), transfer_size_diff));
+}
+
+void PrefetchStreamingURLLoader::OnComplete(
+    const network::URLLoaderCompletionStatus& completion_status) {
+  DisconnectPrefetchURLLoaderMojo();
+  timeout_timer_.AbandonAndStop();
+
+  completion_status_ = completion_status;
+  response_complete_time_ = base::TimeTicks::Now();
+
+  if (status_ == PrefetchStreamingURLLoaderStatus::kWaitingOnHead ||
+      status_ == PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody) {
+    status_ = completion_status_->error_code == net::OK
+                  ? PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed
+                  : PrefetchStreamingURLLoaderStatus::kFailedNetError;
+  } else if (status_ == PrefetchStreamingURLLoaderStatus::
+                            kSuccessfulServedBeforeCompletion &&
+             completion_status_->error_code != net::OK) {
+    status_ = PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed;
+  }
+
+  if (completion_status_->error_code != net::OK) {
+    // Note that we may have already started serving the prefetch if it was
+    // marked as servable in |OnReceiveResponse|.
+    servable_ = false;
+  }
+
+  std::move(on_prefetch_response_completed_callback_)
+      .Run(completion_status_.value());
+
+  if (serving_url_loader_client_) {
+    ForwardCompletionStatus();
+    return;
+  }
+  event_queue_.push_back(
+      base::BindOnce(&PrefetchStreamingURLLoader::ForwardCompletionStatus,
+                     base::Unretained(this)));
+}
+
+void PrefetchStreamingURLLoader::ForwardCompletionStatus() {
+  DCHECK(serving_url_loader_client_);
+  DCHECK(completion_status_);
+  serving_url_loader_client_->OnComplete(completion_status_.value());
+}
+
+PrefetchStreamingURLLoader::RequestHandler
+PrefetchStreamingURLLoader::ServingResponseHandler(
+    std::unique_ptr<PrefetchStreamingURLLoader> self) {
+  return base::BindOnce(&PrefetchStreamingURLLoader::BindAndStart,
+                        weak_ptr_factory_.GetWeakPtr(), std::move(self));
+}
+
+void PrefetchStreamingURLLoader::BindAndStart(
+    std::unique_ptr<PrefetchStreamingURLLoader> self,
+    const network::ResourceRequest& request,
+    mojo::PendingReceiver<network::mojom::URLLoader> receiver,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
+  DCHECK(servable_);
+  DCHECK(!serving_url_loader_receiver_.is_bound());
+  DCHECK(self.get() == this);
+
+  status_ =
+      completion_status_.has_value()
+          ? PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion
+          : PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion;
+
+  // Make this self owned. This will delete itself once prefetching and serving
+  // are both complete.
+  self_pointer_ = std::move(self);
+
+  serving_url_loader_receiver_.Bind(std::move(receiver));
+  serving_url_loader_receiver_.set_disconnect_handler(base::BindOnce(
+      &PrefetchStreamingURLLoader::OnServingURLLoaderMojoDisconnect,
+      weak_ptr_factory_.GetWeakPtr()));
+  serving_url_loader_client_.Bind(std::move(client));
+
+  // Serve the prefetched response by directly passing the |body_| mojo pipe.
+  // All data that has already been received will be buffered in the pipe, and
+  // all other data will be streamed as it is received.
+  DCHECK(head_);
+  DCHECK(body_);
+  serving_url_loader_client_->OnReceiveResponse(
+      head_->Clone(), std::move(body_), absl::nullopt);
+
+  RunEventQueue();
+}
+
+void PrefetchStreamingURLLoader::RunEventQueue() {
+  DCHECK(serving_url_loader_client_);
+  for (auto& event : event_queue_) {
+    std::move(event).Run();
+  }
+  event_queue_.clear();
+}
+
+void PrefetchStreamingURLLoader::FollowRedirect(
+    const std::vector<std::string>& removed_headers,
+    const net::HttpRequestHeaders& modified_headers,
+    const net::HttpRequestHeaders& modified_cors_exempt_headers,
+    const absl::optional<GURL>& new_url) {
+  // Redirects aren't supported by prefetch, and therefore are never served.
+  NOTREACHED();
+}
+
+void PrefetchStreamingURLLoader::SetPriority(net::RequestPriority priority,
+                                             int32_t intra_priority_value) {
+  // Forward calls from the serving URL loader to the prefetch URL loader.
+  if (prefetch_url_loader_) {
+    prefetch_url_loader_->SetPriority(priority, intra_priority_value);
+  }
+}
+
+void PrefetchStreamingURLLoader::PauseReadingBodyFromNet() {
+  // Forward calls from the serving URL loader to the prefetch URL loader.
+  if (prefetch_url_loader_) {
+    prefetch_url_loader_->PauseReadingBodyFromNet();
+  }
+}
+
+void PrefetchStreamingURLLoader::ResumeReadingBodyFromNet() {
+  // Forward calls from the serving URL loader to the prefetch URL loader.
+  if (prefetch_url_loader_) {
+    prefetch_url_loader_->ResumeReadingBodyFromNet();
+  }
+}
+
+}  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
new file mode 100644
index 0000000..2ac24e07
--- /dev/null
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
@@ -0,0 +1,167 @@
+// 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 CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_H_
+#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_H_
+
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h"
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom-forward.h"
+
+namespace content {
+
+class CONTENT_EXPORT PrefetchStreamingURLLoader
+    : public network::mojom::URLLoader,
+      public network::mojom::URLLoaderClient {
+ public:
+  // This callback is used fo the owner to determine if the prefetch is valid
+  // based on |head|. If the prefetch should be servable based on |head|, then
+  // the callback should return |kHeadReceivedWaitingOnBody|. Otherwise it
+  // should return a valid failure reason.
+  using OnPrefetchResponseStartedCallback =
+      base::OnceCallback<PrefetchStreamingURLLoaderStatus(
+          network::mojom::URLResponseHead* head)>;
+
+  using OnPrefetchResponseCompletedCallback = base::OnceCallback<void(
+      const network::URLLoaderCompletionStatus& completion_status)>;
+  using OnPrefetchRedirectCallback = base::RepeatingCallback<void(
+      const net::RedirectInfo& redirect_info,
+      const network::mojom::URLResponseHead& response_head,
+      std::vector<std::string>* removed_headers)>;
+
+  PrefetchStreamingURLLoader(
+      network::mojom::URLLoaderFactory* url_loader_factory,
+      std::unique_ptr<network::ResourceRequest> request,
+      const net::NetworkTrafficAnnotationTag& network_traffic_annotation,
+      base::TimeDelta timeout_duration,
+      OnPrefetchResponseStartedCallback on_prefetch_response_started_callback,
+      OnPrefetchResponseCompletedCallback
+          on_prefetch_response_completed_callback,
+      OnPrefetchRedirectCallback on_prefetch_redirect_callback);
+  ~PrefetchStreamingURLLoader() override;
+
+  PrefetchStreamingURLLoader(const PrefetchStreamingURLLoader&) = delete;
+  PrefetchStreamingURLLoader& operator=(const PrefetchStreamingURLLoader&) =
+      delete;
+
+  bool Servable(base::TimeDelta cacheable_duration) const;
+
+  absl::optional<network::URLLoaderCompletionStatus> GetCompletionStatus()
+      const {
+    return completion_status_;
+  }
+  const network::mojom::URLResponseHead* GetHead() const { return head_.get(); }
+
+  using RequestHandler = base::OnceCallback<void(
+      const network::ResourceRequest& resource_request,
+      mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client)>;
+  RequestHandler ServingResponseHandler(
+      std::unique_ptr<PrefetchStreamingURLLoader> self);
+
+  // The streaming URL loader can be deleted in one of its callbacks, so instead
+  // of deleting it immediately, it is made self owned and then deletes itself.
+  void MakeSelfOwnedAndDeleteSoon(
+      std::unique_ptr<PrefetchStreamingURLLoader> self);
+
+  base::WeakPtr<PrefetchStreamingURLLoader> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  void BindAndStart(
+      std::unique_ptr<PrefetchStreamingURLLoader> self,
+      const network::ResourceRequest& request,
+      mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client);
+
+  // Sends all stored events in |event_queue_| to |serving_url_loader_client_|.
+  void RunEventQueue();
+
+  // Sends the |completion_status_| to |serving_url_loader_client_|.
+  void ForwardCompletionStatus();
+
+  void DisconnectPrefetchURLLoaderMojo();
+  void OnServingURLLoaderMojoDisconnect();
+  void PostTaskToDeleteSelf();
+
+  // network::mojom::URLLoaderClient
+  void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
+  void OnReceiveResponse(
+      network::mojom::URLResponseHeadPtr head,
+      mojo::ScopedDataPipeConsumerHandle body,
+      absl::optional<mojo_base::BigBuffer> cached_metadata) override;
+  void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
+                         network::mojom::URLResponseHeadPtr head) override;
+  void OnUploadProgress(int64_t current_position,
+                        int64_t total_size,
+                        OnUploadProgressCallback callback) override;
+  void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
+  void OnComplete(
+      const network::URLLoaderCompletionStatus& completion_status) override;
+
+  // network::mojom::URLLoader
+  void FollowRedirect(
+      const std::vector<std::string>& removed_headers,
+      const net::HttpRequestHeaders& modified_headers,
+      const net::HttpRequestHeaders& modified_cors_exempt_headers,
+      const absl::optional<GURL>& new_url) override;
+  void SetPriority(net::RequestPriority priority,
+                   int32_t intra_priority_value) override;
+  void PauseReadingBodyFromNet() override;
+  void ResumeReadingBodyFromNet() override;
+
+  // Set when this manages its own lifetime.
+  std::unique_ptr<PrefetchStreamingURLLoader> self_pointer_;
+
+  // Status of the URL loader. This recorded to UMA when the URL loader is
+  // deleted.
+  PrefetchStreamingURLLoaderStatus status_{
+      PrefetchStreamingURLLoaderStatus::kWaitingOnHead};
+
+  // The timer that triggers a timeout when a request takes too long.
+  base::OneShotTimer timeout_timer_;
+
+  // Once prefetching and serving is complete, then this can be deleted.
+  bool prefetch_url_loader_disconnected_{false};
+  bool serving_url_loader_disconnected_{false};
+
+  // The URL loader used to request the prefetch.
+  mojo::Remote<network::mojom::URLLoader> prefetch_url_loader_;
+  mojo::Receiver<network::mojom::URLLoaderClient>
+      prefetch_url_loader_client_receiver_{this};
+
+  // Callbacks used to inform the caller of specific events of the prefetch
+  // request.
+  OnPrefetchResponseStartedCallback on_prefetch_response_started_callback_;
+  OnPrefetchResponseCompletedCallback on_prefetch_response_completed_callback_;
+  OnPrefetchRedirectCallback on_prefetch_redirect_callback_;
+
+  // The prefetched data and metadata.
+  network::mojom::URLResponseHeadPtr head_;
+  mojo::ScopedDataPipeConsumerHandle body_;
+  bool servable_{false};
+  absl::optional<network::URLLoaderCompletionStatus> completion_status_;
+  absl::optional<base::TimeTicks> response_complete_time_;
+
+  // The URL Loader events that occur before serving the prefetch are queued up
+  // until the prefetch is served.
+  std::vector<base::OnceClosure> event_queue_;
+
+  // The URL loader client that will serve the prefetched data.
+  mojo::Receiver<network::mojom::URLLoader> serving_url_loader_receiver_{this};
+  mojo::Remote<network::mojom::URLLoaderClient> serving_url_loader_client_;
+
+  base::WeakPtrFactory<PrefetchStreamingURLLoader> weak_ptr_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_H_
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h
new file mode 100644
index 0000000..eb574de
--- /dev/null
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.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 CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_STATUS_H_
+#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_STATUS_H_
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class PrefetchStreamingURLLoaderStatus {
+  // The streaming URL loader is in progress.
+  kWaitingOnHead = 0,
+  kHeadReceivedWaitingOnBody = 1,
+
+  // The request redirected to a different target.
+  kRedirected = 2,
+
+  // Both the head and body of the response were received successfully.
+  kSuccessfulNotServed = 3,
+  kSuccessfulServedAfterCompletion = 4,
+  kSuccessfulServedBeforeCompletion = 5,
+
+  // Failure reasons based on the head of the response.
+  kPrefetchWasDecoy = 6,
+  kFailedInvalidHead = 7,
+  kFailedInvalidHeaders = 8,
+  kFailedNon2XX = 9,
+  kFailedMIMENotSupported = 10,
+
+  // Failure reasons where the head of the response was good, but an error
+  // occurred while receiving the body of the response.
+  kFailedNetError = 11,
+  kFailedNetErrorButServed = 12,
+
+  kMaxValue = kFailedNetErrorButServed,
+};
+
+#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_STATUS_H_
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
new file mode 100644
index 0000000..bf58bd5b
--- /dev/null
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -0,0 +1,1046 @@
+// 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 "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/data_pipe_drainer.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/early_hints.mojom.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+class TestURLLoaderFactory : public network::mojom::URLLoaderFactory {
+ public:
+  TestURLLoaderFactory() = default;
+  ~TestURLLoaderFactory() override = default;
+
+  TestURLLoaderFactory(const TestURLLoaderFactory&) = delete;
+  TestURLLoaderFactory& operator=(const TestURLLoaderFactory&) = delete;
+
+  void SimulateReceiveHead(net::HttpStatusCode http_status,
+                           uint32_t expected_total_body_size) {
+    ASSERT_TRUE(streaming_client_remote_);
+
+    auto head = network::CreateURLResponseHead(http_status);
+
+    mojo::ScopedDataPipeConsumerHandle body;
+    if (expected_total_body_size > 0) {
+      EXPECT_EQ(mojo::CreateDataPipe(expected_total_body_size, producer_handle_,
+                                     body),
+                MOJO_RESULT_OK);
+    }
+
+    streaming_client_remote_->OnReceiveResponse(std::move(head),
+                                                std::move(body), absl::nullopt);
+  }
+
+  void SimulateReceiveData(const std::string& data,
+                           bool expected_successful = true) {
+    ASSERT_TRUE(producer_handle_);
+    uint32_t bytes_written = data.size();
+    auto write_result = producer_handle_->WriteData(
+        data.data(), &bytes_written, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+    if (expected_successful) {
+      EXPECT_EQ(write_result, MOJO_RESULT_OK);
+    } else {
+      EXPECT_NE(write_result, MOJO_RESULT_OK);
+    }
+  }
+
+  void SimulateResponseComplete(net::Error net_error) {
+    producer_handle_.reset();
+
+    network::URLLoaderCompletionStatus completion_status(net_error);
+    streaming_client_remote_->OnComplete(completion_status);
+  }
+
+  void SimulateRedirect(const GURL& redirect_url,
+                        net::HttpStatusCode http_status) {
+    ASSERT_TRUE(streaming_client_remote_);
+
+    net::RedirectInfo redirect_info;
+    redirect_info.new_url = redirect_url;
+
+    auto head = network::CreateURLResponseHead(http_status);
+
+    streaming_client_remote_->OnReceiveRedirect(redirect_info, std::move(head));
+  }
+
+  void SimulateTransferSizeUpdated(int32_t transfer_size_diff) {
+    ASSERT_TRUE(streaming_client_remote_);
+    streaming_client_remote_->OnTransferSizeUpdated(transfer_size_diff);
+  }
+
+  void DisconnectMojoPipes() {
+    EXPECT_TRUE(streaming_client_remote_);
+    streaming_client_remote_.reset();
+  }
+
+ private:
+  // network::mojom::URLLoaderFactory
+  void CreateLoaderAndStart(
+      mojo::PendingReceiver<network::mojom::URLLoader> receiver,
+      int32_t request_id,
+      uint32_t options,
+      const network::ResourceRequest& url_request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+      override {
+    ASSERT_FALSE(streaming_client_remote_);
+    EXPECT_EQ(request_id, 0);
+    EXPECT_EQ(options,
+              network::mojom::kURLLoadOptionSendSSLInfoWithResponse |
+                  network::mojom::kURLLoadOptionSniffMimeType |
+                  network::mojom::kURLLoadOptionSendSSLInfoForCertificateError);
+
+    streaming_client_remote_.Bind(std::move(client));
+  }
+
+  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
+      override {
+    receivers_.Add(this, std::move(receiver));
+  }
+
+  mojo::Remote<network::mojom::URLLoaderClient> streaming_client_remote_;
+  mojo::ReceiverSet<network::mojom::URLLoaderFactory> receivers_;
+  mojo::ScopedDataPipeProducerHandle producer_handle_;
+};
+
+class TestURLLoaderClient : public network::mojom::URLLoaderClient,
+                            public mojo::DataPipeDrainer::Client {
+ public:
+  TestURLLoaderClient() = default;
+  ~TestURLLoaderClient() override = default;
+
+  TestURLLoaderClient(const TestURLLoaderClient&) = delete;
+  TestURLLoaderClient& operator=(const TestURLLoaderClient&) = delete;
+
+  mojo::PendingReceiver<network::mojom::URLLoader>
+  BindURLloaderAndGetReceiver() {
+    return remote_.BindNewPipeAndPassReceiver();
+  }
+
+  mojo::PendingRemote<network::mojom::URLLoaderClient>
+  BindURLLoaderClientAndGetRemote() {
+    return receiver_.BindNewPipeAndPassRemote();
+  }
+
+  void DisconnectMojoPipes() {
+    remote_.reset();
+    receiver_.reset();
+  }
+
+  std::string body_content() { return body_content_; }
+  uint32_t total_bytes_read() { return total_bytes_read_; }
+  bool body_finished() { return body_finished_; }
+  int32_t total_transfer_size_diff() { return total_transfer_size_diff_; }
+
+  absl::optional<network::URLLoaderCompletionStatus> completion_status() {
+    return completion_status_;
+  }
+
+ private:
+  // network::mojom::URLLoaderClient
+  void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override {
+    NOTREACHED();
+  }
+
+  void OnReceiveResponse(
+      network::mojom::URLResponseHeadPtr head,
+      mojo::ScopedDataPipeConsumerHandle body,
+      absl::optional<mojo_base::BigBuffer> cached_metadata) override {
+    EXPECT_EQ(cached_metadata, absl::nullopt);
+
+    // Drains |body| into |body_content_|
+    pipe_drainer_ =
+        std::make_unique<mojo::DataPipeDrainer>(this, std::move(body));
+  }
+
+  void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
+                         network::mojom::URLResponseHeadPtr head) override {
+    NOTREACHED();
+  }
+
+  void OnUploadProgress(int64_t current_position,
+                        int64_t total_size,
+                        OnUploadProgressCallback callback) override {
+    NOTREACHED();
+  }
+
+  void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
+    total_transfer_size_diff_ += transfer_size_diff;
+  }
+
+  void OnComplete(const network::URLLoaderCompletionStatus& status) override {
+    completion_status_ = status;
+  }
+
+  // mojo::DataPipeDrainer::Client
+  void OnDataAvailable(const void* data, size_t num_bytes) override {
+    body_content_.append(
+        std::string(static_cast<const char*>(data), num_bytes));
+    total_bytes_read_ += num_bytes;
+  }
+
+  void OnDataComplete() override { body_finished_ = true; }
+
+  mojo::Remote<network::mojom::URLLoader> remote_;
+  mojo::Receiver<network::mojom::URLLoaderClient> receiver_{this};
+
+  std::unique_ptr<mojo::DataPipeDrainer> pipe_drainer_;
+
+  std::string body_content_;
+  uint32_t total_bytes_read_{0};
+  bool body_finished_{false};
+  int32_t total_transfer_size_diff_{0};
+
+  absl::optional<network::URLLoaderCompletionStatus> completion_status_;
+};
+
+class PrefetchStreamingURLLoaderTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    task_environment_ =
+        std::make_unique<base::test::SingleThreadTaskEnvironment>(
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME);
+    test_url_loader_factory_ = std::make_unique<TestURLLoaderFactory>();
+  }
+
+  base::test::SingleThreadTaskEnvironment* task_environment() {
+    return task_environment_.get();
+  }
+
+  TestURLLoaderFactory* test_url_loader_factory() {
+    return test_url_loader_factory_.get();
+  }
+
+ private:
+  std::unique_ptr<base::test::SingleThreadTaskEnvironment> task_environment_;
+  std::unique_ptr<TestURLLoaderFactory> test_url_loader_factory_;
+};
+
+TEST_F(PrefetchStreamingURLLoaderTest, SuccessfulServedAfterCompletion) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates receiving the head and body for the prefetch.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->DisconnectMojoPipes();
+
+  // Gets handler to serve prefetch from |streaming_loader|. After this
+  // |streaming_loader| is self owned, so |weak_streaming_loader| should be used
+  // after this point.
+  base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader =
+      streaming_loader->GetWeakPtr();
+  PrefetchStreamingURLLoader::RequestHandler request_handler =
+      weak_streaming_loader->ServingResponseHandler(
+          std::move(streaming_loader));
+
+  // Set up URLLoaderClient to "serve" the prefetch.
+  std::unique_ptr<TestURLLoaderClient> serving_url_loader_client =
+      std::make_unique<TestURLLoaderClient>();
+
+  network::ResourceRequest serving_request;
+  serving_request.url = kTestUrl;
+  serving_request.method = "GET";
+
+  std::move(request_handler)
+      .Run(serving_request,
+           serving_url_loader_client->BindURLloaderAndGetReceiver(),
+           serving_url_loader_client->BindURLLoaderClientAndGetRemote());
+
+  // Wait for the data to be drained from the body pipe.
+  task_environment()->RunUntilIdle();
+
+  EXPECT_TRUE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(), kBodyContent.size());
+
+  EXPECT_TRUE(serving_url_loader_client->completion_status());
+  EXPECT_EQ(serving_url_loader_client->completion_status()->error_code,
+            net::OK);
+
+  serving_url_loader_client->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  // Once the streaming URL loader serves is finished (all prefetched data
+  // received and served) and all mojo pipes are disconnected, it should delete
+  // itself.
+  EXPECT_FALSE(weak_streaming_loader);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, SuccessfulServedBeforeCompletion) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent1 = "example";
+  const std::string kBodyContent2 = " body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates receiving the head for the prefetch, receiving part of the body
+  // data, start to serve the prefetch, and then getting the rest of the body
+  // data. This should result in the data being streamed directly to the serving
+  // URL loader.
+  test_url_loader_factory()->SimulateReceiveHead(
+      net::HTTP_OK, kBodyContent1.size() + kBodyContent2.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent1);
+
+  // Gets handler to serve prefetch from |streaming_loader|. After this
+  // |streaming_loader| is self owned, so |weak_streaming_loader| should be used
+  // after this point.
+  base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader =
+      streaming_loader->GetWeakPtr();
+  PrefetchStreamingURLLoader::RequestHandler request_handler =
+      weak_streaming_loader->ServingResponseHandler(
+          std::move(streaming_loader));
+
+  // Set up URLLoaderClient to "serve" the prefetch.
+  std::unique_ptr<TestURLLoaderClient> serving_url_loader_client =
+      std::make_unique<TestURLLoaderClient>();
+
+  network::ResourceRequest serving_request;
+  serving_request.url = kTestUrl;
+  serving_request.method = "GET";
+
+  std::move(request_handler)
+      .Run(serving_request,
+           serving_url_loader_client->BindURLloaderAndGetReceiver(),
+           serving_url_loader_client->BindURLLoaderClientAndGetRemote());
+  task_environment()->RunUntilIdle();
+
+  // The serving URL loader should immediately get the data that has been
+  // received so far.
+  EXPECT_FALSE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent1);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(),
+            kBodyContent1.size());
+
+  EXPECT_FALSE(serving_url_loader_client->completion_status());
+
+  // The rest of the data is received. This data should be directly streamed to
+  // the serving URL loader.
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent2);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  test_url_loader_factory()->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  EXPECT_TRUE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(),
+            kBodyContent1 + kBodyContent2);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(),
+            kBodyContent1.size() + kBodyContent2.size());
+
+  EXPECT_TRUE(serving_url_loader_client->completion_status());
+  EXPECT_EQ(serving_url_loader_client->completion_status()->error_code,
+            net::OK);
+
+  serving_url_loader_client->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  // Once the streaming URL loader serves is finished (all prefetched data
+  // received and served) and all mojo pipes are disconnected, it should delete
+  // itself.
+  EXPECT_FALSE(weak_streaming_loader);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, SuccessfulNotServed) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates a successful prefetch that is not used.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, FailedInvalidHead) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                // This will cause the prefetch to be marked as not servable.
+                return PrefetchStreamingURLLoaderStatus::kFailedInvalidHead;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](const network::URLLoaderCompletionStatus& completion_status) {
+                NOTREACHED();
+              }),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates a prefetch with a non-2XX response. This should be marked as not
+  // servable.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_NOT_FOUND, 0);
+  on_response_received_loop.Run();
+
+  EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kFailedInvalidHead, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, FailedNetError) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates a prefetch with a non-OK net error.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::ERR_FAILED);
+  on_response_complete_loop.Run();
+
+  EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kFailedNetError, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, FailedNetErrorButServed) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates receiving the head for the prefetch, receiving part of the body
+  // data, start to serve the prefetch, and then getting a net error. The error
+  // should be passed to the serving URL loader.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+
+  // Gets handler to serve prefetch from |streaming_loader|. After this
+  // |streaming_loader| is self owned, so |weak_streaming_loader| should be used
+  // after this point.
+  base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader =
+      streaming_loader->GetWeakPtr();
+  PrefetchStreamingURLLoader::RequestHandler request_handler =
+      weak_streaming_loader->ServingResponseHandler(
+          std::move(streaming_loader));
+
+  // Set up URLLoaderClient to "serve" the prefetch.
+  std::unique_ptr<TestURLLoaderClient> serving_url_loader_client =
+      std::make_unique<TestURLLoaderClient>();
+
+  network::ResourceRequest serving_request;
+  serving_request.url = kTestUrl;
+  serving_request.method = "GET";
+
+  std::move(request_handler)
+      .Run(serving_request,
+           serving_url_loader_client->BindURLloaderAndGetReceiver(),
+           serving_url_loader_client->BindURLLoaderClientAndGetRemote());
+  task_environment()->RunUntilIdle();
+
+  // The serving URL loader should immediately get the data that has been
+  // received so far.
+  EXPECT_FALSE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(), kBodyContent.size());
+
+  EXPECT_FALSE(serving_url_loader_client->completion_status());
+
+  // Once the net error is received, the serving URL loader should be notified.
+  test_url_loader_factory()->SimulateResponseComplete(net::ERR_FAILED);
+  on_response_complete_loop.Run();
+
+  test_url_loader_factory()->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  EXPECT_TRUE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(), kBodyContent.size());
+
+  EXPECT_TRUE(serving_url_loader_client->completion_status());
+  EXPECT_EQ(serving_url_loader_client->completion_status()->error_code,
+            net::ERR_FAILED);
+
+  serving_url_loader_client->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  // Once the streaming URL loader serves is finished (all prefetched data
+  // received and served) and all mojo pipes are disconnected, it should delete
+  // itself.
+  EXPECT_FALSE(weak_streaming_loader);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, Redirected) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_redirect_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce([](network::mojom::URLResponseHead* head) {
+            NOTREACHED();
+            return PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody;
+          }),
+          base::BindOnce(
+              [](const network::URLLoaderCompletionStatus& completion_status) {
+                NOTREACHED();
+              }),
+          base::BindRepeating(
+              [](base::RunLoop* on_redirect_loop,
+                 const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) {
+                on_redirect_loop->Quit();
+              },
+              &on_redirect_loop));
+
+  // Simulate a redirect. These are currently disabled, and cause the request to
+  // stop.
+  test_url_loader_factory()->SimulateRedirect(GURL("https://redirect.com"),
+                                              net::HTTP_PERMANENT_REDIRECT);
+  on_redirect_loop.Run();
+
+  EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kRedirected, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, Decoy) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates a successful prefetch that is not used. However, since the
+  // prefetch is marked as a decoy, it cannot be served.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  // On a decoy, the body pipe is closed since the data should not be stored.
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent,
+                                                 /*expected_successful=*/false);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, Timeout) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::Seconds(1),
+          base::BindOnce([](network::mojom::URLResponseHead* head) {
+            NOTREACHED();
+            return PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody;
+          }),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                EXPECT_EQ(completion_status.error_code, net::ERR_TIMED_OUT);
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  task_environment()->FastForwardBy(base::Seconds(1));
+  on_response_complete_loop.Run();
+
+  EXPECT_FALSE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kFailedNetError, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, StaleResponse) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates a successful prefetch that is not used.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  task_environment()->FastForwardBy(base::Seconds(2));
+
+  // The staleness of the streaming URL loader response is measured from when
+  // the response is complete, not when the head is received.
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta()));
+
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  task_environment()->FastForwardBy(base::Seconds(4));
+
+  // The response should not be servable if its been too long since it has
+  // completed.
+  EXPECT_FALSE(streaming_loader->Servable(base::Seconds(3)));
+  EXPECT_FALSE(streaming_loader->Servable(base::Seconds(4)));
+  EXPECT_TRUE(streaming_loader->Servable(base::Seconds(5)));
+
+  streaming_loader.reset();
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed, 1);
+}
+
+TEST_F(PrefetchStreamingURLLoaderTest, TransferSizeUpdated) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::TimeDelta(),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head,
+                 std::vector<std::string>* removed_headers) { NOTREACHED(); }));
+
+  // Simulates receiving the head for the prefetch, receiving part of the body
+  // data, start to serve the prefetch, and then getting the rest of the body
+  // data. This should result in the data being streamed directly to the serving
+  // URL loader.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  // Simulates updating the transfer size. This event will be queued in the
+  // streaming URL loader and sent to the serving URL loader once bound.
+  test_url_loader_factory()->SimulateTransferSizeUpdated(100);
+
+  // Gets handler to serve prefetch from |streaming_loader|. After this
+  // |streaming_loader| is self owned, so |weak_streaming_loader| should be used
+  // after this point.
+  base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader =
+      streaming_loader->GetWeakPtr();
+  PrefetchStreamingURLLoader::RequestHandler request_handler =
+      weak_streaming_loader->ServingResponseHandler(
+          std::move(streaming_loader));
+
+  // Set up URLLoaderClient to "serve" the prefetch.
+  std::unique_ptr<TestURLLoaderClient> serving_url_loader_client =
+      std::make_unique<TestURLLoaderClient>();
+
+  network::ResourceRequest serving_request;
+  serving_request.url = kTestUrl;
+  serving_request.method = "GET";
+
+  std::move(request_handler)
+      .Run(serving_request,
+           serving_url_loader_client->BindURLloaderAndGetReceiver(),
+           serving_url_loader_client->BindURLLoaderClientAndGetRemote());
+  task_environment()->RunUntilIdle();
+
+  // The serving URL loader should immediately get the queued events.
+  EXPECT_EQ(serving_url_loader_client->total_transfer_size_diff(), 100);
+
+  EXPECT_FALSE(serving_url_loader_client->completion_status());
+
+  // Simulates another transfer size update. Since the serving URL loader is
+  // bound, it should be immediately sent to it.
+  test_url_loader_factory()->SimulateTransferSizeUpdated(200);
+  task_environment()->RunUntilIdle();
+  EXPECT_EQ(serving_url_loader_client->total_transfer_size_diff(), 300);
+
+  // The rest of the data is received. This data should be directly streamed to
+  // the serving URL loader.
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  test_url_loader_factory()->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  EXPECT_TRUE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(), kBodyContent.size());
+
+  EXPECT_TRUE(serving_url_loader_client->completion_status());
+  EXPECT_EQ(serving_url_loader_client->completion_status()->error_code,
+            net::OK);
+
+  serving_url_loader_client->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  // Once the streaming URL loader serves is finished (all prefetched data
+  // received and served) and all mojo pipes are disconnected, it should delete
+  // itself.
+  EXPECT_FALSE(weak_streaming_loader);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion, 1);
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
index 9c098c2c..26a87c9 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -19,6 +19,7 @@
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
 #include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -106,8 +107,7 @@
   }
 #endif
   if (!prefetch_container ||
-      !prefetch_container->HasValidPrefetchedResponse(
-          PrefetchCacheableDuration()) ||
+      !prefetch_container->IsPrefetchServable(PrefetchCacheableDuration()) ||
       prefetch_container->HaveDefaultContextCookiesChanged()) {
     DoNotInterceptNavigation();
     return;
@@ -235,15 +235,31 @@
 
   // Set up URL loader that will serve the prefetched data, and URL loader
   // factory that will "create" this loader.
-  std::unique_ptr<PrefetchFromStringURLLoader> url_loader =
-      std::make_unique<PrefetchFromStringURLLoader>(
-          prefetch_container->ReleasePrefetchedResponse(),
-          prefetch_container->GetPrefetchResponseSizes(),
-          tenative_resource_request);
   scoped_refptr<network::SingleRequestURLLoaderFactory>
-      single_request_url_loader_factory =
-          base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
-              url_loader->ServingResponseHandler());
+      single_request_url_loader_factory;
+  std::unique_ptr<PrefetchFromStringURLLoader> url_loader;
+  if (prefetch_container->GetStreamingLoader()) {
+    // The streaming URL loader manages its own lifetime after this point. It
+    // will delete itself once the prefetch response is completed and the
+    // prefetched response is served.
+    std::unique_ptr<PrefetchStreamingURLLoader> prefetch_streaming_url_loader =
+        prefetch_container->ReleaseStreamingLoader();
+    auto* raw_prefetch_streaming_url_loader =
+        prefetch_streaming_url_loader.get();
+
+    single_request_url_loader_factory =
+        base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
+            raw_prefetch_streaming_url_loader->ServingResponseHandler(
+                std::move(prefetch_streaming_url_loader)));
+  } else {
+    url_loader = std::make_unique<PrefetchFromStringURLLoader>(
+        prefetch_container->ReleasePrefetchedResponse(),
+        prefetch_container->GetPrefetchResponseSizes(),
+        tenative_resource_request);
+    single_request_url_loader_factory =
+        base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
+            url_loader->ServingResponseHandler());
+  }
 
   // Create URL loader factory pipe that can be possibly proxied by Extensions.
   mojo::PendingReceiver<network::mojom::URLLoaderFactory> pending_receiver;
@@ -280,7 +296,9 @@
               std::move(pending_remote))));
 
   // url_loader manages its own lifetime once bound to the mojo pipes.
-  url_loader.release();
+  if (url_loader) {
+    url_loader.release();
+  }
 }
 
 void PrefetchURLLoaderInterceptor::DoNotInterceptNavigation() {
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index b478b19..6992231 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -141,6 +141,7 @@
       cc::PaintHoldingReason,
       absl::optional<cc::PaintHoldingCommitTrigger>) override {}
   void OnPauseRenderingChanged(bool) override {}
+  void OnCommitRequested() override {}
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override;
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.css b/content/browser/resources/attribution_reporting/attribution_internals.css
index 927334a..620f4b6 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.css
+++ b/content/browser/resources/attribution_reporting/attribution_internals.css
@@ -16,7 +16,6 @@
   flex-direction: column;
   font-family: Roboto, sans-serif;
   font-size: 13px;
-  height: 100%;
   margin: 15px;
   overflow: auto;
 }
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index bd3eaea0..5cc74f4 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -27,9 +27,9 @@
   kUnhandledRequest,
   kIdpNotPotentiallyTrustworthy,
   kNotSelectAccount,
-  kManifestHttpNotFound,
-  kManifestNoResponse,
-  kManifestInvalidResponse,
+  kConfigHttpNotFound,
+  kConfigNoResponse,
+  kConfigInvalidResponse,
   kClientMetadataHttpNotFound,     // obsolete
   kClientMetadataNoResponse,       // obsolete
   kClientMetadataInvalidResponse,  // obsolete
@@ -44,11 +44,11 @@
   kThirdPartyCookiesBlocked,
   kDisabledInSettings,
   kDisabledInFlags,
-  kManifestListHttpNotFound,
-  kManifestListNoResponse,
-  kManifestListInvalidResponse,
-  kManifestNotInManifestList,
-  kManifestListTooBig,
+  kWellKnownHttpNotFound,
+  kWellKnownNoResponse,
+  kWellKnownInvalidResponse,
+  kConfigNotInWellKnown,
+  kWellKnownTooBig,
   kDisabledEmbargo,
   kUserInterfaceTimedOut,  // obsolete
   kRpPageNotVisible,
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 8706c41..5f068a01 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -540,18 +540,18 @@
 
   int icon_ideal_size = request_dialog_controller_->GetBrandIconIdealSize();
   int icon_minimum_size = request_dialog_controller_->GetBrandIconMinimumSize();
-  std::unique_ptr<FederatedManifestRequester> manifest_requester =
-      std::make_unique<FederatedManifestRequester>(network_manager_.get());
+  std::unique_ptr<FederatedProviderFetcher> provider_fetcher =
+      std::make_unique<FederatedProviderFetcher>(network_manager_.get());
 
-  // FederatedManifestRequester is passed as a parameter of
-  // OnAllManifestsFetched() so that FederatedManifestRequester is destroyed
-  // when FederatedAuthRequestImpl is destroyed.
-  FederatedManifestRequester* manifest_requester_ptr = manifest_requester.get();
-  manifest_requester_ptr->Start(
+  // FederatedProviderFetcher is passed as a parameter of
+  // OnAllConfigAndWellKnownFetched() so that FederatedProviderFetcher is
+  // destroyed when FederatedAuthRequestImpl is destroyed.
+  FederatedProviderFetcher* provider_fetcher_ptr = provider_fetcher.get();
+  provider_fetcher_ptr->Start(
       idp_order_, icon_ideal_size, icon_minimum_size,
-      base::BindOnce(&FederatedAuthRequestImpl::OnAllManifestsFetched,
+      base::BindOnce(&FederatedAuthRequestImpl::OnAllConfigAndWellKnownFetched,
                      weak_ptr_factory_.GetWeakPtr(),
-                     std::move(manifest_requester), std::move(get_infos)));
+                     std::move(provider_fetcher), std::move(get_infos)));
 }
 
 void FederatedAuthRequestImpl::CancelTokenRequest() {
@@ -647,11 +647,11 @@
   return has_pending_request;
 }
 
-void FederatedAuthRequestImpl::OnAllManifestsFetched(
-    std::unique_ptr<FederatedManifestRequester> manifest_requester,
+void FederatedAuthRequestImpl::OnAllConfigAndWellKnownFetched(
+    std::unique_ptr<FederatedProviderFetcher> provider_fetcher,
     base::flat_map<GURL, IdentityProviderGetInfo> get_infos,
-    std::vector<FederatedManifestRequester::FetchResult> fetch_results) {
-  for (const FederatedManifestRequester::FetchResult& fetch_result :
+    std::vector<FederatedProviderFetcher::FetchResult> fetch_results) {
+  for (const FederatedProviderFetcher::FetchResult& fetch_result :
        fetch_results) {
     const GURL& identity_provider_config_url =
         fetch_result.identity_provider_config_url;
@@ -670,7 +670,7 @@
             get_info_it->second.prefer_auto_signin);
 
     if (fetch_result.error) {
-      const FederatedManifestRequester::FetchError& fetch_error =
+      const FederatedProviderFetcher::FetchError& fetch_error =
           *fetch_result.error;
       if (fetch_error.additional_console_error_message) {
         render_frame_host().AddMessageToConsole(
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 9fb29f8..5c667947 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -14,7 +14,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "content/browser/webid/fedcm_metrics.h"
-#include "content/browser/webid/federated_manifest_requester.h"
+#include "content/browser/webid/federated_provider_fetcher.h"
 #include "content/browser/webid/idp_network_request_manager.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/document_service.h"
@@ -110,10 +110,10 @@
 
   bool HasPendingRequest() const;
 
-  void OnAllManifestsFetched(
-      std::unique_ptr<FederatedManifestRequester> manifest_requester,
+  void OnAllConfigAndWellKnownFetched(
+      std::unique_ptr<FederatedProviderFetcher> provider_fetcher,
       base::flat_map<GURL, IdentityProviderGetInfo> get_infos,
-      std::vector<FederatedManifestRequester::FetchResult> fetch_results);
+      std::vector<FederatedProviderFetcher::FetchResult> fetch_results);
   void OnClientMetadataResponseReceived(
       std::unique_ptr<IdentityProviderInfo> idp_info,
       const IdpNetworkRequestManager::AccountList& accounts,
@@ -229,7 +229,7 @@
   // RequestToken() method, so all metrics must be recorded after that.
   std::unique_ptr<FedCmMetrics> fedcm_metrics_;
 
-  // Populated in OnAllManifestsFetched().
+  // Populated in OnAllConfigAndWellKnownFetched().
   base::flat_map<GURL, GURL> metrics_endpoints_;
 
   // Populated by MaybeShowAccountsDialog().
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index a24e283d..4726a656 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -134,36 +134,35 @@
   bool prefer_auto_sign_in;
 };
 
-// Bitshift to get from MANIFEST->MANIFEST_MULTI,
+// Bitshift to get from CONFIG->CONFIG_MULTI,
 // CLIENT_METADATA->CLIENT_METADATA_MULTI etc.
 const int kFetchedEndpointMultiBitshift = 5;
 
 enum FetchedEndpoint {
-  MANIFEST = 1,
+  CONFIG = 1,
   CLIENT_METADATA = 1 << 1,
   ACCOUNTS = 1 << 2,
   TOKEN = 1 << 3,
-  MANIFEST_LIST = 1 << 4,
+  WELL_KNOWN = 1 << 4,
 
-  MANIFEST_MULTI = MANIFEST | (MANIFEST << kFetchedEndpointMultiBitshift),
+  CONFIG_MULTI = CONFIG | (CONFIG << kFetchedEndpointMultiBitshift),
   CLIENT_METADATA_MULTI =
       CLIENT_METADATA | (CLIENT_METADATA << kFetchedEndpointMultiBitshift),
   ACCOUNTS_MULTI = ACCOUNTS | (ACCOUNTS << kFetchedEndpointMultiBitshift),
-  MANIFEST_LIST_MULTI =
-      MANIFEST_LIST | (MANIFEST_LIST << kFetchedEndpointMultiBitshift),
+  WELL_KNOWN_MULTI = WELL_KNOWN | (WELL_KNOWN << kFetchedEndpointMultiBitshift),
 };
 
 // All endpoints which are fetched in a successful
 // FederatedAuthRequestImpl::RequestToken() request.
 int FETCH_ENDPOINT_ALL_REQUEST_TOKEN =
-    FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
+    FetchedEndpoint::CONFIG | FetchedEndpoint::CLIENT_METADATA |
     FetchedEndpoint::ACCOUNTS | FetchedEndpoint::TOKEN |
-    FetchedEndpoint::MANIFEST_LIST;
+    FetchedEndpoint::WELL_KNOWN;
 
 int FETCH_ENDPOINT_ALL_REQUEST_TOKEN_MULTI =
-    FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::CLIENT_METADATA_MULTI |
+    FetchedEndpoint::CONFIG_MULTI | FetchedEndpoint::CLIENT_METADATA_MULTI |
     FetchedEndpoint::ACCOUNTS_MULTI | FetchedEndpoint::TOKEN |
-    FetchedEndpoint::MANIFEST_LIST_MULTI;
+    FetchedEndpoint::WELL_KNOWN_MULTI;
 
 // Expected return values from a call to RequestToken.
 struct RequestExpectations {
@@ -366,9 +365,9 @@
     delayed_callbacks_.clear();
   }
 
-  void FetchManifestList(const GURL& provider,
-                         FetchManifestListCallback callback) override {
-    add_fetched_endpoint(FetchedEndpoint::MANIFEST_LIST);
+  void FetchWellKnown(const GURL& provider,
+                      FetchWellKnownCallback callback) override {
+    add_fetched_endpoint(FetchedEndpoint::WELL_KNOWN);
 
     const char* provider_key = ConvertProviderToChar(provider);
     std::set<GURL> url_set(
@@ -379,11 +378,11 @@
         FROM_HERE, base::BindOnce(std::move(callback), success, url_set));
   }
 
-  void FetchManifest(const GURL& provider,
-                     int idp_brand_icon_ideal_size,
-                     int idp_brand_icon_minimum_size,
-                     FetchManifestCallback callback) override {
-    add_fetched_endpoint(FetchedEndpoint::MANIFEST);
+  void FetchConfig(const GURL& provider,
+                   int idp_brand_icon_ideal_size,
+                   int idp_brand_icon_minimum_size,
+                   FetchConfigCallback callback) override {
+    add_fetched_endpoint(FetchedEndpoint::CONFIG);
 
     const char* provider_key = ConvertProviderToChar(provider);
     IdpNetworkRequestManager::Endpoints endpoints;
@@ -475,7 +474,7 @@
   void add_fetched_endpoint(int fetched_endpoint) {
     if ((fetched_endpoints_ & fetched_endpoint) != 0) {
       // Endpoint has already been fetched. Mark endpoint as fetched multiple
-      // times (Example: MANIFEST_MULTI).
+      // times (Example: CONFIG_MULTI).
       fetched_endpoint <<= kFetchedEndpointMultiBitshift;
     }
     fetched_endpoints_ |= fetched_endpoint;
@@ -1014,7 +1013,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorConfigNotInWellKnown},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::MANIFEST};
+      FetchedEndpoint::WELL_KNOWN | FetchedEndpoint::CONFIG};
 
   const char* idp_config_url =
       kDefaultRequestParameters.identity_providers[0].provider;
@@ -1041,7 +1040,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorConfigNotInWellKnown},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::MANIFEST};
+      FetchedEndpoint::WELL_KNOWN | FetchedEndpoint::CONFIG};
   RunAuthTest(parameters, expectations, config);
 }
 
@@ -1053,7 +1052,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 
   std::vector<std::string> messages =
@@ -1075,7 +1074,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 
   std::vector<std::string> messages =
@@ -1111,7 +1110,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -1143,8 +1142,8 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
-          FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::ACCOUNTS |
+          FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -1157,8 +1156,8 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
-          FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::ACCOUNTS |
+          FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -1207,7 +1206,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
   std::vector<std::string> messages =
       RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
@@ -2100,8 +2099,8 @@
       RequestTokenStatus::kError,
       /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::ACCOUNTS};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::CLIENT_METADATA |
+          FetchedEndpoint::WELL_KNOWN | FetchedEndpoint::ACCOUNTS};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2126,8 +2125,8 @@
       /*return_status=*/absl::nullopt,
       /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::ACCOUNTS};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::CLIENT_METADATA |
+          FetchedEndpoint::WELL_KNOWN | FetchedEndpoint::ACCOUNTS};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2192,8 +2191,8 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
-          FetchedEndpoint::MANIFEST_LIST};
+      FetchedEndpoint::CONFIG | FetchedEndpoint::ACCOUNTS |
+          FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2224,9 +2223,9 @@
   RequestExpectations expectations = {RequestTokenStatus::kError,
                                       {FederatedAuthRequestResult::kError},
                                       /*selected_idp_config_url=*/absl::nullopt,
-                                      FetchedEndpoint::MANIFEST |
+                                      FetchedEndpoint::CONFIG |
                                           FetchedEndpoint::ACCOUNTS |
-                                          FetchedEndpoint::MANIFEST_LIST};
+                                          FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2315,8 +2314,8 @@
                                       {},
                                       absl::nullopt,
                                       FetchedEndpoint::ACCOUNTS |
-                                          FetchedEndpoint::MANIFEST |
-                                          FetchedEndpoint::MANIFEST_LIST};
+                                          FetchedEndpoint::CONFIG |
+                                          FetchedEndpoint::WELL_KNOWN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2344,7 +2343,7 @@
 
 // Test fetching information for the 1st IdP failing, and succeeding for the
 // second.
-TEST_F(FederatedAuthRequestImplTest, FirstIdpManifestListInvalid) {
+TEST_F(FederatedAuthRequestImplTest, FirstIdpWellKnownInvalid) {
   base::test::ScopedFeatureList list;
   list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
 
@@ -2358,7 +2357,7 @@
       RequestTokenStatus::kSuccess,
       {FederatedAuthRequestResult::kErrorConfigNotInWellKnown},
       /*selected_idp_config_url=*/kProviderTwoUrlFull,
-      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI |
+      FetchedEndpoint::CONFIG_MULTI | FetchedEndpoint::WELL_KNOWN_MULTI |
           FetchedEndpoint::CLIENT_METADATA | FetchedEndpoint::ACCOUNTS |
           FetchedEndpoint::TOKEN};
 
@@ -2367,12 +2366,12 @@
 
 // Test fetching information for the 1st IdP succeeding, and failing for the
 // second.
-TEST_F(FederatedAuthRequestImplTest, SecondIdpManifestListInvalid) {
+TEST_F(FederatedAuthRequestImplTest, SecondIdpWellKnownInvalid) {
   base::test::ScopedFeatureList list;
   list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
 
   // Intentionally fail the 2nd provider's request by having an invalid
-  // manifest list.
+  // well-known file.
   MockConfiguration configuration = kConfigurationMultiIdpValid;
   configuration.idp_info[kProviderTwoUrlFull].well_known.provider_urls =
       std::set<std::string>{"https://not-in-list.example"};
@@ -2381,7 +2380,7 @@
       RequestTokenStatus::kSuccess,
       {FederatedAuthRequestResult::kErrorConfigNotInWellKnown},
       /*selected_idp_config_url=*/kProviderUrlFull,
-      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI |
+      FetchedEndpoint::CONFIG_MULTI | FetchedEndpoint::WELL_KNOWN_MULTI |
           FetchedEndpoint::CLIENT_METADATA | FetchedEndpoint::ACCOUNTS |
           FetchedEndpoint::TOKEN};
 
@@ -2389,12 +2388,12 @@
 }
 
 // Test fetching information for all of the IdPs failing.
-TEST_F(FederatedAuthRequestImplTest, AllManifestListsInvalid) {
+TEST_F(FederatedAuthRequestImplTest, AllWellKnownsInvalid) {
   base::test::ScopedFeatureList list;
   list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
 
   // Intentionally fail the requests for both IdPs by returning an invalid
-  // manifest list.
+  // well-known file.
   MockConfiguration configuration = kConfigurationMultiIdpValid;
   configuration.idp_info[kProviderUrlFull].well_known.provider_urls =
       std::set<std::string>{"https://not-in-list.example"};
@@ -2405,7 +2404,7 @@
       RequestTokenStatus::kError,
       {FederatedAuthRequestResult::kErrorConfigNotInWellKnown},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI};
+      FetchedEndpoint::CONFIG_MULTI | FetchedEndpoint::WELL_KNOWN_MULTI};
 
   RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
 }
diff --git a/content/browser/webid/federated_manifest_requester.h b/content/browser/webid/federated_manifest_requester.h
deleted file mode 100644
index d7cd34fd3..0000000
--- a/content/browser/webid/federated_manifest_requester.h
+++ /dev/null
@@ -1,103 +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.
-
-#ifndef CONTENT_BROWSER_WEBID_FEDERATED_MANIFEST_REQUESTER_H_
-#define CONTENT_BROWSER_WEBID_FEDERATED_MANIFEST_REQUESTER_H_
-
-#include <memory>
-#include <set>
-#include <vector>
-
-#include "base/callback_forward.h"
-#include "base/containers/flat_set.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "content/browser/webid/fedcm_metrics.h"
-#include "content/browser/webid/idp_network_request_manager.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
-
-namespace content {
-
-// Fetches the manifests and manifest lists for a list of identity providers.
-// Validates returned information and calls callback when done.
-class FederatedManifestRequester {
- public:
-  struct FetchError {
-    FetchError(const FetchError& info);
-    FetchError(blink::mojom::FederatedAuthRequestResult result,
-               FedCmRequestIdTokenStatus token_status,
-               absl::optional<std::string> additional_console_error_message);
-    ~FetchError();
-
-    blink::mojom::FederatedAuthRequestResult result;
-    FedCmRequestIdTokenStatus token_status;
-    absl::optional<std::string> additional_console_error_message;
-  };
-
-  struct FetchResult {
-    FetchResult();
-    FetchResult(const FetchResult&);
-    ~FetchResult();
-    GURL identity_provider_config_url;
-    IdpNetworkRequestManager::Endpoints endpoints;
-    absl::optional<IdentityProviderMetadata> metadata;
-    absl::optional<FetchError> error;
-  };
-
-  using RequesterCallback = base::OnceCallback<void(std::vector<FetchResult>)>;
-
-  explicit FederatedManifestRequester(
-      IdpNetworkRequestManager* network_manager);
-  ~FederatedManifestRequester();
-
-  FederatedManifestRequester(const FederatedManifestRequester&) = delete;
-  FederatedManifestRequester& operator=(const FederatedManifestRequester&) =
-      delete;
-
-  // Starts fetch of manifests and manifest lists. Start() should be called at
-  // most once per FederatedManifestRequester instance.
-  void Start(const std::vector<GURL>& identity_provider_config_urls,
-             int icon_ideal_size,
-             int icon_minimum_size,
-             RequesterCallback callback);
-
- private:
-  void OnManifestListFetched(FetchResult& fetch_result,
-                             IdpNetworkRequestManager::FetchStatus status,
-                             const std::set<GURL>& urls);
-  void OnManifestFetched(FetchResult& fetch_result,
-                         IdpNetworkRequestManager::FetchStatus status,
-                         IdpNetworkRequestManager::Endpoints,
-                         IdentityProviderMetadata idp_metadata);
-
-  // Called when fetching either the manifest endpoint or the manifest list
-  // endpoint fails.
-  void OnError(FetchResult& fetch_result,
-               blink::mojom::FederatedAuthRequestResult result,
-               content::FedCmRequestIdTokenStatus token_status,
-               absl::optional<std::string> additional_console_error_message);
-
-  void RunCallbackIfDone();
-
-  RequesterCallback callback_;
-
-  // Config endpoints which has not yet been fetched.
-  base::flat_set<GURL> pending_manifest_fetches_;
-
-  // Config endpoints for which associated manifest list has not yet been
-  // fetched.
-  base::flat_set<GURL> pending_manifest_list_fetches_;
-
-  std::vector<FetchResult> fetch_results_;
-
-  // Fetches the manifest and manifest list.
-  base::raw_ptr<IdpNetworkRequestManager, DanglingUntriaged> network_manager_;
-
-  base::WeakPtrFactory<FederatedManifestRequester> weak_ptr_factory_{this};
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_WEBID_FEDERATED_MANIFEST_REQUESTER_H_
diff --git a/content/browser/webid/federated_manifest_requester.cc b/content/browser/webid/federated_provider_fetcher.cc
similarity index 76%
rename from content/browser/webid/federated_manifest_requester.cc
rename to content/browser/webid/federated_provider_fetcher.cc
index 441637f..fbcb432ca 100644
--- a/content/browser/webid/federated_manifest_requester.cc
+++ b/content/browser/webid/federated_provider_fetcher.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/webid/federated_manifest_requester.h"
+#include "content/browser/webid/federated_provider_fetcher.h"
 
 #include "content/browser/webid/webid_utils.h"
 
@@ -10,7 +10,7 @@
 
 namespace {
 
-// Maximum number of provider URLs in the manifest list.
+// Maximum number of provider URLs in the well-known file.
 // TODO(cbiesinger): Determine what the right number is.
 static constexpr size_t kMaxProvidersInWellKnownFile = 1ul;
 
@@ -19,9 +19,9 @@
 using blink::mojom::FederatedAuthRequestResult;
 using TokenStatus = FedCmRequestIdTokenStatus;
 
-FederatedManifestRequester::FetchError::FetchError(const FetchError&) = default;
+FederatedProviderFetcher::FetchError::FetchError(const FetchError&) = default;
 
-FederatedManifestRequester::FetchError::FetchError(
+FederatedProviderFetcher::FetchError::FetchError(
     blink::mojom::FederatedAuthRequestResult result,
     FedCmRequestIdTokenStatus token_status,
     absl::optional<std::string> additional_console_error_message)
@@ -30,20 +30,20 @@
       additional_console_error_message(
           std::move(additional_console_error_message)) {}
 
-FederatedManifestRequester::FetchError::~FetchError() = default;
+FederatedProviderFetcher::FetchError::~FetchError() = default;
 
-FederatedManifestRequester::FetchResult::FetchResult() = default;
-FederatedManifestRequester::FetchResult::FetchResult(const FetchResult&) =
+FederatedProviderFetcher::FetchResult::FetchResult() = default;
+FederatedProviderFetcher::FetchResult::FetchResult(const FetchResult&) =
     default;
-FederatedManifestRequester::FetchResult::~FetchResult() = default;
+FederatedProviderFetcher::FetchResult::~FetchResult() = default;
 
-FederatedManifestRequester::FederatedManifestRequester(
+FederatedProviderFetcher::FederatedProviderFetcher(
     IdpNetworkRequestManager* network_manager)
     : network_manager_(network_manager) {}
 
-FederatedManifestRequester::~FederatedManifestRequester() = default;
+FederatedProviderFetcher::~FederatedProviderFetcher() = default;
 
-void FederatedManifestRequester::Start(
+void FederatedProviderFetcher::Start(
     const std::vector<GURL>& identity_provider_config_urls,
     int icon_ideal_size,
     int icon_minimum_size,
@@ -56,31 +56,30 @@
     fetch_result.identity_provider_config_url = identity_provider_config_url;
     fetch_results_.push_back(std::move(fetch_result));
 
-    pending_manifest_list_fetches_.insert(identity_provider_config_url);
-    pending_manifest_fetches_.insert(identity_provider_config_url);
+    pending_well_known_fetches_.insert(identity_provider_config_url);
+    pending_config_fetches_.insert(identity_provider_config_url);
   }
 
   // In a separate loop to avoid invalidating references when adding elements to
   // `fetch_results_`.
   for (FetchResult& fetch_result : fetch_results_) {
-    network_manager_->FetchManifestList(
+    network_manager_->FetchWellKnown(
         fetch_result.identity_provider_config_url,
-        base::BindOnce(&FederatedManifestRequester::OnManifestListFetched,
+        base::BindOnce(&FederatedProviderFetcher::OnWellKnownFetched,
                        weak_ptr_factory_.GetWeakPtr(), std::ref(fetch_result)));
-    network_manager_->FetchManifest(
+    network_manager_->FetchConfig(
         fetch_result.identity_provider_config_url, icon_ideal_size,
         icon_minimum_size,
-        base::BindOnce(&FederatedManifestRequester::OnManifestFetched,
+        base::BindOnce(&FederatedProviderFetcher::OnConfigFetched,
                        weak_ptr_factory_.GetWeakPtr(), std::ref(fetch_result)));
   }
 }
 
-void FederatedManifestRequester::OnManifestListFetched(
+void FederatedProviderFetcher::OnWellKnownFetched(
     FetchResult& fetch_result,
     IdpNetworkRequestManager::FetchStatus status,
     const std::set<GURL>& urls) {
-  pending_manifest_list_fetches_.erase(
-      fetch_result.identity_provider_config_url);
+  pending_well_known_fetches_.erase(fetch_result.identity_provider_config_url);
 
   constexpr char kWellKnownFileStr[] = "well-known file";
 
@@ -93,14 +92,14 @@
       case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: {
         OnError(fetch_result,
                 FederatedAuthRequestResult::kErrorFetchingWellKnownHttpNotFound,
-                TokenStatus::kManifestListHttpNotFound,
+                TokenStatus::kWellKnownHttpNotFound,
                 additional_console_error_message);
         return;
       }
       case IdpNetworkRequestManager::ParseStatus::kNoResponseError: {
         OnError(fetch_result,
                 FederatedAuthRequestResult::kErrorFetchingWellKnownNoResponse,
-                TokenStatus::kManifestListNoResponse,
+                TokenStatus::kWellKnownNoResponse,
                 additional_console_error_message);
         return;
       }
@@ -108,7 +107,7 @@
         OnError(
             fetch_result,
             FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidResponse,
-            TokenStatus::kManifestListInvalidResponse,
+            TokenStatus::kWellKnownInvalidResponse,
             additional_console_error_message);
         return;
       }
@@ -120,7 +119,7 @@
 
   if (urls.size() > kMaxProvidersInWellKnownFile) {
     OnError(fetch_result, FederatedAuthRequestResult::kErrorWellKnownTooBig,
-            TokenStatus::kManifestListTooBig,
+            TokenStatus::kWellKnownTooBig,
             /*additional_console_error_message=*/absl::nullopt);
     return;
   }
@@ -134,7 +133,7 @@
   //     }],
   //   }
   // });
-  // must match the one in the manifest list:
+  // must match the one in the well-known file:
   // {
   //   "provider_urls": [
   //     "https://foo.idp.example/fedcm.json"
@@ -146,7 +145,7 @@
   if (!provider_url_is_valid) {
     OnError(fetch_result,
             FederatedAuthRequestResult::kErrorConfigNotInWellKnown,
-            TokenStatus::kManifestNotInManifestList,
+            TokenStatus::kConfigNotInWellKnown,
             /*additional_console_error_message=*/absl::nullopt);
     return;
   }
@@ -154,12 +153,12 @@
   RunCallbackIfDone();
 }
 
-void FederatedManifestRequester::OnManifestFetched(
+void FederatedProviderFetcher::OnConfigFetched(
     FetchResult& fetch_result,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::Endpoints endpoints,
     IdentityProviderMetadata idp_metadata) {
-  pending_manifest_fetches_.erase(fetch_result.identity_provider_config_url);
+  pending_config_fetches_.erase(fetch_result.identity_provider_config_url);
 
   constexpr char kConfigFileStr[] = "config file";
 
@@ -172,21 +171,21 @@
       case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: {
         OnError(fetch_result,
                 FederatedAuthRequestResult::kErrorFetchingConfigHttpNotFound,
-                TokenStatus::kManifestHttpNotFound,
+                TokenStatus::kConfigHttpNotFound,
                 additional_console_error_message);
         return;
       }
       case IdpNetworkRequestManager::ParseStatus::kNoResponseError: {
         OnError(fetch_result,
                 FederatedAuthRequestResult::kErrorFetchingConfigNoResponse,
-                TokenStatus::kManifestNoResponse,
+                TokenStatus::kConfigNoResponse,
                 additional_console_error_message);
         return;
       }
       case IdpNetworkRequestManager::ParseStatus::kInvalidResponseError: {
         OnError(fetch_result,
                 FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse,
-                TokenStatus::kManifestInvalidResponse,
+                TokenStatus::kConfigInvalidResponse,
                 additional_console_error_message);
         return;
       }
@@ -218,14 +217,14 @@
 
     OnError(fetch_result,
             FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse,
-            TokenStatus::kManifestInvalidResponse, console_message);
+            TokenStatus::kConfigInvalidResponse, console_message);
     return;
   }
 
   RunCallbackIfDone();
 }
 
-void FederatedManifestRequester::OnError(
+void FederatedProviderFetcher::OnError(
     FetchResult& fetch_result,
     blink::mojom::FederatedAuthRequestResult result,
     content::FedCmRequestIdTokenStatus token_status,
@@ -235,9 +234,8 @@
   RunCallbackIfDone();
 }
 
-void FederatedManifestRequester::RunCallbackIfDone() {
-  if (pending_manifest_fetches_.empty() &&
-      pending_manifest_list_fetches_.empty()) {
+void FederatedProviderFetcher::RunCallbackIfDone() {
+  if (pending_config_fetches_.empty() && pending_well_known_fetches_.empty()) {
     std::move(callback_).Run(std::move(fetch_results_));
   }
 }
diff --git a/content/browser/webid/federated_provider_fetcher.h b/content/browser/webid/federated_provider_fetcher.h
new file mode 100644
index 0000000..6441298
--- /dev/null
+++ b/content/browser/webid/federated_provider_fetcher.h
@@ -0,0 +1,101 @@
+// 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 CONTENT_BROWSER_WEBID_FEDERATED_PROVIDER_FETCHER_H_
+#define CONTENT_BROWSER_WEBID_FEDERATED_PROVIDER_FETCHER_H_
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/browser/webid/fedcm_metrics.h"
+#include "content/browser/webid/idp_network_request_manager.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
+
+namespace content {
+
+// Fetches the config and well-known files for a list of identity providers.
+// Validates returned information and calls callback when done.
+class FederatedProviderFetcher {
+ public:
+  struct FetchError {
+    FetchError(const FetchError& info);
+    FetchError(blink::mojom::FederatedAuthRequestResult result,
+               FedCmRequestIdTokenStatus token_status,
+               absl::optional<std::string> additional_console_error_message);
+    ~FetchError();
+
+    blink::mojom::FederatedAuthRequestResult result;
+    FedCmRequestIdTokenStatus token_status;
+    absl::optional<std::string> additional_console_error_message;
+  };
+
+  struct FetchResult {
+    FetchResult();
+    FetchResult(const FetchResult&);
+    ~FetchResult();
+    GURL identity_provider_config_url;
+    IdpNetworkRequestManager::Endpoints endpoints;
+    absl::optional<IdentityProviderMetadata> metadata;
+    absl::optional<FetchError> error;
+  };
+
+  using RequesterCallback = base::OnceCallback<void(std::vector<FetchResult>)>;
+
+  explicit FederatedProviderFetcher(IdpNetworkRequestManager* network_manager);
+  ~FederatedProviderFetcher();
+
+  FederatedProviderFetcher(const FederatedProviderFetcher&) = delete;
+  FederatedProviderFetcher& operator=(const FederatedProviderFetcher&) = delete;
+
+  // Starts fetch of config and well-known files. Start() should be called at
+  // most once per FederatedProviderFetcher instance.
+  void Start(const std::vector<GURL>& identity_provider_config_urls,
+             int icon_ideal_size,
+             int icon_minimum_size,
+             RequesterCallback callback);
+
+ private:
+  void OnWellKnownFetched(FetchResult& fetch_result,
+                          IdpNetworkRequestManager::FetchStatus status,
+                          const std::set<GURL>& urls);
+  void OnConfigFetched(FetchResult& fetch_result,
+                       IdpNetworkRequestManager::FetchStatus status,
+                       IdpNetworkRequestManager::Endpoints,
+                       IdentityProviderMetadata idp_metadata);
+
+  // Called when fetching either the config endpoint or the well-known
+  // endpoint fails.
+  void OnError(FetchResult& fetch_result,
+               blink::mojom::FederatedAuthRequestResult result,
+               content::FedCmRequestIdTokenStatus token_status,
+               absl::optional<std::string> additional_console_error_message);
+
+  void RunCallbackIfDone();
+
+  RequesterCallback callback_;
+
+  // Config endpoints which has not yet been fetched.
+  base::flat_set<GURL> pending_config_fetches_;
+
+  // Config endpoints for which associated well-known has not yet been
+  // fetched.
+  base::flat_set<GURL> pending_well_known_fetches_;
+
+  std::vector<FetchResult> fetch_results_;
+
+  // Fetches the config and well-known files.
+  base::raw_ptr<IdpNetworkRequestManager, DanglingUntriaged> network_manager_;
+
+  base::WeakPtrFactory<FederatedProviderFetcher> weak_ptr_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_WEBID_FEDERATED_PROVIDER_FETCHER_H_
diff --git a/content/browser/webid/flags.cc b/content/browser/webid/flags.cc
index 019db2f..4449fb7 100644
--- a/content/browser/webid/flags.cc
+++ b/content/browser/webid/flags.cc
@@ -44,4 +44,9 @@
 bool IsFedCmMetricsEndpointEnabled() {
   return base::FeatureList::IsEnabled(features::kFedCmMetricsEndpoint);
 }
+
+bool IsFedCmUserInfoEnabled() {
+  return base::FeatureList::IsEnabled(features::kFedCmUserInfo);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/flags.h b/content/browser/webid/flags.h
index 0698bda..128a291 100644
--- a/content/browser/webid/flags.h
+++ b/content/browser/webid/flags.h
@@ -27,6 +27,9 @@
 // Whether metrics endpoint is enabled.
 bool IsFedCmMetricsEndpointEnabled();
 
+// Whether the UserInfo API is enabled.
+bool IsFedCmUserInfoEnabled();
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_WEBID_FLAGS_H_
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index b3db37c..ec152c3 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -46,10 +46,10 @@
 // TODO(kenrb): These need to be defined in the explainer or draft spec and
 // referenced here.
 
-// Path to find the manifest list on the eTLD+1 host.
-constexpr char kManifestListPath[] = "/.well-known/web-identity";
+// Path to find the well-known file on the eTLD+1 host.
+constexpr char kWellKnownPath[] = "/.well-known/web-identity";
 
-// manifest list JSON keys
+// Well-known file JSON keys
 constexpr char kProviderUrlListKey[] = "provider_urls";
 
 // fedcm.json configuration keys.
@@ -126,7 +126,7 @@
         })");
 }
 
-GURL ResolveManifestUrl(const GURL& config_url, const std::string& endpoint) {
+GURL ResolveConfigUrl(const GURL& config_url, const std::string& endpoint) {
   if (endpoint.empty())
     return GURL();
   return config_url.Resolve(endpoint);
@@ -314,9 +314,9 @@
                      response_code));
 }
 
-void OnManifestListParsed(
-    IdpNetworkRequestManager::FetchManifestListCallback callback,
-    const GURL& manifest_list_url,
+void OnWellKnownParsed(
+    IdpNetworkRequestManager::FetchWellKnownCallback callback,
+    const GURL& well_known_url,
     FetchStatus fetch_status,
     data_decoder::DataDecoder::ValueOrError result) {
   if (callback.IsCancelled())
@@ -353,7 +353,7 @@
     }
     GURL url(*url_str);
     if (!url.is_valid()) {
-      url = manifest_list_url.Resolve(*url_str);
+      url = well_known_url.Resolve(*url_str);
     }
     urls.insert(url);
   }
@@ -362,12 +362,12 @@
                           urls);
 }
 
-void OnManifestParsed(const GURL& provider,
-                      int idp_brand_icon_ideal_size,
-                      int idp_brand_icon_minimum_size,
-                      IdpNetworkRequestManager::FetchManifestCallback callback,
-                      FetchStatus fetch_status,
-                      data_decoder::DataDecoder::ValueOrError result) {
+void OnConfigParsed(const GURL& provider,
+                    int idp_brand_icon_ideal_size,
+                    int idp_brand_icon_minimum_size,
+                    IdpNetworkRequestManager::FetchConfigCallback callback,
+                    FetchStatus fetch_status,
+                    data_decoder::DataDecoder::ValueOrError result) {
   if (fetch_status.parse_status != ParseStatus::kSuccess) {
     std::move(callback).Run(fetch_status, Endpoints(),
                             IdentityProviderMetadata());
@@ -380,7 +380,7 @@
     if (!endpoint || !endpoint->is_string()) {
       return GURL();
     }
-    return ResolveManifestUrl(provider, endpoint->GetString());
+    return ResolveConfigUrl(provider, endpoint->GetString());
   };
 
   Endpoints endpoints;
@@ -528,64 +528,63 @@
 IdpNetworkRequestManager::~IdpNetworkRequestManager() = default;
 
 // static
-absl::optional<GURL> IdpNetworkRequestManager::ComputeManifestListUrl(
+absl::optional<GURL> IdpNetworkRequestManager::ComputeWellKnownUrl(
     const GURL& provider) {
-  GURL manifest_list_url;
+  GURL well_known_url;
   if (net::IsLocalhost(provider)) {
-    manifest_list_url = provider.GetWithEmptyPath();
+    well_known_url = provider.GetWithEmptyPath();
   } else {
     std::string etld_plus_one = GetDomainAndRegistry(
         provider, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
 
     if (etld_plus_one.empty())
       return absl::nullopt;
-    manifest_list_url = GURL(provider.scheme() + "://" + etld_plus_one);
+    well_known_url = GURL(provider.scheme() + "://" + etld_plus_one);
   }
 
   GURL::Replacements replacements;
-  replacements.SetPathStr(kManifestListPath);
-  return manifest_list_url.ReplaceComponents(replacements);
+  replacements.SetPathStr(kWellKnownPath);
+  return well_known_url.ReplaceComponents(replacements);
 }
 
-void IdpNetworkRequestManager::FetchManifestList(
-    const GURL& provider,
-    FetchManifestListCallback callback) {
-  absl::optional<GURL> manifest_list_url =
-      IdpNetworkRequestManager::ComputeManifestListUrl(provider);
+void IdpNetworkRequestManager::FetchWellKnown(const GURL& provider,
+                                              FetchWellKnownCallback callback) {
+  absl::optional<GURL> well_known_url =
+      IdpNetworkRequestManager::ComputeWellKnownUrl(provider);
 
-  if (!manifest_list_url) {
+  if (!well_known_url) {
     // Pass net::HTTP_OK as the |response_code| so we do not add a console error
     // message about a fetch we didn't even attempt.
     FetchStatus fetch_status = {ParseStatus::kHttpNotFoundError, net::HTTP_OK};
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(&OnManifestListParsed, std::move(callback),
-                                  /*manifest_list_url=*/GURL(), fetch_status,
+        FROM_HERE, base::BindOnce(&OnWellKnownParsed, std::move(callback),
+                                  /*well_known_url=*/GURL(), fetch_status,
                                   data_decoder::DataDecoder::ValueOrError()));
     return;
   }
 
   std::unique_ptr<network::ResourceRequest> resource_request =
-      CreateUncredentialedResourceRequest(*manifest_list_url,
+      CreateUncredentialedResourceRequest(*well_known_url,
                                           /*send_origin=*/false,
                                           /* follow_redirects= */ true);
-  DownloadJsonAndParse(std::move(resource_request),
-                       /*url_encoded_post_data=*/absl::nullopt,
-                       base::BindOnce(&OnManifestListParsed,
-                                      std::move(callback), *manifest_list_url),
-                       maxResponseSizeInKiB * 1024);
+  DownloadJsonAndParse(
+      std::move(resource_request),
+      /*url_encoded_post_data=*/absl::nullopt,
+      base::BindOnce(&OnWellKnownParsed, std::move(callback), *well_known_url),
+      maxResponseSizeInKiB * 1024);
 }
 
-void IdpNetworkRequestManager::FetchManifest(const GURL& provider,
-                                             int idp_brand_icon_ideal_size,
-                                             int idp_brand_icon_minimum_size,
-                                             FetchManifestCallback callback) {
+void IdpNetworkRequestManager::FetchConfig(const GURL& provider,
+                                           int idp_brand_icon_ideal_size,
+                                           int idp_brand_icon_minimum_size,
+                                           FetchConfigCallback callback) {
   std::unique_ptr<network::ResourceRequest> resource_request =
       CreateUncredentialedResourceRequest(provider,
                                           /* send_origin= */ false);
   DownloadJsonAndParse(
       std::move(resource_request),
       /*url_encoded_post_data=*/absl::nullopt,
-      base::BindOnce(&OnManifestParsed, provider, idp_brand_icon_ideal_size,
+      base::BindOnce(&OnConfigParsed, provider, idp_brand_icon_ideal_size,
                      idp_brand_icon_minimum_size, std::move(callback)),
       maxResponseSizeInKiB * 1024);
 }
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h
index 79706ba..25c6db7 100644
--- a/content/browser/webid/idp_network_request_manager.h
+++ b/content/browser/webid/idp_network_request_manager.h
@@ -125,9 +125,9 @@
   using DownloadCallback =
       base::OnceCallback<void(std::unique_ptr<std::string> response_body,
                               int response_code)>;
-  using FetchManifestListCallback =
+  using FetchWellKnownCallback =
       base::OnceCallback<void(FetchStatus, const std::set<GURL>&)>;
-  using FetchManifestCallback = base::OnceCallback<
+  using FetchConfigCallback = base::OnceCallback<
       void(FetchStatus, Endpoints, IdentityProviderMetadata)>;
   using FetchClientMetadataCallback =
       base::OnceCallback<void(FetchStatus, ClientMetadata)>;
@@ -151,20 +151,19 @@
   IdpNetworkRequestManager(const IdpNetworkRequestManager&) = delete;
   IdpNetworkRequestManager& operator=(const IdpNetworkRequestManager&) = delete;
 
-  // Computes the manifest list URL from the identity provider URL.
-  static absl::optional<GURL> ComputeManifestListUrl(const GURL& url);
+  // Computes the well-known URL from the identity provider URL.
+  static absl::optional<GURL> ComputeWellKnownUrl(const GURL& url);
 
-  // Fetch the manifest list. This is the /.well-known/web-identity file on
+  // Fetch the well-known file. This is the /.well-known/web-identity file on
   // the eTLD+1 calculated from the provider URL, used to check that the
   // provider URL is valid for this eTLD+1.
-  virtual void FetchManifestList(const GURL& provider,
-                                 FetchManifestListCallback);
+  virtual void FetchWellKnown(const GURL& provider, FetchWellKnownCallback);
 
-  // Attempt to fetch the IDP's FedCM parameters from the fedcm.json manifest.
-  virtual void FetchManifest(const GURL& provider,
-                             int idp_brand_icon_ideal_size,
-                             int idp_brand_icon_minimum_size,
-                             FetchManifestCallback);
+  // Attempt to fetch the IDP's FedCM parameters from the config file.
+  virtual void FetchConfig(const GURL& provider,
+                           int idp_brand_icon_ideal_size,
+                           int idp_brand_icon_minimum_size,
+                           FetchConfigCallback);
 
   virtual void FetchClientMetadata(const GURL& endpoint,
                                    const std::string& client_id,
diff --git a/content/browser/webid/idp_network_request_manager_unittest.cc b/content/browser/webid/idp_network_request_manager_unittest.cc
index 6014a7c..550b153 100644
--- a/content/browser/webid/idp_network_request_manager_unittest.cc
+++ b/content/browser/webid/idp_network_request_manager_unittest.cc
@@ -46,8 +46,8 @@
 
 const char kTestIdpUrl[] = "https://idp.test";
 const char kTestRpUrl[] = "https://rp.test";
-const char kTestManifestListUrl[] = "https://idp.test/.well-known/web-identity";
-const char kTestManifestUrl[] = "https://idp.test/fedcm.json";
+const char kTestWellKnownUrl[] = "https://idp.test/.well-known/web-identity";
+const char kTestConfigUrl[] = "https://idp.test/fedcm.json";
 const char kTestAccountsEndpoint[] = "https://idp.test/accounts_endpoint";
 const char kTestTokenEndpoint[] = "https://idp.test/token_endpoint";
 const char kTestClientMetadataEndpoint[] =
@@ -107,11 +107,11 @@
   }
 
   std::tuple<FetchStatus, std::set<GURL>>
-  SendManifestListRequestAndWaitForResponse(
+  SendWellKnownRequestAndWaitForResponse(
       const char* test_data,
       net::HttpStatusCode http_status = net::HTTP_OK) {
-    GURL manifest_list_url(kTestManifestListUrl);
-    test_url_loader_factory().AddResponse(manifest_list_url.spec(), test_data,
+    GURL well_known_url(kTestWellKnownUrl);
+    test_url_loader_factory().AddResponse(well_known_url.spec(), test_data,
                                           http_status);
 
     base::RunLoop run_loop;
@@ -125,18 +125,18 @@
         });
 
     std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
-    manager->FetchManifestList(GURL(kTestIdpUrl), std::move(callback));
+    manager->FetchWellKnown(GURL(kTestIdpUrl), std::move(callback));
     run_loop.Run();
 
     return {parsed_fetch_status, parsed_urls};
   }
 
   std::tuple<FetchStatus, IdentityProviderMetadata>
-  SendManifestRequestAndWaitForResponse(
+  SendConfigRequestAndWaitForResponse(
       const char* test_data,
       net::HttpStatusCode http_status = net::HTTP_OK) {
-    GURL manifest_url(kTestManifestUrl);
-    test_url_loader_factory().AddResponse(manifest_url.spec(), test_data,
+    GURL config_url(kTestConfigUrl);
+    test_url_loader_factory().AddResponse(config_url.spec(), test_data,
                                           http_status);
 
     base::RunLoop run_loop;
@@ -151,8 +151,8 @@
         });
 
     std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
-    manager->FetchManifest(GURL(kTestManifestUrl), kTestIdpBrandIconIdealSize,
-                           kTestIdpBrandIconMinimumSize, std::move(callback));
+    manager->FetchConfig(GURL(kTestConfigUrl), kTestIdpBrandIconIdealSize,
+                         kTestIdpBrandIconMinimumSize, std::move(callback));
     run_loop.Run();
 
     return {parsed_fetch_status, parsed_idp_metadata};
@@ -492,24 +492,24 @@
   EXPECT_TRUE(accounts.empty());
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ComputeManifestListUrl) {
+TEST_F(IdpNetworkRequestManagerTest, ComputeWellKnownUrl) {
   EXPECT_EQ("https://localhost:8000/.well-known/web-identity",
-            IdpNetworkRequestManager::ComputeManifestListUrl(
+            IdpNetworkRequestManager::ComputeWellKnownUrl(
                 GURL("https://localhost:8000/test/"))
                 ->spec());
 
   EXPECT_EQ("https://google.com/.well-known/web-identity",
-            IdpNetworkRequestManager::ComputeManifestListUrl(
+            IdpNetworkRequestManager::ComputeWellKnownUrl(
                 GURL("https://www.google.com:8000/test/"))
                 ->spec());
 
-  EXPECT_EQ(absl::nullopt, IdpNetworkRequestManager::ComputeManifestListUrl(
+  EXPECT_EQ(absl::nullopt, IdpNetworkRequestManager::ComputeWellKnownUrl(
                                GURL("https://192.101.0.1/test/")));
 }
 
-// Test that IdpNetworkRequestManager::FetchManifestList() fails when the
+// Test that IdpNetworkRequestManager::FetchWellKnown() fails when the
 // identity provider domain is empty.
-TEST_F(IdpNetworkRequestManagerTest, FetchManifestListIllegalDomainFails) {
+TEST_F(IdpNetworkRequestManagerTest, FetchWellKnownIllegalDomainFails) {
   GURL illegal_idp_url("https://192.101.0.1/test/");
 
   network::TestURLLoaderFactory test_url_loader_factory;
@@ -524,65 +524,65 @@
       [&](FetchStatus fetch_status, const std::set<GURL>& urls) {
         EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status);
         // We receive OK here because
-        // IdpNetworkRequestManager::ComputeManifestListUrl() fails.
+        // IdpNetworkRequestManager::ComputeWellKnownUrl() fails.
         EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
         run_loop.Quit();
       });
-  network_manager->FetchManifestList(illegal_idp_url, std::move(callback));
+  network_manager->FetchWellKnown(illegal_idp_url, std::move(callback));
   run_loop.Run();
 
-  // Manifest list download should not have been attempted.
+  // Well-known download should not have been attempted.
   EXPECT_EQ(0, test_url_loader_factory.NumPending());
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ParseManifestList) {
+TEST_F(IdpNetworkRequestManagerTest, ParseWellKnown) {
   FetchStatus fetch_status;
   std::set<GURL> urls;
 
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": ["https://idp.test/fedcm.json"]
   })");
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")}, urls);
 
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": ["https://idp.test/path/fedcm.json"]
   })");
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, urls);
 
   // Value not a list
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": "https://idp.test/fedcm.json"
   })");
   EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);
 
   // Toplevel not a dictionary
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"(
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"(
   ["https://idp.test/fedcm.json"]
   )");
   EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);
 
   // Incorrect key
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "providers": ["https://idp.test/fedcm.json"]
   })");
   EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);
 
   // Array entry not a string
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": [1]
   })");
   EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);
 
   // Relative URLs
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": ["/fedcm.json"]
   })");
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")}, urls);
 
-  std::tie(fetch_status, urls) = SendManifestListRequestAndWaitForResponse(R"({
+  std::tie(fetch_status, urls) = SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": ["fedcm.json"]
   })");
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
@@ -591,7 +591,7 @@
 }
 
 // Test that the "alpha" value in the "branding" JSON is ignored.
-TEST_F(IdpNetworkRequestManagerTest, ParseManifestBrandingRemoveAlpha) {
+TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingRemoveAlpha) {
   const char test_json[] = R"({
   "branding" : {
     "background_color": "#20202020"
@@ -601,7 +601,7 @@
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(test_json);
+      SendConfigRequestAndWaitForResponse(test_json);
 
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -609,7 +609,7 @@
             idp_metadata.brand_background_color);
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ParseManifestBrandingInvalidColor) {
+TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingInvalidColor) {
   const char test_json[] = R"({
   "branding" : {
     "background_color": "fake_color"
@@ -619,7 +619,7 @@
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(test_json);
+      SendConfigRequestAndWaitForResponse(test_json);
 
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -627,7 +627,7 @@
 }
 
 TEST_F(IdpNetworkRequestManagerTest,
-       ParseManifestIgnoreInsufficientContrastTextColor) {
+       ParseConfigIgnoreInsufficientContrastTextColor) {
   const char test_json[] = R"({
   "branding" : {
     "background_color": "#000000",
@@ -638,7 +638,7 @@
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(test_json);
+      SendConfigRequestAndWaitForResponse(test_json);
 
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -647,7 +647,7 @@
 }
 
 TEST_F(IdpNetworkRequestManagerTest,
-       ParseManifestBrandingIgnoreCustomTextColorNoCustomBackgroundColor) {
+       ParseConfigBrandingIgnoreCustomTextColorNoCustomBackgroundColor) {
   const char test_json[] = R"({
   "branding" : {
     "color": "blue"
@@ -657,7 +657,7 @@
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(test_json);
+      SendConfigRequestAndWaitForResponse(test_json);
 
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -665,7 +665,7 @@
   EXPECT_EQ(absl::nullopt, idp_metadata.brand_text_color);
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ParseManifestBrandingSelectBestSize) {
+TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingSelectBestSize) {
   const char test_json[] = R"({
   "branding" : {
     "icons": [
@@ -698,7 +698,7 @@
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(test_json);
+      SendConfigRequestAndWaitForResponse(test_json);
 
   EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -706,9 +706,9 @@
 }
 
 // Test that the icon is rejected if there is an explicit brand icon size in the
-// manifest and it is smaller than the `idp_brand_icon_minimum_size` parameter
-// passed to IdpNetworkRequestManager::FetchManifest().
-TEST_F(IdpNetworkRequestManagerTest, ParseManifestBrandingMinSize) {
+// config and it is smaller than the `idp_brand_icon_minimum_size` parameter
+// passed to IdpNetworkRequestManager::FetchConfig().
+TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingMinSize) {
   ASSERT_EQ(16, kTestIdpBrandIconMinimumSize);
 
   {
@@ -726,7 +726,7 @@
     FetchStatus fetch_status;
     IdentityProviderMetadata idp_metadata;
     std::tie(fetch_status, idp_metadata) =
-        SendManifestRequestAndWaitForResponse(test_json);
+        SendConfigRequestAndWaitForResponse(test_json);
 
     EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
     EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -748,7 +748,7 @@
     FetchStatus fetch_status;
     IdentityProviderMetadata idp_metadata;
     std::tie(fetch_status, idp_metadata) =
-        SendManifestRequestAndWaitForResponse(test_json);
+        SendConfigRequestAndWaitForResponse(test_json);
 
     EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
     EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
@@ -1011,29 +1011,29 @@
   EXPECT_FALSE(callback_called);
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingManifestList) {
+TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingWellKnown) {
   FetchStatus fetch_status;
   std::set<GURL> urls;
   std::tie(fetch_status, urls) =
-      SendManifestListRequestAndWaitForResponse(R"({
+      SendWellKnownRequestAndWaitForResponse(R"({
   "provider_urls": ["https://idp.test/fedcm.json"]
   })",
-                                                net::HTTP_REQUEST_TIMEOUT);
+                                             net::HTTP_REQUEST_TIMEOUT);
   EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_REQUEST_TIMEOUT, fetch_status.response_code);
   EXPECT_EQ(std::set<GURL>{}, urls);
 }
 
-TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingManifest) {
+TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingConfig) {
   FetchStatus fetch_status;
   IdentityProviderMetadata idp_metadata;
   std::tie(fetch_status, idp_metadata) =
-      SendManifestRequestAndWaitForResponse(R"({
+      SendConfigRequestAndWaitForResponse(R"({
   "branding" : {
     "color": "blue"
   }
   })",
-                                            net::HTTP_NOT_FOUND);
+                                          net::HTTP_NOT_FOUND);
   EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status);
   EXPECT_EQ(net::HTTP_NOT_FOUND, fetch_status.response_code);
 }
diff --git a/content/browser/webid/test/delegated_idp_network_request_manager.cc b/content/browser/webid/test/delegated_idp_network_request_manager.cc
index 11c74f0..46fcc2f 100644
--- a/content/browser/webid/test/delegated_idp_network_request_manager.cc
+++ b/content/browser/webid/test/delegated_idp_network_request_manager.cc
@@ -15,19 +15,19 @@
 DelegatedIdpNetworkRequestManager::~DelegatedIdpNetworkRequestManager() =
     default;
 
-void DelegatedIdpNetworkRequestManager::FetchManifestList(
+void DelegatedIdpNetworkRequestManager::FetchWellKnown(
     const GURL& provider,
-    FetchManifestListCallback callback) {
-  delegate_->FetchManifestList(provider, std::move(callback));
+    FetchWellKnownCallback callback) {
+  delegate_->FetchWellKnown(provider, std::move(callback));
 }
 
-void DelegatedIdpNetworkRequestManager::FetchManifest(
+void DelegatedIdpNetworkRequestManager::FetchConfig(
     const GURL& provider,
     int idp_brand_icon_ideal_size,
     int idp_brand_icon_minimum_size,
-    FetchManifestCallback callback) {
-  delegate_->FetchManifest(provider, idp_brand_icon_ideal_size,
-                           idp_brand_icon_minimum_size, std::move(callback));
+    FetchConfigCallback callback) {
+  delegate_->FetchConfig(provider, idp_brand_icon_ideal_size,
+                         idp_brand_icon_minimum_size, std::move(callback));
 }
 
 void DelegatedIdpNetworkRequestManager::FetchClientMetadata(
diff --git a/content/browser/webid/test/delegated_idp_network_request_manager.h b/content/browser/webid/test/delegated_idp_network_request_manager.h
index 0c7db015..b56f594 100644
--- a/content/browser/webid/test/delegated_idp_network_request_manager.h
+++ b/content/browser/webid/test/delegated_idp_network_request_manager.h
@@ -24,12 +24,12 @@
   DelegatedIdpNetworkRequestManager& operator=(
       const DelegatedIdpNetworkRequestManager&) = delete;
 
-  void FetchManifestList(const GURL& provider,
-                         FetchManifestListCallback callback) override;
-  void FetchManifest(const GURL& provider,
-                     int idp_brand_icon_ideal_size,
-                     int idp_brand_icon_minimum_size,
-                     FetchManifestCallback callback) override;
+  void FetchWellKnown(const GURL& provider,
+                      FetchWellKnownCallback callback) override;
+  void FetchConfig(const GURL& provider,
+                   int idp_brand_icon_ideal_size,
+                   int idp_brand_icon_minimum_size,
+                   FetchConfigCallback callback) override;
   void FetchClientMetadata(const GURL& endpoint,
                            const std::string& client_id,
                            FetchClientMetadataCallback callback) override;
diff --git a/content/browser/webid/test/mock_idp_network_request_manager.h b/content/browser/webid/test/mock_idp_network_request_manager.h
index 1fbc6aa2..808bebb 100644
--- a/content/browser/webid/test/mock_idp_network_request_manager.h
+++ b/content/browser/webid/test/mock_idp_network_request_manager.h
@@ -20,9 +20,8 @@
   MockIdpNetworkRequestManager& operator=(const MockIdpNetworkRequestManager&) =
       delete;
 
-  MOCK_METHOD2(FetchManifestList, void(const GURL&, FetchManifestListCallback));
-  MOCK_METHOD4(FetchManifest,
-               void(const GURL&, int, int, FetchManifestCallback));
+  MOCK_METHOD2(FetchWellKnown, void(const GURL&, FetchWellKnownCallback));
+  MOCK_METHOD4(FetchConfig, void(const GURL&, int, int, FetchConfigCallback));
   MOCK_METHOD3(FetchClientMetadata,
                void(const GURL&,
                     const std::string&,
diff --git a/content/browser/webid/webid_browsertest.cc b/content/browser/webid/webid_browsertest.cc
index ad718efc..9f7c9f69 100644
--- a/content/browser/webid/webid_browsertest.cc
+++ b/content/browser/webid/webid_browsertest.cc
@@ -50,14 +50,13 @@
 
 constexpr char kRpHostName[] = "rp.example";
 
-// Use localhost for IDP so that the manifest list can be fetched from the test
-// server's custom port.
-// IdpNetworkRequestManager::ComputeManifestListUrl() does not enforce a
-// specific port if the IDP is localhost.
+// Use localhost for IDP so that the well-known file can be fetched from the
+// test server's custom port. IdpNetworkRequestManager::ComputeWellKnownUrl()
+// does not enforce a specific port if the IDP is localhost.
 constexpr char kIdpOrigin[] = "https://127.0.0.1";
 
-constexpr char kExpectedManifestPath[] = "/fedcm.json";
-constexpr char kExpectedManifestListPath[] = "/.well-known/web-identity";
+constexpr char kExpectedConfigPath[] = "/fedcm.json";
+constexpr char kExpectedWellKnownPath[] = "/.well-known/web-identity";
 constexpr char kTestContentType[] = "application/json";
 constexpr char kIdpForbiddenHeader[] = "Sec-FedCM-CSRF";
 
@@ -81,7 +80,7 @@
 // test HTTP server.
 class IdpTestServer {
  public:
-  struct ManifestDetails {
+  struct ConfigDetails {
     HttpStatusCode status_code;
     std::string content_type;
     std::string accounts_endpoint_url;
@@ -109,13 +108,13 @@
     }
 
     auto response = std::make_unique<BasicHttpResponse>();
-    if (IsGetRequestWithPath(request, kExpectedManifestPath)) {
-      BuildManifestResponseFromDetails(*response.get(), manifest_details_);
+    if (IsGetRequestWithPath(request, kExpectedConfigPath)) {
+      BuildConfigResponseFromDetails(*response.get(), config_details_);
       return response;
     }
 
-    if (IsGetRequestWithPath(request, kExpectedManifestListPath)) {
-      BuildManifestListResponse(*response.get());
+    if (IsGetRequestWithPath(request, kExpectedWellKnownPath)) {
+      BuildWellKnownResponse(*response.get());
       return response;
     }
 
@@ -139,13 +138,13 @@
     return response;
   }
 
-  void SetManifestResponseDetails(ManifestDetails details) {
-    manifest_details_ = details;
+  void SetConfigResponseDetails(ConfigDetails details) {
+    config_details_ = details;
   }
 
  private:
-  void BuildManifestResponseFromDetails(BasicHttpResponse& response,
-                                        const ManifestDetails& details) {
+  void BuildConfigResponseFromDetails(BasicHttpResponse& response,
+                                      const ConfigDetails& details) {
     std::string content = ConvertToJsonDictionary(
         {{"accounts_endpoint", details.accounts_endpoint_url},
          {"client_metadata_endpoint", details.client_metadata_endpoint_url},
@@ -155,9 +154,9 @@
     response.set_content_type(details.content_type);
   }
 
-  void BuildManifestListResponse(BasicHttpResponse& response) {
+  void BuildWellKnownResponse(BasicHttpResponse& response) {
     std::string content = base::StringPrintf("{\"provider_urls\": [\"%s\"]}",
-                                             kExpectedManifestPath);
+                                             kExpectedConfigPath);
     response.set_code(net::HTTP_OK);
     response.set_content(content);
     response.set_content_type("application/json");
@@ -175,7 +174,7 @@
     return out;
   }
 
-  ManifestDetails manifest_details_;
+  ConfigDetails config_details_;
 };
 
 }  // namespace
@@ -216,7 +215,7 @@
     std::vector<base::test::FeatureRef> features;
 
     // kSplitCacheByNetworkIsolationKey feature is needed to verify
-    // that the network shard for fetching the fedcm manifest file is different
+    // that the network shard for fetching the config file is different
     // from that used for other IdP transactions, to prevent data leakage.
     features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
     features.push_back(features::kFedCm);
@@ -250,7 +249,7 @@
     )";
   }
 
-  IdpTestServer::ManifestDetails BuildValidManifestDetails() {
+  IdpTestServer::ConfigDetails BuildValidConfigDetails() {
     std::string accounts_endpoint_url = "/fedcm/accounts_endpoint.json";
     std::string client_metadata_endpoint_url =
         "/fedcm/client_metadata_endpoint.json";
@@ -297,7 +296,7 @@
 
 // Verify a standard login flow with IdP sign-in page.
 IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, FullLoginFlow) {
-  idp_server()->SetManifestResponseDetails(BuildValidManifestDetails());
+  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());
 
   EXPECT_EQ(std::string(kToken), EvalJs(shell(), GetBasicRequestString()));
 }
@@ -305,21 +304,21 @@
 // Verify full login flow where the IdP uses absolute rather than relative
 // URLs.
 IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, AbsoluteURLs) {
-  IdpTestServer::ManifestDetails manifest_details = BuildValidManifestDetails();
-  manifest_details.accounts_endpoint_url = "/fedcm/accounts_endpoint.json";
-  manifest_details.client_metadata_endpoint_url =
+  IdpTestServer::ConfigDetails config_details = BuildValidConfigDetails();
+  config_details.accounts_endpoint_url = "/fedcm/accounts_endpoint.json";
+  config_details.client_metadata_endpoint_url =
       "/fedcm/client_metadata_endpoint.json";
-  manifest_details.id_assertion_endpoint_url =
+  config_details.id_assertion_endpoint_url =
       "/fedcm/id_assertion_endpoint.json";
 
-  idp_server()->SetManifestResponseDetails(manifest_details);
+  idp_server()->SetConfigResponseDetails(config_details);
 
   EXPECT_EQ(std::string(kToken), EvalJs(shell(), GetBasicRequestString()));
 }
 
 // Verify an attempt to invoke FedCM with an insecure IDP path fails.
 IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, FailsOnHTTP) {
-  idp_server()->SetManifestResponseDetails(BuildValidManifestDetails());
+  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());
 
   std::string script = R"(
         (async () => {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 806ddd3..d91627e4 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -215,6 +215,7 @@
     {wf::EnableFedCm, features::kFedCm, kSetOnlyIfOverridden},
     {wf::EnableFedCmMultipleIdentityProviders,
      features::kFedCmMultipleIdentityProviders, kDefault},
+    {wf::EnableFedCmUserInfo, features::kFedCmUserInfo, kDefault},
     {wf::EnableFencedFrames, features::kPrivacySandboxAdsAPIsOverride,
      kSetOnlyIfOverridden},
     {wf::EnableSharedStorageAPI, features::kPrivacySandboxAdsAPIsOverride,
diff --git a/content/public/browser/download_manager_delegate.h b/content/public/browser/download_manager_delegate.h
index 2554baa2..56e004d 100644
--- a/content/public/browser/download_manager_delegate.h
+++ b/content/public/browser/download_manager_delegate.h
@@ -67,7 +67,7 @@
     const base::FilePath& target_path,
     download::DownloadItem::TargetDisposition disposition,
     download::DownloadDangerType danger_type,
-    download::DownloadItem::MixedContentStatus mixed_content_status,
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status,
     const base::FilePath& intermediate_path,
     const base::FilePath& display_name,
     const std::string& mime_type,
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 22603cb..556d2b1e 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -399,6 +399,11 @@
              "FedCmMultipleIdentityProviders",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables usage of the FedCM API with the User Info API at the same time.
+BASE_FEATURE(kFedCmUserInfo,
+             "FedCmUserInfo",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Field trial boolean parameter which indicates whether IdpSigninStatus API is
 // used in FedCM API.
 const char kFedCmIdpSigninStatusFieldTrialParamName[] = "IdpSigninStatus";
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 5e8b22b..3800796 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -87,6 +87,7 @@
     kFedCmIdpSigninStatusMetricsOnlyFieldTrialParamName[];
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmMetricsEndpoint);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmMultipleIdentityProviders);
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmUserInfo);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFirstPartySets);
 CONTENT_EXPORT extern const base::FeatureParam<bool>
     kFirstPartySetsClearSiteDataOnChangedSets;
diff --git a/content/public/test/fake_download_item.cc b/content/public/test/fake_download_item.cc
index a6d4d49..c64fb49 100644
--- a/content/public/test/fake_download_item.cc
+++ b/content/public/test/fake_download_item.cc
@@ -256,17 +256,17 @@
   is_dangerous_ = is_dangerous;
 }
 
-void FakeDownloadItem::SetIsMixedContent(bool is_mixed_content) {
-  is_mixed_content_ = is_mixed_content;
+void FakeDownloadItem::SetIsInsecure(bool is_insecure) {
+  is_insecure_ = is_insecure;
 }
 
 void FakeDownloadItem::SetDangerType(download::DownloadDangerType danger_type) {
   danger_type_ = danger_type;
 }
 
-void FakeDownloadItem::SetMixedContentStatus(
-    download::DownloadItem::MixedContentStatus mixed_content_status) {
-  mixed_content_status_ = mixed_content_status;
+void FakeDownloadItem::SetInsecureDownloadStatus(
+    download::DownloadItem::InsecureDownloadStatus insecure_download_status) {
+  insecure_download_status_ = insecure_download_status;
 }
 
 bool FakeDownloadItem::GetOpenWhenComplete() const {
@@ -282,7 +282,7 @@
   NOTREACHED();
 }
 
-void FakeDownloadItem::ValidateMixedContentDownload() {
+void FakeDownloadItem::ValidateInsecureDownload() {
   NOTREACHED();
 }
 
@@ -479,17 +479,17 @@
   return is_dangerous_;
 }
 
-bool FakeDownloadItem::IsMixedContent() const {
-  return is_mixed_content_;
+bool FakeDownloadItem::IsInsecure() const {
+  return is_insecure_;
 }
 
 download::DownloadDangerType FakeDownloadItem::GetDangerType() const {
   return danger_type_;
 }
 
-download::DownloadItem::MixedContentStatus
-FakeDownloadItem::GetMixedContentStatus() const {
-  return mixed_content_status_;
+download::DownloadItem::InsecureDownloadStatus
+FakeDownloadItem::GetInsecureDownloadStatus() const {
+  return insecure_download_status_;
 }
 
 bool FakeDownloadItem::TimeRemaining(base::TimeDelta* remaining) const {
diff --git a/content/public/test/fake_download_item.h b/content/public/test/fake_download_item.h
index 212c077..332540431 100644
--- a/content/public/test/fake_download_item.h
+++ b/content/public/test/fake_download_item.h
@@ -100,9 +100,9 @@
   download::DownloadItemRenameHandler* GetRenameHandler() override;
   const download::DownloadItemRerouteInfo& GetRerouteInfo() const override;
   bool IsDangerous() const override;
-  bool IsMixedContent() const override;
+  bool IsInsecure() const override;
   download::DownloadDangerType GetDangerType() const override;
-  download::DownloadItem::MixedContentStatus GetMixedContentStatus()
+  download::DownloadItem::InsecureDownloadStatus GetInsecureDownloadStatus()
       const override;
   bool TimeRemaining(base::TimeDelta* remaining) const override;
   int64_t CurrentSpeed() const override;
@@ -128,7 +128,7 @@
   void SimulateErrorForTesting(
       download::DownloadInterruptReason reason) override;
   void ValidateDangerousDownload() override;
-  void ValidateMixedContentDownload() override;
+  void ValidateInsecureDownload() override;
   void StealDangerousDownload(bool delete_file_afterward,
                               AcquireFileCallback callback) override;
   void Rename(const base::FilePath& name,
@@ -168,10 +168,10 @@
   void SetPercentComplete(int percent_complete);
   void SetDummyFilePath(const base::FilePath& dummy_file_path);
   void SetIsDangerous(bool is_dangerous);
-  void SetIsMixedContent(bool is_mixed_content);
+  void SetIsInsecure(bool is_insecure);
   void SetDangerType(download::DownloadDangerType danger_type);
-  void SetMixedContentStatus(
-      download::DownloadItem::MixedContentStatus mixed_content_status);
+  void SetInsecureDownloadStatus(
+      download::DownloadItem::InsecureDownloadStatus insecure_download_status);
 
  private:
   base::ObserverList<Observer>::Unchecked observers_;
@@ -206,12 +206,12 @@
   download::DownloadItemRerouteInfo reroute_info_;
   bool open_when_complete_ = false;
   bool is_dangerous_ = false;
-  bool is_mixed_content_ = false;
+  bool is_insecure_ = false;
   absl::optional<net::IsolationInfo> isolation_info_;
   download::DownloadDangerType danger_type_ =
       download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
-  download::DownloadItem::MixedContentStatus mixed_content_status_ =
-      download::DownloadItem::MixedContentStatus::UNKNOWN;
+  download::DownloadItem::InsecureDownloadStatus insecure_download_status_ =
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN;
 
   // The members below are to be returned by methods, which return by reference.
   GURL dummy_url;
diff --git a/content/shell/browser/shell_download_manager_delegate.cc b/content/shell/browser/shell_download_manager_delegate.cc
index 712d03ee..cd63d92 100644
--- a/content/shell/browser/shell_download_manager_delegate.cc
+++ b/content/shell/browser/shell_download_manager_delegate.cc
@@ -77,7 +77,7 @@
         download->GetForcedFilePath(),
         download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        download::DownloadItem::MixedContentStatus::UNKNOWN,
+        download::DownloadItem::InsecureDownloadStatus::UNKNOWN,
         download->GetForcedFilePath(), base::FilePath(),
         std::string() /*mime_type*/, download::DOWNLOAD_INTERRUPT_REASON_NONE);
     return true;
@@ -143,7 +143,7 @@
     std::move(callback).Run(
         suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        download::DownloadItem::MixedContentStatus::UNKNOWN,
+        download::DownloadItem::InsecureDownloadStatus::UNKNOWN,
         suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")),
         base::FilePath(), std::string() /*mime_type*/,
         download::DOWNLOAD_INTERRUPT_REASON_NONE);
@@ -193,12 +193,12 @@
   NOTIMPLEMENTED();
 #endif
 
-  std::move(callback).Run(result,
-                          download::DownloadItem::TARGET_DISPOSITION_PROMPT,
-                          download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                          download::DownloadItem::MixedContentStatus::UNKNOWN,
-                          result, base::FilePath(), std::string() /*mime_type*/,
-                          download::DOWNLOAD_INTERRUPT_REASON_NONE);
+  std::move(callback).Run(
+      result, download::DownloadItem::TARGET_DISPOSITION_PROMPT,
+      download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN, result,
+      base::FilePath(), std::string() /*mime_type*/,
+      download::DOWNLOAD_INTERRUPT_REASON_NONE);
 }
 
 void ShellDownloadManagerDelegate::SetDownloadBehaviorForTesting(
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index a89a8840..230d740f 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2352,6 +2352,7 @@
     "../browser/preloading/prefetch/prefetch_params_unittest.cc",
     "../browser/preloading/prefetch/prefetch_proxy_configurator_unittest.cc",
     "../browser/preloading/prefetch/prefetch_service_unittest.cc",
+    "../browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc",
     "../browser/preloading/prefetch/prefetch_type_unittest.cc",
     "../browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc",
     "../browser/preloading/prefetch/proxy_lookup_client_impl_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index 95206f5..30b202b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -121,6 +121,12 @@
 crbug.com/1099576 [ mac intel ] GpuProcess_mac_webgl_backgrounded_high_performance [ Failure ]
 
 # canvas2d flakily crashes, which then affects the following test.
+# finder:disable-stale Nature of crash causes stale result finder to remove these
+crbug.com/1383609 [ android android-sm-a135m ] GpuProcess_canvas2d [ Failure ]
+crbug.com/1383609 [ android android-sm-a135m ] GpuProcess_css3d [ Failure ]
+crbug.com/1383609 [ android android-sm-a235m ] GpuProcess_canvas2d [ Failure ]
+crbug.com/1383609 [ android android-sm-a235m ] GpuProcess_css3d [ Failure ]
+# finder:enable-stale
 
 crbug.com/1384930 [ android android-sm-a135m ] GpuProcess_visibility [ RetryOnFailure ]
 
diff --git a/device/bluetooth/bluetooth_adapter.cc b/device/bluetooth/bluetooth_adapter.cc
index 727bf44..5cc8ba4e 100644
--- a/device/bluetooth/bluetooth_adapter.cc
+++ b/device/bluetooth/bluetooth_adapter.cc
@@ -347,6 +347,12 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS)
+void BluetoothAdapter::NotifyDeviceBondedChanged(BluetoothDevice* device,
+                                                 bool new_bonded_status) {
+  for (auto& observer : observers_)
+    observer.DeviceBondedChanged(this, device, new_bonded_status);
+}
+
 void BluetoothAdapter::NotifyDeviceIsBlockedByPolicyChanged(
     BluetoothDevice* device,
     bool new_blocked_status) {
diff --git a/device/bluetooth/bluetooth_adapter.h b/device/bluetooth/bluetooth_adapter.h
index 4617c62e..97cc160 100644
--- a/device/bluetooth/bluetooth_adapter.h
+++ b/device/bluetooth/bluetooth_adapter.h
@@ -160,9 +160,17 @@
         const BluetoothDevice::ServiceDataMap& service_data_map,
         const BluetoothDevice::ManufacturerDataMap& manufacturer_data_map) {}
 
+#if BUILDFLAG(IS_CHROMEOS)
+    // Called when the bonded property of the device |device| known to the
+    // adapter |adapter| changed.
+    virtual void DeviceBondedChanged(BluetoothAdapter* adapter,
+                                     BluetoothDevice* device,
+                                     bool new_bonded_status) {}
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
-    // Called when paired property of the device |device| known to the adapter
-    // |adapter| changed.
+    // Called when the paired property of the device |device| known to the
+    // adapter |adapter| changed.
     virtual void DevicePairedChanged(BluetoothAdapter* adapter,
                                      BluetoothDevice* device,
                                      bool new_paired_status) {}
@@ -672,6 +680,8 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS)
+  void NotifyDeviceBondedChanged(BluetoothDevice* device,
+                                 bool new_bonded_status);
   void NotifyDeviceIsBlockedByPolicyChanged(BluetoothDevice* device,
                                             bool new_blocked_status);
 #endif
diff --git a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
index 81a1260e..23fb523 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
@@ -924,6 +924,9 @@
       property_name == properties->address.name() ||
       property_name == properties->name.name() ||
       property_name == properties->paired.name() ||
+#if BUILDFLAG(IS_CHROMEOS)
+      property_name == properties->bonded.name() ||
+#endif
       property_name == properties->trusted.name() ||
       property_name == properties->connected.name() ||
       property_name == properties->uuids.name() ||
@@ -967,6 +970,12 @@
     NotifyDevicePairedChanged(device_bluez, properties->paired.value());
   }
 
+#if BUILDFLAG(IS_CHROMEOS)
+  if (property_name == properties->bonded.name()) {
+    NotifyDeviceBondedChanged(device_bluez, properties->bonded.value());
+  }
+#endif
+
   // UMA connection counting
   if (property_name == properties->connected.name()) {
     int count = 0;
diff --git a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
index b1f8900..6e5f72d6 100644
--- a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
+++ b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
@@ -2255,6 +2255,47 @@
   EXPECT_EQ(std::string(kNewAddress), devices[idx]->GetAddress());
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+TEST_F(BluetoothBlueZTest, DeviceBondedChanged) {
+  // Simulate a change of bonded state of a device.
+  GetAdapter();
+
+  BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
+  ASSERT_EQ(2U, devices.size());
+
+  int idx = GetDeviceIndexByAddress(
+      devices, bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress);
+  ASSERT_NE(-1, idx);
+  ASSERT_EQ(bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress,
+            devices[idx]->GetAddress());
+  ASSERT_EQ(true, devices[idx]->IsBonded());
+
+  // Install an observer; expect the DeviceBondedChanged method to be called
+  // when we change the bonded state of the device.
+  TestBluetoothAdapterObserver observer(adapter_);
+
+  bluez::FakeBluetoothDeviceClient::Properties* properties =
+      fake_bluetooth_device_client_->GetProperties(dbus::ObjectPath(
+          bluez::FakeBluetoothDeviceClient::kPairedDevicePath));
+
+  properties->bonded.ReplaceValue(false);
+
+  EXPECT_EQ(1, observer.device_changed_count());
+  EXPECT_EQ(1, observer.device_bonded_changed_count());
+  EXPECT_FALSE(observer.device_new_bonded_status());
+  EXPECT_EQ(devices[idx], observer.last_device());
+
+  // Change the bonded state back to true to examine the consistent behavior of
+  // DevicePairedChanged method.
+  properties->bonded.ReplaceValue(true);
+
+  EXPECT_EQ(2, observer.device_changed_count());
+  EXPECT_EQ(2, observer.device_bonded_changed_count());
+  EXPECT_TRUE(observer.device_new_bonded_status());
+  EXPECT_EQ(devices[idx], observer.last_device());
+}
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 TEST_F(BluetoothBlueZTest, DevicePairedChanged) {
   // Simulate a change of paired state of a device.
diff --git a/device/bluetooth/bluez/bluetooth_device_bluez.cc b/device/bluetooth/bluez/bluetooth_device_bluez.cc
index aa3e8a1..794e254a 100644
--- a/device/bluetooth/bluez/bluetooth_device_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_device_bluez.cc
@@ -422,20 +422,28 @@
           object_path_);
   DCHECK(properties);
 
-  // The Paired property reflects the successful pairing for BR/EDR/LE. The
-  // value of the Paired property is always false for the devices that don't
-  // support pairing. Once a device is paired successfully, both Paired and
-  // Trusted properties will be set to true.
+  // The "paired" property reflects the successful pairing for BR/EDR/LE. The
+  // value of the "paired" property is always false for the devices that don't
+  // support pairing. Once a device is paired successfully, both the "paired"
+  // and the "trusted" properties will be set to true.
   return properties->paired.value();
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
 bool BluetoothDeviceBlueZ::IsBonded() const {
-  // TODO(b/217464014): Update to retrieve whether the peripheral is bonded to
-  // the device when this information is available from the platform.
-  return IsPaired();
+  bluez::BluetoothDeviceClient::Properties* properties =
+      bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->GetProperties(
+          object_path_);
+  DCHECK(properties);
+
+  // The "bonded" property reflects the successful pairing for BR/EDR/LE, where
+  // the information required to initiate and authenticate connections is stored
+  // on-device. The value of the "bonded" property is always false for the
+  // devices that don't support pairing. Once a device is bonded successfully,
+  // the "bonded", "paired", and "trusted" properties will be set to true.
+  return properties->bonded.value();
 }
-#endif  // BUILDFLAG(IS_CHROMEOS)
+#endif
 
 bool BluetoothDeviceBlueZ::IsConnected() const {
   bluez::BluetoothDeviceClient::Properties* properties =
diff --git a/device/bluetooth/dbus/bluetooth_device_client.cc b/device/bluetooth/dbus/bluetooth_device_client.cc
index 2ceaac0..7a203ff 100644
--- a/device/bluetooth/dbus/bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/bluetooth_device_client.cc
@@ -32,6 +32,10 @@
 // |bluetooth_device::kConnectClassic| once it has been uprev'd.
 constexpr char kConnectClassicPlaceholder[] = "ConnectClassic";
 
+// TODO(b/217464014): Remove this constant and replace with
+// |bluetooth_device::kBondedProperty| once it has been uprev'd.
+constexpr char kBondedPropertyPlaceholder[] = "Bonded";
+
 std::unique_ptr<BluetoothServiceAttributeValueBlueZ> ReadAttributeValue(
     dbus::MessageReader* struct_reader) {
   uint8_t type_val;
@@ -199,6 +203,7 @@
   RegisterProperty(bluetooth_device::kAppearanceProperty, &appearance);
   RegisterProperty(bluetooth_device::kUUIDsProperty, &uuids);
   RegisterProperty(bluetooth_device::kPairedProperty, &paired);
+  RegisterProperty(kBondedPropertyPlaceholder, &bonded);
   RegisterProperty(bluetooth_device::kConnectedProperty, &connected);
   RegisterProperty(bluetooth_device::kConnectedLEProperty, &connected_le);
   RegisterProperty(bluetooth_device::kTrustedProperty, &trusted);
diff --git a/device/bluetooth/dbus/bluetooth_device_client.h b/device/bluetooth/dbus/bluetooth_device_client.h
index ea1e80f..4ffda62b3 100644
--- a/device/bluetooth/dbus/bluetooth_device_client.h
+++ b/device/bluetooth/dbus/bluetooth_device_client.h
@@ -82,6 +82,9 @@
     // Indicates that the device is currently paired. Read-only.
     dbus::Property<bool> paired;
 
+    // Indicates that the device is currently bonded. Read-only.
+    dbus::Property<bool> bonded;
+
     // Indicates that the device is currently connected via any transports.
     // Read-only.
     dbus::Property<bool> connected;
diff --git a/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
index 9772578..ea9eb68 100644
--- a/device/bluetooth/dbus/fake_bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
@@ -330,6 +330,7 @@
   properties->name.set_valid(true);
   properties->alias.ReplaceValue(kPairedDeviceAlias);
   properties->paired.ReplaceValue(true);
+  properties->bonded.ReplaceValue(true);
   properties->trusted.ReplaceValue(true);
   properties->adapter.ReplaceValue(
       dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath));
@@ -354,6 +355,7 @@
   properties->name.set_valid(true);
   properties->alias.ReplaceValue(kPairedUnconnectableDeviceAlias);
   properties->paired.ReplaceValue(true);
+  properties->bonded.ReplaceValue(true);
   properties->trusted.ReplaceValue(true);
   properties->adapter.ReplaceValue(
       dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath));
@@ -860,6 +862,7 @@
     properties->connected.ReplaceValue(true);
     properties->connected_le.ReplaceValue(true);
     properties->paired.ReplaceValue(false);
+    properties->bonded.ReplaceValue(false);
     properties->name.ReplaceValue(kConnectedTrustedNotPairedDeviceName);
     properties->name.set_valid(true);
   } else {
@@ -891,8 +894,10 @@
   properties->bluetooth_class.ReplaceValue(props.device_class);
   properties->trusted.ReplaceValue(props.is_trusted);
 
-  if (props.is_trusted)
+  if (props.is_trusted) {
     properties->paired.ReplaceValue(true);
+    properties->bonded.ReplaceValue(true);
+  }
 
   std::unique_ptr<SimulatedPairingOptions> options(new SimulatedPairingOptions);
   options->pairing_method = props.pairing_method;
diff --git a/device/bluetooth/test/fake_peripheral.cc b/device/bluetooth/test/fake_peripheral.cc
index a1f387c..f92da0ae 100644
--- a/device/bluetooth/test/fake_peripheral.cc
+++ b/device/bluetooth/test/fake_peripheral.cc
@@ -126,7 +126,7 @@
   NOTREACHED();
   return device::BLUETOOTH_TRANSPORT_INVALID;
 }
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 
 std::string FakePeripheral::GetIdentifier() const {
   NOTREACHED();
diff --git a/device/bluetooth/test/fake_peripheral.h b/device/bluetooth/test/fake_peripheral.h
index 3a52119..bb138b3 100644
--- a/device/bluetooth/test/fake_peripheral.h
+++ b/device/bluetooth/test/fake_peripheral.h
@@ -78,7 +78,7 @@
   uint32_t GetBluetoothClass() const override;
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   device::BluetoothTransport GetType() const override;
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   std::string GetIdentifier() const override;
   std::string GetAddress() const override;
   AddressType GetAddressType() const override;
diff --git a/device/bluetooth/test/test_bluetooth_adapter_observer.cc b/device/bluetooth/test/test_bluetooth_adapter_observer.cc
index 655d25c6..713fe525 100644
--- a/device/bluetooth/test/test_bluetooth_adapter_observer.cc
+++ b/device/bluetooth/test/test_bluetooth_adapter_observer.cc
@@ -44,6 +44,10 @@
   last_rssi_ = 128;
   last_tx_power_ = 128;
   last_appearance_ = 128;
+#if BUILDFLAG(IS_CHROMEOS)
+  device_bonded_changed_count_ = 0;
+  device_new_bonded_status_ = false;
+#endif
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   device_paired_changed_count_ = 0;
   device_new_paired_status_ = false;
@@ -187,6 +191,19 @@
   QuitMessageLoop();
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+void TestBluetoothAdapterObserver::DeviceBondedChanged(
+    device::BluetoothAdapter* adapter,
+    device::BluetoothDevice* device,
+    bool new_bonded_status) {
+  ++device_bonded_changed_count_;
+  last_device_ = device;
+  device_new_bonded_status_ = new_bonded_status;
+
+  QuitMessageLoop();
+}
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 void TestBluetoothAdapterObserver::DevicePairedChanged(
     device::BluetoothAdapter* adapter,
diff --git a/device/bluetooth/test/test_bluetooth_adapter_observer.h b/device/bluetooth/test/test_bluetooth_adapter_observer.h
index 95beddd5..62ba3fae0 100644
--- a/device/bluetooth/test/test_bluetooth_adapter_observer.h
+++ b/device/bluetooth/test/test_bluetooth_adapter_observer.h
@@ -59,6 +59,11 @@
       const device::BluetoothDevice::ServiceDataMap& service_data_map,
       const device::BluetoothDevice::ManufacturerDataMap& manufacturer_data_map)
       override;
+#if BUILDFLAG(IS_CHROMEOS)
+  void DeviceBondedChanged(device::BluetoothAdapter* adapter,
+                           device::BluetoothDevice* device,
+                           bool new_bonded_status) override;
+#endif
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   void DevicePairedChanged(device::BluetoothAdapter* adapter,
                            device::BluetoothDevice* device,
@@ -156,6 +161,13 @@
     return last_manufacturer_data_map_;
   }
 
+#if BUILDFLAG(IS_CHROMEOS)
+  int device_bonded_changed_count() const {
+    return device_bonded_changed_count_;
+  }
+  bool device_new_bonded_status() const { return device_new_bonded_status_; }
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   int device_paired_changed_count() const {
     return device_paired_changed_count_;
@@ -271,6 +283,11 @@
   base::RepeatingClosure discovering_changed_callback_;
   base::RepeatingClosure discovery_change_completed_callback_;
 
+#if BUILDFLAG(IS_CHROMEOS)
+  int device_bonded_changed_count_;
+  bool device_new_bonded_status_;
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
   int device_paired_changed_count_;
   bool device_new_paired_status_;
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn
index 18abf94..eb5b6dc 100644
--- a/extensions/BUILD.gn
+++ b/extensions/BUILD.gn
@@ -133,6 +133,7 @@
     "test/extension_test_message_listener.h",
     "test/extension_test_notification_observer.cc",
     "test/extension_test_notification_observer.h",
+    "test/flakiness_test_util.h",
     "test/logging_timer.cc",
     "test/logging_timer.h",
     "test/permissions_manager_waiter.cc",
@@ -244,6 +245,7 @@
 
   sources = [
     "test/extensions_unittests_main.cc",
+    "test/flakiness_test_util_test.cc",
     "test/logging_timer_unittest.cc",
   ]
 
diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
index 0b27900..89d1208d 100644
--- a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
+++ b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
@@ -32,7 +32,6 @@
 #include "net/http/http_util.h"
 
 using base::CaseInsensitiveCompareASCII;
-using base::ListValue;
 using base::Value;
 
 namespace helpers = extension_web_request_api_helpers;
@@ -712,9 +711,9 @@
 
 namespace {
 
-// Reads strings stored in |value|, which is expected to be a ListValue, and
-// sets corresponding bits (see RequestStage) in |out_stages|. Returns true on
-// success, false otherwise.
+// Reads strings stored in |value|, which is expected to be a Value of type
+// LIST, and sets corresponding bits (see RequestStage) in |out_stages|.
+// Returns true on success, false otherwise.
 bool ParseListOfStages(const base::Value& value, int* out_stages) {
   if (!value.is_list())
     return false;
diff --git a/extensions/browser/api/feedback_private/feedback_private_api_chromeos_unittest.cc b/extensions/browser/api/feedback_private/feedback_private_api_chromeos_unittest.cc
index 73f0502..62b281b 100644
--- a/extensions/browser/api/feedback_private/feedback_private_api_chromeos_unittest.cc
+++ b/extensions/browser/api/feedback_private/feedback_private_api_chromeos_unittest.cc
@@ -33,7 +33,7 @@
 // list.
 template <typename T>
 std::string ParamsToJSON(const T& params) {
-  base::ListValue params_value;
+  base::Value::List params_value;
   params_value.Append(base::Value(params.ToValue()));
   std::string params_json_string;
   EXPECT_TRUE(base::JSONWriter::Write(params_value, &params_json_string));
diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc
index 6c54d60..1ca2fe3 100644
--- a/extensions/browser/api/runtime/runtime_api.cc
+++ b/extensions/browser/api/runtime/runtime_api.cc
@@ -496,7 +496,7 @@
 void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
     content::BrowserContext* context,
     const std::string& extension_id,
-    const base::DictionaryValue* manifest) {
+    const base::Value::Dict* manifest) {
   ExtensionSystem* system = ExtensionSystem::Get(context);
   if (!system)
     return;
diff --git a/extensions/browser/api/runtime/runtime_api.h b/extensions/browser/api/runtime/runtime_api.h
index 24af14e..839187a 100644
--- a/extensions/browser/api/runtime/runtime_api.h
+++ b/extensions/browser/api/runtime/runtime_api.h
@@ -12,6 +12,7 @@
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "base/values.h"
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/api/runtime/runtime_api_delegate.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
@@ -208,10 +209,9 @@
                                        bool chrome_updated);
 
   // Dispatches the onUpdateAvailable event to the given extension.
-  static void DispatchOnUpdateAvailableEvent(
-      content::BrowserContext* context,
-      const std::string& extension_id,
-      const base::DictionaryValue* manifest);
+  static void DispatchOnUpdateAvailableEvent(content::BrowserContext* context,
+                                             const std::string& extension_id,
+                                             const base::Value::Dict* manifest);
 
   // Dispatches the onBrowserUpdateAvailable event to all extensions.
   static void DispatchOnBrowserUpdateAvailableEvent(
diff --git a/extensions/browser/api/socket/udp_socket.cc b/extensions/browser/api/socket/udp_socket.cc
index 96d05596..68c1f6e 100644
--- a/extensions/browser/api/socket/udp_socket.cc
+++ b/extensions/browser/api/socket/udp_socket.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
 #include "base/lazy_instance.h"
 #include "base/ranges/algorithm.h"
 #include "extensions/browser/api/api_resource.h"
@@ -306,9 +307,7 @@
                                       const std::string& normalized_address,
                                       int result) {
   if (result == net::OK) {
-    auto find_result =
-        base::ranges::find(multicast_groups_, normalized_address);
-    multicast_groups_.erase(find_result);
+    base::Erase(multicast_groups_, normalized_address);
   }
 
   std::move(callback).Run(result);
diff --git a/extensions/browser/api/storage/settings_quota_unittest.cc b/extensions/browser/api/storage/settings_quota_unittest.cc
index 7dda6100..f1bb6f6 100644
--- a/extensions/browser/api/storage/settings_quota_unittest.cc
+++ b/extensions/browser/api/storage/settings_quota_unittest.cc
@@ -15,8 +15,6 @@
 #include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::DictionaryValue;
-
 namespace extensions {
 
 // To save typing value_store::ValueStore::DEFAULTS/IGNORE_QUOTA everywhere.
diff --git a/extensions/browser/api/web_request/upload_data_presenter.h b/extensions/browser/api/web_request/upload_data_presenter.h
index 9df9cfd..bcc20c0 100644
--- a/extensions/browser/api/web_request/upload_data_presenter.h
+++ b/extensions/browser/api/web_request/upload_data_presenter.h
@@ -94,12 +94,12 @@
 // This class inspects the contents of bytes elements. It uses the
 // parser classes inheriting from FormDataParser to parse the concatenated
 // content of such elements. If the parsing is successful, the parsed form is
-// returned as a DictionaryValue. For example, a form consisting of
+// returned as a Value of type DICT. For example, a form consisting of
 // <input name="check" type="checkbox" value="A" checked />
 // <input name="check" type="checkbox" value="B" checked />
 // <input name="text" type="text" value="abc" />
 // would be represented as {"check": ["A", "B"], "text": ["abc"]} (although as a
-// DictionaryValue, not as a JSON string).
+// Value, not as a JSON string).
 class ParsedDataPresenter : public UploadDataPresenter {
  public:
   explicit ParsedDataPresenter(const net::HttpRequestHeaders& request_headers);
diff --git a/extensions/browser/api/web_request/web_request_event_details.cc b/extensions/browser/api/web_request/web_request_event_details.cc
index 65569b8a..d53500b 100644
--- a/extensions/browser/api/web_request/web_request_event_details.cc
+++ b/extensions/browser/api/web_request/web_request_event_details.cc
@@ -83,7 +83,7 @@
     return;
   request_body_ = absl::nullopt;
   if (request->request_body_data) {
-    request_body_ = std::move(*request->request_body_data).TakeDict();
+    request_body_ = std::move(request->request_body_data);
     request->request_body_data.reset();
   }
 }
diff --git a/extensions/browser/api/web_request/web_request_info.cc b/extensions/browser/api/web_request/web_request_info.cc
index 3e201f3..1d75ab9af 100644
--- a/extensions/browser/api/web_request/web_request_info.cc
+++ b/extensions/browser/api/web_request/web_request_info.cc
@@ -27,6 +27,7 @@
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/url_loader.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace keys = extension_web_request_api_constants;
 
@@ -111,14 +112,14 @@
   return true;
 }
 
-std::unique_ptr<base::DictionaryValue> CreateRequestBodyData(
+absl::optional<base::Value::Dict> CreateRequestBodyData(
     const std::string& method,
     const net::HttpRequestHeaders& request_headers,
     const std::vector<std::unique_ptr<UploadDataSource>>& data_sources) {
   if (method != "POST" && method != "PUT")
-    return nullptr;
+    return absl::nullopt;
 
-  auto request_body_data = std::make_unique<base::DictionaryValue>();
+  base::Value::Dict request_body_data;
 
   // Get the data presenters, ordered by how specific they are.
   ParsedDataPresenter parsed_data_presenter(request_headers);
@@ -136,8 +137,7 @@
       for (auto& source : data_sources)
         source->FeedToPresenter(presenters[i]);
       if (presenters[i]->Succeeded()) {
-        request_body_data->GetDict().Set(kKeys[i],
-                                         presenters[i]->TakeResult().value());
+        request_body_data.Set(kKeys[i], presenters[i]->TakeResult().value());
         some_succeeded = true;
         break;
       }
@@ -145,8 +145,7 @@
   }
 
   if (!some_succeeded) {
-    request_body_data->SetStringKey(keys::kRequestBodyErrorKey,
-                                    "Unknown error.");
+    request_body_data.Set(keys::kRequestBodyErrorKey, "Unknown error.");
   }
 
   return request_body_data;
diff --git a/extensions/browser/api/web_request/web_request_info.h b/extensions/browser/api/web_request/web_request_info.h
index 6302b27d..5648447 100644
--- a/extensions/browser/api/web_request/web_request_info.h
+++ b/extensions/browser/api/web_request/web_request_info.h
@@ -69,7 +69,7 @@
   WebRequestResourceType web_request_type = WebRequestResourceType::OTHER;
   bool is_async = false;
   net::HttpRequestHeaders extra_request_headers;
-  std::unique_ptr<base::DictionaryValue> request_body_data;
+  absl::optional<base::Value::Dict> request_body_data;
   bool is_web_view = false;
   int web_view_instance_id = -1;
   int web_view_rules_registry_id = -1;
@@ -152,7 +152,7 @@
   // A dictionary of request body data matching the format expected by
   // WebRequest API consumers. This may have a "formData" key and/or a "raw"
   // key. See WebRequest API documentation for more details.
-  std::unique_ptr<base::DictionaryValue> request_body_data;
+  absl::optional<base::Value::Dict> request_body_data;
 
   // Indicates whether this request was initiated by a <webview> instance.
   const bool is_web_view;
diff --git a/extensions/browser/api/web_request/web_request_info_unittest.cc b/extensions/browser/api/web_request/web_request_info_unittest.cc
index 5bfac2f5..1c924467 100644
--- a/extensions/browser/api/web_request/web_request_info_unittest.cc
+++ b/extensions/browser/api/web_request/web_request_info_unittest.cc
@@ -35,7 +35,7 @@
                                                false, false, absl::nullopt,
                                                ukm::kInvalidSourceIdObj));
   ASSERT_TRUE(info.request_body_data);
-  base::Value* value = info.request_body_data->FindKey(
+  base::Value* value = info.request_body_data->Find(
       extension_web_request_api_constants::kRequestBodyRawKey);
   ASSERT_TRUE(value);
 
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index a89bf47..9e51affe 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -1433,13 +1433,11 @@
     const base::Value::Dict* old_manifest =
         extension_dict->FindDict(kPrefManifest);
     bool update_required =
-        !old_manifest ||
-        extension->manifest()->value()->GetDict() != *old_manifest;
+        !old_manifest || *extension->manifest()->value() != *old_manifest;
     if (update_required) {
-      UpdateExtensionPref(
-          extension->id(), kPrefManifest,
-          base::DictionaryValue::From(base::Value::ToUniquePtrValue(
-              extension->manifest()->value()->Clone())));
+      UpdateExtensionPref(extension->id(), kPrefManifest,
+                          std::make_unique<base::Value>(
+                              extension->manifest()->value()->Clone()));
     }
   }
 }
@@ -1494,10 +1492,8 @@
   // Make path absolute. Most (but not all) extension types have relative paths.
   if (!file_path.IsAbsolute())
     file_path = install_directory_.Append(file_path);
-  const base::DictionaryValue* manifest_dict =
-      (manifest && manifest->is_dict())
-          ? &base::Value::AsDictionaryValue(*manifest)
-          : nullptr;
+  const base::Value::Dict* manifest_dict =
+      (manifest && manifest->is_dict()) ? &manifest->GetDict() : nullptr;
   return std::make_unique<ExtensionInfo>(manifest_dict, extension_id, file_path,
                                          location);
 }
@@ -1917,10 +1913,10 @@
       // we could instead initialize the controlled preferences when the
       // extension is more finalized, but this also needs to happen sufficiently
       // before other subsystems are notified about the extension being loaded.
-      Manifest::Type type = info->extension_manifest
-                                ? Manifest::GetTypeFromManifestValue(
-                                      info->extension_manifest->GetDict())
-                                : Manifest::TYPE_UNKNOWN;
+      Manifest::Type type =
+          info->extension_manifest
+              ? Manifest::GetTypeFromManifestValue(*info->extension_manifest)
+              : Manifest::TYPE_UNKNOWN;
       bool is_theme = type == Manifest::TYPE_THEME;
       // Erase the entry if the extension won't be loaded.
       return !Manifest::ShouldAlwaysLoadExtension(info->extension_location,
@@ -2359,8 +2355,8 @@
   // We store prefs about LOAD extensions, but don't cache their manifest
   // since it may change on disk.
   if (!Manifest::IsUnpackedLocation(extension->location())) {
-    extension_dict->SetKey(kPrefManifest,
-                           extension->manifest()->value()->Clone());
+    extension_dict->SetKey(
+        kPrefManifest, base::Value(extension->manifest()->value()->Clone()));
   }
 
   // Only writes kPrefDoNotSync when it is not the default.
@@ -2585,7 +2581,7 @@
     // We only want to migrate extensions we can actually withhold permissions
     // from.
     Manifest::Type type =
-        Manifest::GetTypeFromManifestValue(info->extension_manifest->GetDict());
+        Manifest::GetTypeFromManifestValue(*info->extension_manifest);
     ManifestLocation location = info->extension_location;
     if (!util::CanWithholdPermissionsFromExtension(extension_id, type,
                                                    location))
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index dfa5f34..c39f195 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -75,9 +75,8 @@
   }
 
   return mojom::ExtensionLoadedParams::New(
-      static_cast<base::DictionaryValue&&>(
-          extension.manifest()->value()->Clone()),
-      extension.location(), extension.path(),
+      extension.manifest()->value()->Clone(), extension.location(),
+      extension.path(),
       CreatePermissionSet(permissions_data->active_permissions()),
       CreatePermissionSet(permissions_data->withheld_permissions()),
       std::move(tab_specific_permissions),
diff --git a/extensions/browser/sandboxed_unpacker.cc b/extensions/browser/sandboxed_unpacker.cc
index 3c0c308..182ba5bd 100644
--- a/extensions/browser/sandboxed_unpacker.cc
+++ b/extensions/browser/sandboxed_unpacker.cc
@@ -553,20 +553,17 @@
   }
   extension->AddInstallWarnings(std::move(warnings));
 
-  UnpackExtensionSucceeded(std::move(manifest.value()));
+  UnpackExtensionSucceeded(std::move(manifest.value()).TakeDict());
 }
 
-void SandboxedUnpacker::UnpackExtensionSucceeded(base::Value manifest) {
+void SandboxedUnpacker::UnpackExtensionSucceeded(base::Value::Dict manifest) {
   DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
 
-  absl::optional<base::Value> final_manifest(RewriteManifestFile(manifest));
+  absl::optional<base::Value::Dict> final_manifest(
+      RewriteManifestFile(manifest));
   if (!final_manifest)
     return;
 
-  std::unique_ptr<base::DictionaryValue> final_manifest_dict =
-      base::DictionaryValue::From(
-          base::Value::ToUniquePtrValue(std::move(final_manifest.value())));
-
   // Create an extension object that refers to the temporary location the
   // extension was unpacked to. We use this until the extension is finally
   // installed. For example, the install UI shows images from inside the
@@ -578,7 +575,7 @@
   // with std::u16string
   std::string utf8_error;
   if (!extension_l10n_util::LocalizeExtension(
-          extension_root_, &final_manifest_dict->GetDict(),
+          extension_root_, &final_manifest.value(),
           extension_l10n_util::GzippedMessagesPermission::kDisallow,
           &utf8_error)) {
     ReportFailure(
@@ -589,7 +586,7 @@
   }
 
   extension_ =
-      Extension::Create(extension_root_, location_, *final_manifest_dict,
+      Extension::Create(extension_root_, location_, final_manifest.value(),
                         Extension::REQUIRE_KEY | creation_flags_, &utf8_error);
 
   if (!extension_.get()) {
@@ -1026,8 +1023,7 @@
   // Client takes ownership of temporary directory, manifest, and extension.
   client_->OnUnpackSuccess(
       temp_dir_.Take(), extension_root_,
-      base::DictionaryValue::From(
-          base::Value::ToUniquePtrValue(std::move(manifest_.value()))),
+      std::make_unique<base::Value::Dict>(std::move(manifest_.value())),
       extension_.get(), install_icon_, std::move(ruleset_install_prefs_));
 
   // Interestingly, the C++ standard doesn't guarantee that a moved-from vector
@@ -1039,24 +1035,24 @@
   Cleanup();
 }
 
-absl::optional<base::Value> SandboxedUnpacker::RewriteManifestFile(
-    const base::Value& manifest) {
+absl::optional<base::Value::Dict> SandboxedUnpacker::RewriteManifestFile(
+    const base::Value::Dict& manifest) {
   constexpr int64_t kMaxFingerprintSize = 1024;
 
   // Add the public key extracted earlier to the parsed manifest and overwrite
   // the original manifest. We do this to ensure the manifest doesn't contain an
   // exploitable bug that could be used to compromise the browser.
   DCHECK(!public_key_.empty());
-  base::Value final_manifest = manifest.Clone();
-  final_manifest.SetStringKey(manifest_keys::kPublicKey, public_key_);
+  base::Value::Dict final_manifest = manifest.Clone();
+  final_manifest.Set(manifest_keys::kPublicKey, public_key_);
 
   {
     std::string differential_fingerprint;
     if (base::ReadFileToStringWithMaxSize(
             extension_root_.Append(kDifferentialFingerprintFilename),
             &differential_fingerprint, kMaxFingerprintSize)) {
-      final_manifest.SetStringKey(manifest_keys::kDifferentialFingerprint,
-                                  std::move(differential_fingerprint));
+      final_manifest.Set(manifest_keys::kDifferentialFingerprint,
+                         std::move(differential_fingerprint));
     }
   }
 
diff --git a/extensions/browser/sandboxed_unpacker.h b/extensions/browser/sandboxed_unpacker.h
index 24de36a..013cf66 100644
--- a/extensions/browser/sandboxed_unpacker.h
+++ b/extensions/browser/sandboxed_unpacker.h
@@ -90,7 +90,7 @@
   virtual void OnUnpackSuccess(
       const base::FilePath& temp_dir,
       const base::FilePath& extension_root,
-      std::unique_ptr<base::DictionaryValue> original_manifest,
+      std::unique_ptr<base::Value::Dict> original_manifest,
       const Extension* extension,
       const SkBitmap& install_icon,
       declarative_net_request::RulesetInstallPrefs ruleset_install_prefs) = 0;
@@ -201,7 +201,7 @@
   void Unpack(const base::FilePath& directory);
   void ReadManifestDone(absl::optional<base::Value> manifest,
                         const absl::optional<std::string>& error);
-  void UnpackExtensionSucceeded(base::Value manifest);
+  void UnpackExtensionSucceeded(base::Value::Dict manifest);
 
   // Helper which calls ReportFailure.
   void ReportUnpackExtensionFailed(base::StringPiece error);
@@ -230,7 +230,8 @@
 
   // Overwrites original manifest with safe result from utility process.
   // Returns nullopt on error.
-  absl::optional<base::Value> RewriteManifestFile(const base::Value& manifest);
+  absl::optional<base::Value::Dict> RewriteManifestFile(
+      const base::Value::Dict& manifest);
 
   // Cleans up temp directory artifacts.
   void Cleanup();
@@ -279,7 +280,7 @@
   // Parsed original manifest of the extension. Set after unpacking the
   // extension and working with its manifest, so after UnpackExtensionSucceeded
   // is called.
-  absl::optional<base::Value> manifest_;
+  absl::optional<base::Value::Dict> manifest_;
 
   // Install prefs needed for the Declarative Net Request API.
   declarative_net_request::RulesetInstallPrefs ruleset_install_prefs_;
diff --git a/extensions/browser/sandboxed_unpacker_unittest.cc b/extensions/browser/sandboxed_unpacker_unittest.cc
index 9a1a2a1..6dc69c0 100644
--- a/extensions/browser/sandboxed_unpacker_unittest.cc
+++ b/extensions/browser/sandboxed_unpacker_unittest.cc
@@ -135,7 +135,7 @@
 
   void OnUnpackSuccess(const base::FilePath& temp_dir,
                        const base::FilePath& extension_root,
-                       std::unique_ptr<base::DictionaryValue> original_manifest,
+                       std::unique_ptr<base::Value::Dict> original_manifest,
                        const Extension* extension,
                        const SkBitmap& install_icon,
                        declarative_net_request::RulesetInstallPrefs
@@ -302,7 +302,8 @@
     sandboxed_unpacker_->extension_root_ = path;
   }
 
-  absl::optional<base::Value> RewriteManifestFile(const base::Value& manifest) {
+  absl::optional<base::Value::Dict> RewriteManifestFile(
+      const base::Value::Dict& manifest) {
     return sandboxed_unpacker_->RewriteManifestFile(manifest);
   }
 
@@ -486,12 +487,12 @@
                       FILE_PATH_LITERAL("manifest.fingerprint")),
                   fingerprint.c_str(),
                   base::checked_cast<int>(fingerprint.size()));
-  absl::optional<base::Value> manifest(RewriteManifestFile(
-      *DictionaryBuilder().Set(kVersionStr, kTestVersion).Build()));
-  auto* key = manifest->FindStringKey("key");
-  auto* version = manifest->FindStringKey(kVersionStr);
+  absl::optional<base::Value::Dict> manifest(RewriteManifestFile(
+      DictionaryBuilder().Set(kVersionStr, kTestVersion).BuildDict()));
+  auto* key = manifest->FindString("key");
+  auto* version = manifest->FindString(kVersionStr);
   auto* differential_fingerprint =
-      manifest->FindStringKey("differential_fingerprint");
+      manifest->FindString("differential_fingerprint");
   ASSERT_NE(nullptr, key);
   ASSERT_NE(nullptr, version);
   ASSERT_NE(nullptr, differential_fingerprint);
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index 548d98a..74299556 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -1465,6 +1465,9 @@
     // Suspend any media playing within this tree.
     static void suspendMedia();
 
+    // Simulates long click on node.
+    static void longClick();
+
     // Scrolls this scrollable container backward.
     static void scrollBackward(optional PerformActionCallback callback);
 
diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc
index 49557d8..4210813 100644
--- a/extensions/common/extension.cc
+++ b/extensions/common/extension.cc
@@ -24,7 +24,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/timer/elapsed_timer.h"
-#include "base/values.h"
 #include "base/version.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/common/url_constants.h"
@@ -858,14 +857,13 @@
   return true;
 }
 
-ExtensionInfo::ExtensionInfo(const base::DictionaryValue* manifest,
+ExtensionInfo::ExtensionInfo(const base::Value::Dict* manifest,
                              const std::string& id,
                              const base::FilePath& path,
                              ManifestLocation location)
     : extension_id(id), extension_path(path), extension_location(location) {
   if (manifest)
-    extension_manifest = base::DictionaryValue::From(
-        base::Value::ToUniquePtrValue(manifest->Clone()));
+    extension_manifest = std::make_unique<base::Value::Dict>(manifest->Clone());
 }
 
 ExtensionInfo::~ExtensionInfo() {}
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index dde6bf6..0862216d 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -15,6 +15,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
+#include "base/values.h"
 #include "base/version.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/extension_guid.h"
@@ -33,7 +34,6 @@
 
 namespace base {
 class DictAdapterForMigration;
-class DictionaryValue;
 }
 
 namespace extensions {
@@ -371,8 +371,8 @@
   // Initialize the extension from a parsed manifest.
   // TODO(aa): Rename to just Init()? There's no Value here anymore.
   // TODO(aa): It is really weird the way this class essentially contains a copy
-  // of the underlying DictionaryValue in its members. We should decide to
-  // either wrap the DictionaryValue and go with that only, or we should parse
+  // of the underlying base::Value::Dict in its members. We should decide to
+  // either wrap the base::Value::Dict and go with that only, or we should parse
   // into strong types and discard the value. But doing both is bad.
   bool InitFromValue(int flags, std::u16string* error);
 
@@ -497,7 +497,7 @@
 
 // Handy struct to pass core extension info around.
 struct ExtensionInfo {
-  ExtensionInfo(const base::DictionaryValue* manifest,
+  ExtensionInfo(const base::Value::Dict* manifest,
                 const ExtensionId& id,
                 const base::FilePath& path,
                 mojom::ManifestLocation location);
@@ -507,7 +507,7 @@
 
   // Note: This may be null (e.g. for unpacked extensions retrieved from the
   // Preferences file).
-  std::unique_ptr<base::DictionaryValue> extension_manifest;
+  std::unique_ptr<base::Value::Dict> extension_manifest;
 
   ExtensionId extension_id;
   base::FilePath extension_path;
diff --git a/extensions/common/extension_builder.cc b/extensions/common/extension_builder.cc
index 202d3ec..9207b4d5 100644
--- a/extensions/common/extension_builder.cc
+++ b/extensions/common/extension_builder.cc
@@ -158,8 +158,8 @@
 
 base::Value ExtensionBuilder::BuildManifest() {
   CHECK(manifest_data_ || manifest_value_);
-  return manifest_data_ ? base::Value(manifest_data_->GetValue())
-                        : manifest_value_->Clone();
+  return base::Value(manifest_data_ ? manifest_data_->GetValue()
+                                    : manifest_value_->Clone());
 }
 
 ExtensionBuilder& ExtensionBuilder::AddPermission(
@@ -217,7 +217,7 @@
   CHECK(parsed.has_value())
       << "Failed to parse json for extension '" << manifest_data_->name
       << "':" << parsed.error().message;
-  return MergeManifest(*parsed);
+  return MergeManifest(std::move(*parsed).TakeDict());
 }
 
 ExtensionBuilder& ExtensionBuilder::SetPath(const base::FilePath& path) {
@@ -234,7 +234,7 @@
 ExtensionBuilder& ExtensionBuilder::SetManifest(
     std::unique_ptr<base::DictionaryValue> manifest) {
   CHECK(!manifest_data_);
-  manifest_value_ = std::move(manifest);
+  manifest_value_ = std::move(*manifest).TakeDict();
   return *this;
 }
 
@@ -243,19 +243,18 @@
       std::make_unique<base::Value>(std::move(manifest))));
 }
 
-ExtensionBuilder& ExtensionBuilder::MergeManifest(const base::Value& to_merge) {
-  CHECK(to_merge.is_dict());
+ExtensionBuilder& ExtensionBuilder::MergeManifest(base::Value::Dict to_merge) {
   if (manifest_data_) {
-    manifest_data_->get_extra().Merge(to_merge.GetDict().Clone());
+    manifest_data_->get_extra().Merge(std::move(to_merge));
   } else {
-    manifest_value_->MergeDictionary(&to_merge);
+    manifest_value_->Merge(std::move(to_merge));
   }
   return *this;
 }
 
 ExtensionBuilder& ExtensionBuilder::MergeManifest(
     std::unique_ptr<base::DictionaryValue> manifest) {
-  return MergeManifest(*manifest);
+  return MergeManifest(std::move(*manifest).TakeDict());
 }
 
 ExtensionBuilder& ExtensionBuilder::AddFlags(int init_from_value_flags) {
diff --git a/extensions/common/extension_builder.h b/extensions/common/extension_builder.h
index b2959d6..8df1e75 100644
--- a/extensions/common/extension_builder.h
+++ b/extensions/common/extension_builder.h
@@ -18,6 +18,7 @@
 #include "extensions/common/manifest.h"
 #include "extensions/common/mojom/manifest.mojom-shared.h"
 #include "extensions/common/value_builder.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace extensions {
 class Extension;
@@ -170,7 +171,7 @@
 
   // Merge another manifest into the current manifest, with new keys taking
   // precedence.
-  ExtensionBuilder& MergeManifest(const base::Value& manifest);
+  ExtensionBuilder& MergeManifest(base::Value::Dict manifest);
   ExtensionBuilder& MergeManifest(
       std::unique_ptr<base::DictionaryValue> manifest);
 
@@ -192,7 +193,7 @@
   // manifest which will be used to construct it, or the dictionary itself. Only
   // one will be present.
   std::unique_ptr<ManifestData> manifest_data_;
-  std::unique_ptr<base::DictionaryValue> manifest_value_;
+  absl::optional<base::Value::Dict> manifest_value_;
 
   base::FilePath path_;
   mojom::ManifestLocation location_;
diff --git a/extensions/common/features/manifest_feature.cc b/extensions/common/features/manifest_feature.cc
index 152082a..e18a3d4 100644
--- a/extensions/common/features/manifest_feature.cc
+++ b/extensions/common/features/manifest_feature.cc
@@ -29,7 +29,7 @@
 
   // We know we can skip manifest()->GetKey() here because we just did the same
   // validation it would do above.
-  if (extension && !extension->manifest()->value()->FindKey(name()))
+  if (extension && !extension->manifest()->value()->contains(name()))
     return CreateAvailability(NOT_PRESENT, extension->GetType());
 
   return CreateAvailability(IS_AVAILABLE);
diff --git a/extensions/common/manifest.cc b/extensions/common/manifest.cc
index 230b883..12d6532 100644
--- a/extensions/common/manifest.cc
+++ b/extensions/common/manifest.cc
@@ -110,7 +110,7 @@
  public:
   // Filters `manifest.values()` removing any unavailable keys.
   static base::Value::Dict Filter(const Manifest& manifest) {
-    return FilterInternal(manifest, manifest.value()->GetDict(), "");
+    return FilterInternal(manifest, *manifest.value(), "");
   }
 
  private:
@@ -269,8 +269,8 @@
       hashed_id_(HashedExtensionId(extension_id_)),
       location_(location),
       value_(std::move(value)),
-      type_(GetTypeFromManifestValue(value_.GetDict(), for_login_screen)),
-      manifest_version_(GetManifestVersion(value_.GetDict(), type_)) {
+      type_(GetTypeFromManifestValue(value_, for_login_screen)),
+      manifest_version_(GetManifestVersion(value_, type_)) {
   DCHECK(!extension_id_.empty());
 
   available_values_ = base::Value(AvailableValuesFilter::Filter(*this));
@@ -290,7 +290,7 @@
   const FeatureProvider* manifest_feature_provider =
       FeatureProvider::GetManifestFeatures();
   for (const auto& map_entry : manifest_feature_provider->GetAllFeatures()) {
-    if (!value_.GetDict().FindByDottedPath(map_entry.first))
+    if (!value_.FindByDottedPath(map_entry.first))
       continue;
 
     Feature::Availability result = map_entry.second->IsAvailableToManifest(
@@ -300,7 +300,7 @@
   }
 
   // Also generate warnings for keys that are not features.
-  for (const auto item : value_.GetDict()) {
+  for (const auto item : value_) {
     if (!manifest_feature_provider->GetFeature(item.first)) {
       warnings->push_back(InstallWarning(
           ErrorUtils::FormatErrorMessage(
@@ -310,8 +310,7 @@
   }
 
   if (IsUnpackedLocation(location_) &&
-      value_.GetDict().FindByDottedPath(
-          manifest_keys::kDifferentialFingerprint)) {
+      value_.FindByDottedPath(manifest_keys::kDifferentialFingerprint)) {
     warnings->push_back(
         InstallWarning(manifest_errors::kHasDifferentialFingerprint,
                        manifest_keys::kDifferentialFingerprint));
diff --git a/extensions/common/manifest.h b/extensions/common/manifest.h
index 2a780ba..8406f34 100644
--- a/extensions/common/manifest.h
+++ b/extensions/common/manifest.h
@@ -185,11 +185,9 @@
   // Returns true if this equals the |other| manifest.
   bool EqualsForTesting(const Manifest& other) const;
 
-  // Gets the underlying DictionaryValue representing the manifest.
+  // Gets the underlying base::Value::Dict representing the manifest.
   // Note: only use this when you KNOW you don't need the validation.
-  const base::DictionaryValue* value() const {
-    return &base::Value::AsDictionaryValue(value_);
-  }
+  const base::Value::Dict* value() const { return &value_; }
 
   // Gets the underlying DictionaryValue representing the manifest with all
   // unavailable manifest keys removed.
@@ -222,9 +220,7 @@
   const mojom::ManifestLocation location_;
 
   // The underlying dictionary representation of the manifest.
-  // TODO(https://crbug.com/1366865): Make base::Value::Dict when callers of
-  // `value()` are migrated.
-  const base::Value value_;
+  const base::Value::Dict value_;
 
   // Same as |value_| but comprises only of keys available to this manifest.
   // TODO(https://crbug.com/1366865): Make base::Value::Dict when callers of
diff --git a/extensions/common/manifest_handlers/csp_info.cc b/extensions/common/manifest_handlers/csp_info.cc
index cf78a5d..7c6e5e2 100644
--- a/extensions/common/manifest_handlers/csp_info.cc
+++ b/extensions/common/manifest_handlers/csp_info.cc
@@ -44,6 +44,11 @@
 // The minimum CSP to be used in order to prevent remote scripts.
 static const char kMinimumMV3CSP[] =
     "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';";
+// For unpacked extensions, we additionally allow the use of localhost files to
+// aid in rapid local development.
+static const char kMinimumUnpackedMV3CSP[] =
+    "script-src 'self' 'wasm-unsafe-eval' http://localhost:* "
+    "http://127.0.0.1:*; object-src 'self';";
 
 #define PLATFORM_APP_LOCAL_CSP_SOURCES "'self' blob: filesystem: data:"
 
@@ -107,6 +112,19 @@
   return kDefaultContentSecurityPolicy;
 }
 
+// Returns the minimum CSP to apply for the given MV3 extension.
+const std::string* GetMinimumMV3CSPForExtension(const Extension& extension) {
+  DCHECK_GE(extension.manifest_version(), 3);
+
+  static const base::NoDestructor<std::string> default_csp(kMinimumMV3CSP);
+  static const base::NoDestructor<std::string> default_unpacked_csp(
+      kMinimumUnpackedMV3CSP);
+
+  return Manifest::IsUnpackedLocation(extension.location())
+             ? default_unpacked_csp.get()
+             : default_csp.get();
+}
+
 }  // namespace
 
 CSPInfo::CSPInfo(std::string extension_pages_csp)
@@ -143,8 +161,7 @@
   // cause the parsed CSP to not be as strong as the default one. For example,
   // see crbug.com/1042963.
 
-  static const base::NoDestructor<std::string> default_csp(kMinimumMV3CSP);
-  return default_csp.get();
+  return GetMinimumMV3CSPForExtension(extension);
 }
 
 // static
@@ -152,9 +169,7 @@
   if (extension.manifest_version() >= 3) {
     // The isolated world will use its own CSP which blocks remotely hosted
     // code.
-    static const base::NoDestructor<std::string> default_isolated_world_csp(
-        kMinimumMV3CSP);
-    return default_isolated_world_csp.get();
+    return GetMinimumMV3CSPForExtension(extension);
   }
 
   Manifest::Type type = extension.GetType();
@@ -191,6 +206,16 @@
 
 CSPHandler::~CSPHandler() = default;
 
+// static
+const char* CSPHandler::GetMinimumMV3CSPForTesting() {
+  return kMinimumMV3CSP;
+}
+
+// static
+const char* CSPHandler::GetMinimumUnpackedMV3CSPForTesting() {
+  return kMinimumUnpackedMV3CSP;
+}
+
 bool CSPHandler::Parse(Extension* extension, std::u16string* error) {
   const char* key = extension->GetType() == Manifest::TYPE_PLATFORM_APP
                         ? keys::kPlatformAppContentSecurityPolicy
diff --git a/extensions/common/manifest_handlers/csp_info.h b/extensions/common/manifest_handlers/csp_info.h
index b2d904a..ba6607d8 100644
--- a/extensions/common/manifest_handlers/csp_info.h
+++ b/extensions/common/manifest_handlers/csp_info.h
@@ -71,6 +71,10 @@
   // ManifestHandler override:
   bool Parse(Extension* extension, std::u16string* error) override;
 
+  // Returns the minimum CSP to use in MV3 extensions. Only exposed for testing.
+  static const char* GetMinimumMV3CSPForTesting();
+  static const char* GetMinimumUnpackedMV3CSPForTesting();
+
  private:
   // Parses the "content_security_policy" dictionary in the manifest.
   bool ParseCSPDictionary(Extension* extension, std::u16string* error);
diff --git a/extensions/common/manifest_handlers/csp_info_unittest.cc b/extensions/common/manifest_handlers/csp_info_unittest.cc
index 5b1877b..10289f6 100644
--- a/extensions/common/manifest_handlers/csp_info_unittest.cc
+++ b/extensions/common/manifest_handlers/csp_info_unittest.cc
@@ -31,8 +31,7 @@
     "script-src 'self' blob: filesystem:; "
     "object-src 'self' blob: filesystem:;";
 const char kDefaultSecureCSP[] = "script-src 'self'; object-src 'self';";
-const char kMinimumMV3CSP[] =
-    "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';";
+
 }  // namespace
 
 using CSPInfoUnitTest = ManifestTest;
@@ -237,20 +236,51 @@
   const char* default_case_filenames[] = {"csp_dictionary_empty_v3.json",
                                           "csp_dictionary_missing_v3.json"};
 
+  // First, run through with loading the extensions as packed extensions.
   for (const char* filename : default_case_filenames) {
     SCOPED_TRACE(filename);
-    scoped_refptr<Extension> extension = LoadAndExpectSuccess(filename);
+    scoped_refptr<Extension> extension =
+        LoadAndExpectSuccess(filename, mojom::ManifestLocation::kInternal);
     ASSERT_TRUE(extension);
 
     const std::string* isolated_world_csp =
         CSPInfo::GetIsolatedWorldCSP(*extension);
     ASSERT_TRUE(isolated_world_csp);
-    EXPECT_EQ(kMinimumMV3CSP, *isolated_world_csp);
+    EXPECT_EQ(CSPHandler::GetMinimumMV3CSPForTesting(), *isolated_world_csp);
 
     EXPECT_EQ(kDefaultSandboxedPageCSP,
               CSPInfo::GetSandboxContentSecurityPolicy(extension.get()));
     EXPECT_EQ(kDefaultSecureCSP,
               CSPInfo::GetExtensionPagesCSP(extension.get()));
+
+    EXPECT_EQ(
+        CSPHandler::GetMinimumMV3CSPForTesting(),
+        *CSPInfo::GetMinimumCSPToAppend(*extension, "not_sandboxed.html"));
+  }
+
+  // Repeat the test, loading the extensions as unpacked extensions.
+  // The minimum CSP we append (and thus the isolated world CSP) should be
+  // different, while the other CSPs should remain the same.
+  for (const char* filename : default_case_filenames) {
+    SCOPED_TRACE(filename);
+    scoped_refptr<Extension> extension =
+        LoadAndExpectSuccess(filename, mojom::ManifestLocation::kUnpacked);
+    ASSERT_TRUE(extension);
+
+    const std::string* isolated_world_csp =
+        CSPInfo::GetIsolatedWorldCSP(*extension);
+    ASSERT_TRUE(isolated_world_csp);
+    EXPECT_EQ(CSPHandler::GetMinimumUnpackedMV3CSPForTesting(),
+              *isolated_world_csp);
+
+    EXPECT_EQ(kDefaultSandboxedPageCSP,
+              CSPInfo::GetSandboxContentSecurityPolicy(extension.get()));
+    EXPECT_EQ(kDefaultSecureCSP,
+              CSPInfo::GetExtensionPagesCSP(extension.get()));
+
+    EXPECT_EQ(
+        CSPHandler::GetMinimumUnpackedMV3CSPForTesting(),
+        *CSPInfo::GetMinimumCSPToAppend(*extension, "not_sandboxed.html"));
   }
 }
 
diff --git a/extensions/common/mojom/renderer.mojom b/extensions/common/mojom/renderer.mojom
index 554224b4..65e86110 100644
--- a/extensions/common/mojom/renderer.mojom
+++ b/extensions/common/mojom/renderer.mojom
@@ -19,7 +19,7 @@
 // Mojo struct to be used as argument on the LoadExtension method.
 struct ExtensionLoadedParams {
   // The subset of the extension manifest data we send to renderers.
-  mojo_base.mojom.Value manifest;
+  mojo_base.mojom.DictionaryValue manifest;
 
   // The location the extension was installed from. This is used in the renderer
   // only to generate the extension ID for extensions that are loaded unpacked.
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 0768531..9b72981 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -222,8 +222,7 @@
   // normal case, and because in tests, extensions may not have paths or keys,
   // but it's important to retain the same id.
   scoped_refptr<Extension> extension =
-      Extension::Create(params->path, params->location,
-                        base::Value::AsDictionaryValue(params->manifest),
+      Extension::Create(params->path, params->location, params->manifest,
                         params->creation_flags, params->id, error);
 
   if (!extension.get())
diff --git a/extensions/test/flakiness_test_util.h b/extensions/test/flakiness_test_util.h
new file mode 100644
index 0000000..5bddf4f
--- /dev/null
+++ b/extensions/test/flakiness_test_util.h
@@ -0,0 +1,85 @@
+// 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 EXTENSIONS_TEST_FLAKINESS_TEST_UTIL_H_
+#define EXTENSIONS_TEST_FLAKINESS_TEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+// NO *LANDED* CODE SHOULD EVER USE THESE. They are purely for development
+// aid.
+//
+// A set of helper macros to quickly instantiate a test suite for a test to
+// run hundreds of times in order to help flush out flakes.
+//
+// Given a test suite, MyTestSuite, these macros work together to generate a
+// parameterized test that will run a given number of times, as follows:
+//
+//   // Note: Prepend "AAAA_" so that the test always comes first.
+//   class AAAA_Deflake_MyTestSuite : public MyTestSuite,
+//                                    public testing::WithParamInterface<int> {
+//   };
+//
+//   INSTANTIATE_TEST_SUITE_P(
+//       AAAA_Flaky, AAAA_Deflake_MyTestSuite,
+//       testing::Range(0, 100));  // Or another provided amount.
+//
+// Assume there is the following flaky test that you want to exercise:
+//
+//   class MyTestSuite { ... };
+//
+//   // Disabled for flakiness.
+//   IN_PROC_BROWSER_TEST_F(MyTestSuite, DISABLED_MyTestCase) {
+//     ...
+//   }
+//
+// These macros can be used as follows:
+//
+//   class MyTestSuite { ... };
+//
+//   INSTANTIATE_FLAKINESS_TEST_100(MyTestSuite);
+//   IN_PROC_BROWSER_TEST_P(FLAKINESS_TEST_NAME(MyTestSuite), MyTestCase) {
+//     ...
+//   }
+//
+// The generated test will run 100 times.
+
+namespace extensions {
+
+// Produces the flakiness test name, e.g. "AAAA_Deflake_MyTestSuite".
+#define FLAKINESS_TEST_NAME(TestName) AAAA_Deflake_##TestName
+
+namespace internal {
+
+// Defines the subclass for the parameterized test suite.
+#define DEFINE_FLAKINESS_TEST(TestName) \
+  class FLAKINESS_TEST_NAME(TestName)   \
+      : public TestName, public testing::WithParamInterface<int> {}
+
+// Instantiates the test suite. This is broken into multiple helpers in order
+// to work around C's, uh, interesting macro evaluation patterns.
+#define INSTANTIATE_FLAKINESS_TEST_IMPL(IterationCount, FlakinessTestName) \
+  INSTANTIATE_TEST_SUITE_P(AAAA_Flaky, FlakinessTestName,                  \
+                           testing::Range(0, IterationCount))
+
+#define INSTANTIATE_FLAKINESS_TEST_HELPER(IterationCount, ...) \
+  INSTANTIATE_FLAKINESS_TEST_IMPL(IterationCount, ##__VA_ARGS__)
+
+}  // namespace internal
+
+// Defines and instantiates a test suite that will run `IterationCount` times.
+#define INSTANTIATE_FLAKINESS_TEST(TestName, IterationCount) \
+  DEFINE_FLAKINESS_TEST(TestName);                           \
+  INSTANTIATE_FLAKINESS_TEST_HELPER(IterationCount,          \
+                                    FLAKINESS_TEST_NAME(TestName))
+
+#define INSTANTIATE_FLAKINESS_TEST_100(TestName) \
+  INSTANTIATE_FLAKINESS_TEST(TestName, 100)
+
+#define INSTANTIATE_FLAKINESS_TEST_1000(TestName) \
+  INSTANTIATE_FLAKINESS_TEST(TestName, 1000)
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_TEST_FLAKINESS_TEST_UTIL_H_
diff --git a/extensions/test/flakiness_test_util_test.cc b/extensions/test/flakiness_test_util_test.cc
new file mode 100644
index 0000000..54ebc48
--- /dev/null
+++ b/extensions/test/flakiness_test_util_test.cc
@@ -0,0 +1,29 @@
+// 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 "extensions/test/flakiness_test_util.h"
+
+// Create a "real" subclass just to more accurately mimic what most test
+// suites would do.
+class FlakinessTestUtilTest : public testing::Test {
+ public:
+  FlakinessTestUtilTest() = default;
+  ~FlakinessTestUtilTest() override = default;
+};
+
+// Since we're not really exercising flakiness (just compilation), we only
+// instantiate a single test run.
+INSTANTIATE_FLAKINESS_TEST(FlakinessTestUtilTest, 1);
+
+// This test exercises the output from the INSTANTIATE_FLAKINESS_TEST macro.
+TEST_P(FLAKINESS_TEST_NAME(FlakinessTestUtilTest), FlakinessTest) {
+  const ::testing::TestInfo* test_info =
+      ::testing::UnitTest::GetInstance()->current_test_info();
+  // Test the generated test suite name.
+  EXPECT_STREQ("AAAA_Flaky/AAAA_Deflake_FlakinessTestUtilTest",
+               test_info->test_suite_name());
+  // Test the test case name. Since we only instantiate with a range of 1, there
+  // should only be a single test (/0).
+  EXPECT_STREQ("FlakinessTest/0", test_info->name());
+}
diff --git a/infra/config/generated/builders/ci/lacros-amd64-generic-rel-skylab/properties.json b/infra/config/generated/builders/ci/lacros-amd64-generic-rel-skylab/properties.json
index 9d57c5fb..035f282 100644
--- a/infra/config/generated/builders/ci/lacros-amd64-generic-rel-skylab/properties.json
+++ b/infra/config/generated/builders/ci/lacros-amd64-generic-rel-skylab/properties.json
@@ -21,6 +21,9 @@
                 "config": "chromium",
                 "target_arch": "intel",
                 "target_bits": 64,
+                "target_cros_boards": [
+                  "amd64-generic"
+                ],
                 "target_platform": "chromeos"
               },
               "legacy_gclient_config": {
diff --git a/infra/config/generated/builders/try/lacros-amd64-generic-rel-skylab/properties.json b/infra/config/generated/builders/try/lacros-amd64-generic-rel-skylab/properties.json
index 15a447b0..f601946 100644
--- a/infra/config/generated/builders/try/lacros-amd64-generic-rel-skylab/properties.json
+++ b/infra/config/generated/builders/try/lacros-amd64-generic-rel-skylab/properties.json
@@ -21,6 +21,9 @@
                 "config": "chromium",
                 "target_arch": "intel",
                 "target_bits": 64,
+                "target_cros_boards": [
+                  "amd64-generic"
+                ],
                 "target_platform": "chromeos"
               },
               "legacy_gclient_config": {
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 37cb4b9b..de6f717 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -35,8 +35,6 @@
 
 * [chromium_presubmit](https://ci.chromium.org/p/chromium/builders/try/chromium_presubmit) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""chromium_presubmit""))
 
-* [fuchsia-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/fuchsia-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-arm64-rel""))
-
 * [fuchsia-binary-size](https://ci.chromium.org/p/chromium/builders/try/fuchsia-binary-size) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-binary-size""))
 
 * [fuchsia-x64-cast-receiver-rel](https://ci.chromium.org/p/chromium/builders/try/fuchsia-x64-cast-receiver-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-x64-cast-receiver-rel""))
diff --git a/infra/config/generated/cq-usage/default.cfg b/infra/config/generated/cq-usage/default.cfg
index 306c5c10..783c13a 100644
--- a/infra/config/generated/cq-usage/default.cfg
+++ b/infra/config/generated/cq-usage/default.cfg
@@ -44,9 +44,6 @@
         disable_reuse: true
       }
       builders {
-        name: "chromium/try/fuchsia-arm64-rel"
-      }
-      builders {
         name: "chromium/try/fuchsia-binary-size"
       }
       builders {
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg
index 6406a93..f00549f 100644
--- a/infra/config/generated/cq-usage/full.cfg
+++ b/infra/config/generated/cq-usage/full.cfg
@@ -1075,26 +1075,6 @@
         }
       }
       builders {
-        name: "chromium/try/fuchsia-arm64-rel"
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "docs/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/generated/builders/try/fuchsia-arm64-rel/.+"
-        }
-      }
-      builders {
         name: "chromium/try/fuchsia-binary-size"
         location_filters {
           gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index ac79839..380378b5 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1690,23 +1690,7 @@
       }
       builders {
         name: "chromium/try/fuchsia-arm64-rel"
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "docs/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/generated/builders/try/fuchsia-arm64-rel/.+"
-        }
+        includable_only: true
       }
       builders {
         name: "chromium/try/fuchsia-arm64-rel-compilator"
diff --git a/infra/config/generated/luci/project.cfg b/infra/config/generated/luci/project.cfg
index 3a9d35b..1ff9f32 100644
--- a/infra/config/generated/luci/project.cfg
+++ b/infra/config/generated/luci/project.cfg
@@ -7,11 +7,10 @@
 name: "chromium"
 access: "group:all"
 lucicfg {
-  version: "1.33.7"
+  version: "1.35.2"
   package_dir: "../.."
   config_dir: "generated/luci"
   entry_point: "main.star"
-  experiments: "crbug.com/1171945"
   experiments: "crbug.com/1182002"
   experiments: "crbug.com/1347252"
 }
diff --git a/infra/config/subprojects/chromium/ci/chromium.chromiumos.star b/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
index dfc6509..0f06d9c 100644
--- a/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
+++ b/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
@@ -550,6 +550,9 @@
             build_config = builder_config.build_config.RELEASE,
             target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
+            target_cros_boards = [
+                "amd64-generic",
+            ],
             target_platform = builder_config.target_platform.CHROMEOS,
         ),
         skylab_upload_location = builder_config.skylab_upload_location(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
index c474d52..e901c3c 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
@@ -142,6 +142,9 @@
             build_config = builder_config.build_config.RELEASE,
             target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
+            target_cros_boards = [
+                "amd64-generic",
+            ],
             target_platform = builder_config.target_platform.CHROMEOS,
         ),
         # TODO(https://crbug.com/1399919): change skylab_upload_location
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 7e25d596..c7e7f08 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -64,7 +64,6 @@
     compilator = "fuchsia-arm64-rel-compilator",
     branch_selector = branches.FUCHSIA_LTS_MILESTONE,
     main_list_view = "try",
-    tryjob = try_.job(),
     mirrors = [
         "ci/fuchsia-arm64-rel",
     ],
diff --git a/ios/chrome/browser/commerce/BUILD.gn b/ios/chrome/browser/commerce/BUILD.gn
index 75f9428..e12bba14 100644
--- a/ios/chrome/browser/commerce/BUILD.gn
+++ b/ios/chrome/browser/commerce/BUILD.gn
@@ -24,6 +24,7 @@
     "//ios/chrome/browser/signin:signin",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/web",
+    "//ios/web/public",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/chrome/browser/commerce/session_proto_db_factory.h b/ios/chrome/browser/commerce/session_proto_db_factory.h
index 90279fb..e8a599a 100644
--- a/ios/chrome/browser/commerce/session_proto_db_factory.h
+++ b/ios/chrome/browser/commerce/session_proto_db_factory.h
@@ -14,6 +14,8 @@
 #import "components/leveldb_proto/public/shared_proto_database_client_list.h"
 #import "components/session_proto_db/session_proto_db.h"
 #import "ios/web/public/browser_state.h"
+#import "ios/web/public/thread/web_task_traits.h"
+#import "ios/web/public/thread/web_thread.h"
 
 namespace {
 const char kCommerceSubscriptionDBFolder[] = "commerce_subscription_db";
@@ -71,7 +73,8 @@
     return std::make_unique<SessionProtoDB<T>>(
         state->GetProtoDatabaseProvider(),
         state->GetStatePath().AppendASCII(kCommerceSubscriptionDBFolder),
-        leveldb_proto::ProtoDbType::COMMERCE_SUBSCRIPTION_DATABASE);
+        leveldb_proto::ProtoDbType::COMMERCE_SUBSCRIPTION_DATABASE,
+        web::GetUIThreadTaskRunner({}));
   } else {
     // Must add in leveldb_proto::ProtoDbType and database directory folder
     // new protos.
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
index 98966fe..550319e1 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h
@@ -87,7 +87,7 @@
   void OnHistoryDeleted() override;
 
   // ukm::UkmConsentStateObserver:
-  void OnUkmAllowedStateChanged(bool must_purge) override;
+  void OnUkmAllowedStateChanged(bool must_purge, ukm::UkmConsentState) override;
 
   // web::GlobalWebStateObserver:
   void WebStateDidStartLoading(web::WebState* web_state) override;
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
index 4dac6d1..499167c 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
@@ -312,7 +312,7 @@
       metrics_state_manager_->IsMetricsReportingEnabled()));
 
   metrics_service_->RegisterMetricsProvider(
-        std::make_unique<metrics::EntropyStateProvider>(local_state));
+      std::make_unique<metrics::EntropyStateProvider>(local_state));
 
   metrics_service_->RegisterMetricsProvider(
       std::make_unique<metrics::FormFactorMetricsProvider>());
@@ -533,7 +533,9 @@
     ukm_service_->Purge();
 }
 
-void IOSChromeMetricsServiceClient::OnUkmAllowedStateChanged(bool must_purge) {
+void IOSChromeMetricsServiceClient::OnUkmAllowedStateChanged(
+    bool must_purge,
+    ukm::UkmConsentState) {
   if (!ukm_service_)
     return;
   if (must_purge) {
diff --git a/ios/chrome/browser/metrics/metrics_app_interface.mm b/ios/chrome/browser/metrics/metrics_app_interface.mm
index 1f4b3b7..b2007a7 100644
--- a/ios/chrome/browser/metrics/metrics_app_interface.mm
+++ b/ios/chrome/browser/metrics/metrics_app_interface.mm
@@ -53,6 +53,10 @@
 + (void)overrideMetricsAndCrashReportingForTesting {
   IOSChromeMetricsServiceAccessor::SetMetricsAndCrashReportingForTesting(
       &g_metrics_enabled);
+
+  // Give MSBB consent to the UKMService.
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  ukm_test_helper.SetMsbbConsent();
 }
 
 + (void)stopOverridingMetricsAndCrashReportingForTesting {
diff --git a/ios/chrome/browser/metrics/ukm_egtest.mm b/ios/chrome/browser/metrics/ukm_egtest.mm
index 50bf04b..7d50d81 100644
--- a/ios/chrome/browser/metrics/ukm_egtest.mm
+++ b/ios/chrome/browser/metrics/ukm_egtest.mm
@@ -60,6 +60,7 @@
 
   // Grant metrics consent and update MetricsServicesManager.
   [MetricsAppInterface overrideMetricsAndCrashReportingForTesting];
+
   GREYAssert(![MetricsAppInterface setMetricsAndCrashReportingForTesting:YES],
              @"Unpaired set/reset of user consent.");
   GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:YES],
@@ -234,7 +235,6 @@
 // Corresponds to MetricsConsentCheck in //chrome/browser/metrics/
 // ukm_browsertest.cc.
 - (void)testMetricsConsent {
-
   const uint64_t originalClientID = [MetricsAppInterface UKMClientID];
 
   [MetricsAppInterface setMetricsAndCrashReportingForTesting:NO];
@@ -278,39 +278,37 @@
 // Corresponds to ConsentAddedButNoSyncCheck in //chrome/browser/metrics/
 // ukm_browsertest.cc.
 - (void)testSingleDisableSync {
-
   const uint64_t originalClientID = [MetricsAppInterface UKMClientID];
 
   [ChromeEarlGreyUI openSettingsMenu];
-    // Open Sync and Google services settings
-    [ChromeEarlGreyUI tapSettingsMenuButton:GoogleServicesSettingsButton()];
-    // Toggle "Make searches and browsing better" switch off.
+  // Open Sync and Google services settings
+  [ChromeEarlGreyUI tapSettingsMenuButton:GoogleServicesSettingsButton()];
+  // Toggle "Make searches and browsing better" switch off.
 
-    [[[EarlGrey
-        selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
-                                     @"betterSearchAndBrowsingItem_switch",
-                                     YES)]
-           usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
-        onElementWithMatcher:chrome_test_util::GoogleServicesSettingsView()]
-        performAction:chrome_test_util::TurnTableViewSwitchOn(NO)];
+  [[[EarlGrey
+      selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
+                                   @"betterSearchAndBrowsingItem_switch", YES)]
+         usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
+      onElementWithMatcher:chrome_test_util::GoogleServicesSettingsView()]
+      performAction:chrome_test_util::TurnTableViewSwitchOn(NO)];
 
-    GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:NO],
-               @"Failed to assert that UKM was not enabled.");
+  GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:NO],
+             @"Failed to assert that UKM was not enabled.");
 
-    // Toggle "Make searches and browsing better" switch on.
-    [[EarlGrey
-        selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
-                                     @"betterSearchAndBrowsingItem_switch", NO)]
-        performAction:chrome_test_util::TurnTableViewSwitchOn(YES)];
+  // Toggle "Make searches and browsing better" switch on.
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
+                                   @"betterSearchAndBrowsingItem_switch", NO)]
+      performAction:chrome_test_util::TurnTableViewSwitchOn(YES)];
 
-    GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:YES],
-               @"Failed to assert that UKM was enabled.");
-    // Client ID should have been reset.
-    GREYAssertNotEqual(originalClientID, [MetricsAppInterface UKMClientID],
-                       @"Client ID was not reset.");
+  GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:YES],
+             @"Failed to assert that UKM was enabled.");
+  // Client ID should have been reset.
+  GREYAssertNotEqual(originalClientID, [MetricsAppInterface UKMClientID],
+                     @"Client ID was not reset.");
 
-    [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
-        performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
+      performAction:grey_tap()];
 }
 
 // Make sure that UKM is disabled when sync is not enabled.
diff --git a/ios/chrome/browser/web/web_navigation_browser_agent.mm b/ios/chrome/browser/web/web_navigation_browser_agent.mm
index 9ee702c..f7ef3e9 100644
--- a/ios/chrome/browser/web/web_navigation_browser_agent.mm
+++ b/ios/chrome/browser/web/web_navigation_browser_agent.mm
@@ -30,11 +30,14 @@
 WebNavigationBrowserAgent::~WebNavigationBrowserAgent() {}
 
 bool WebNavigationBrowserAgent::CanGoBack(const web::WebState* web_state) {
-  if (!web_state) {
+  if (!web_state || !web_state->IsRealized()) {
     return false;
   }
 
-  if (web_state->GetNavigationManager()->CanGoBack()) {
+  const web::NavigationManager* navigation_manager =
+      web_state->GetNavigationManager();
+  DCHECK(navigation_manager);
+  if (navigation_manager->CanGoBack()) {
     return true;
   }
 
@@ -53,7 +56,14 @@
 }
 
 bool WebNavigationBrowserAgent::CanGoForward(const web::WebState* web_state) {
-  return web_state && web_state->GetNavigationManager()->CanGoForward();
+  if (!web_state || !web_state->IsRealized()) {
+    return false;
+  }
+
+  const web::NavigationManager* navigation_manager =
+      web_state->GetNavigationManager();
+  DCHECK(navigation_manager);
+  return navigation_manager->CanGoForward();
 }
 
 bool WebNavigationBrowserAgent::CanGoForward() {
diff --git a/ios/chrome/test/app/browsing_data_test_util.h b/ios/chrome/test/app/browsing_data_test_util.h
index f3cb2ae..572d0ef 100644
--- a/ios/chrome/test/app/browsing_data_test_util.h
+++ b/ios/chrome/test/app/browsing_data_test_util.h
@@ -17,10 +17,6 @@
 // successful or timed out.
 [[nodiscard]] bool ClearBrowsingHistory();
 
-// Clears browsing cookies and returns whether clearing the cookies was
-// successful or timed out.
-[[nodiscard]] bool ClearBrowsingCookies();
-
 // Clears browsing data and returns whether clearing was successful or timed
 // out.
 // TODO(crbug.com/1016960): The method will time out if it's called from
diff --git a/ios/chrome/test/app/browsing_data_test_util.mm b/ios/chrome/test/app/browsing_data_test_util.mm
index 75dfaac4..366b1a0 100644
--- a/ios/chrome/test/app/browsing_data_test_util.mm
+++ b/ios/chrome/test/app/browsing_data_test_util.mm
@@ -62,11 +62,6 @@
                            BrowsingDataRemoveMask::REMOVE_HISTORY);
 }
 
-bool ClearBrowsingCookies() {
-  return ClearBrowsingData(/*off_the_record=*/false,
-                           BrowsingDataRemoveMask::REMOVE_COOKIES);
-}
-
 bool ClearAllBrowsingData(bool off_the_record) {
   return ClearBrowsingData(off_the_record, BrowsingDataRemoveMask::REMOVE_ALL);
 }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 6e53faf..4dc8254 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -615,10 +615,6 @@
 // A GREYAssert is induced if cookies can not be returned.
 - (NSDictionary*)cookies;
 
-// Clears browsing cookies. Raises an EarlGrey exception if cookies is not
-// cleared within a timeout.
-- (void)clearBrowsingCookies;
-
 #pragma mark - Accessibility Utilities (EG2)
 
 // Verifies that all interactive elements on screen (or at least one of their
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index b78f9e7d..ade093d 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -522,15 +522,6 @@
   return cookies;
 }
 
-- (void)clearBrowsingCookies {
-  EG_TEST_HELPER_ASSERT_NO_ERROR(
-      [ChromeEarlGreyAppInterface clearBrowsingCookies]);
-
-  // After clearing browsing cookies via code, wait for the UI to be done
-  // with any updates. This includes icons from the new tab page being removed.
-  GREYWaitForAppToIdle(@"App failed to idle");
-}
-
 #pragma mark - WebState Utilities (EG2)
 
 - (void)tapWebStateElementWithID:(NSString*)elementID {
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index 987b547c..07d5dfce 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -41,11 +41,6 @@
 // operation failed.
 + (NSError*)clearBrowsingHistory;
 
-// Clears browsing cookies and waits for cookies to finish clearing before
-// returning. Returns nil on success, or else an NSError indicating why the
-// operation failed.
-+ (NSError*)clearBrowsingCookies;
-
 // Clears all web state browsing data and waits to finish clearing before
 // returning. Returns nil on success, otherwise an NSError indicating why
 // the operation failed.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 4de665e..853b446d4 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -145,15 +145,6 @@
       @"Clearing browser history timed out");
 }
 
-+ (NSError*)clearBrowsingCookies {
-  if (chrome_test_util::ClearBrowsingCookies()) {
-    return nil;
-  }
-
-  return testing::NSErrorWithLocalizedDescription(
-      @"Clearing browser cookies timed out");
-}
-
 + (NSInteger)browsingHistoryEntryCountWithError:
     (NSError* __autoreleasing*)error {
   return chrome_test_util::GetBrowsingHistoryEntryCount(error);
diff --git a/mojo/core/ipcz_driver/mojo_message.cc b/mojo/core/ipcz_driver/mojo_message.cc
index c5eca06..6194c0c 100644
--- a/mojo/core/ipcz_driver/mojo_message.cc
+++ b/mojo/core/ipcz_driver/mojo_message.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/containers/span.h"
+#include "base/containers/stack_container.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/ranges/algorithm.h"
 #include "mojo/core/ipcz_api.h"
@@ -18,6 +19,62 @@
 
 namespace mojo::core::ipcz_driver {
 
+namespace {
+
+// Data pipe attachments come in two parts within a message's handle list: the
+// DataPipe object wherever it was placed by the sender, and its control portal
+// as a separate attachment at the end of the handle list. For a message with
+// two data pipes endpoints (X and Y) and two message pipe endpoints(A and B),
+// sent in the order AXBY, a well-formed message will have 6 total handles
+// attached:
+//
+// Message Pipe A   Message Pipe B   DataPipe X's portal
+//      |               |              |
+//     0:A     1:X     2:B     3:Y    4:x    5:y
+//              |               |             |
+//          DataPipe X       DataPipe Y      DataPipe Y's portal
+//
+// This function validates that each DataPipe in `handles` has an associated
+// portal, and it fixes up `handles` by stripping those portals off the end of
+// the list and passing ownership to their corresponding DataPipe object.
+//
+// Returns true if and only if the handle list is well-formed in this regard.
+//
+// TODO(https://crbug.com/1382170): Since boxes now support application objects,
+// DataPipe can be migrated out of the driver and we can avoid this whole
+// serialization hack.
+bool FixUpDataPipeHandles(std::vector<IpczHandle>& handles) {
+  base::StackVector<DataPipe*, 2> data_pipes;
+  for (IpczHandle handle : handles) {
+    if (auto* data_pipe = DataPipe::FromBox(handle)) {
+      data_pipes->push_back(data_pipe);
+    }
+  }
+
+  if (handles.size() < data_pipes->size() * 2) {
+    // Not enough handles.
+    return false;
+  }
+
+  // The last N handles must be portals for the pipes in `data_pipes`, in order.
+  // Remove them from the message's handles and give them to their data pipes.
+  const size_t first_data_pipe_portal = handles.size() - data_pipes->size();
+  for (size_t i = 0; i < data_pipes->size(); ++i) {
+    const IpczHandle handle = handles[first_data_pipe_portal + i];
+    IpczPortalStatus status = {.size = sizeof(status)};
+    if (GetIpczAPI().QueryPortalStatus(handle, IPCZ_NO_FLAGS, nullptr,
+                                       &status) != IPCZ_RESULT_OK) {
+      // Not a portal, so not a valid MojoMessage parcel.
+      return false;
+    }
+    data_pipes[i]->AdoptPortal(ScopedIpczHandle(handle));
+  }
+  handles.resize(first_data_pipe_portal);
+  return true;
+}
+
+}  // namespace
+
 MojoMessage::MojoMessage() = default;
 
 MojoMessage::MojoMessage(std::vector<uint8_t> data,
@@ -41,7 +98,7 @@
   }
 }
 
-bool MojoMessage::SetParcel(ScopedIpczHandle parcel) {
+void MojoMessage::SetParcel(ScopedIpczHandle parcel) {
   DCHECK(!data_storage_);
   DCHECK(!parcel_.is_valid());
 
@@ -52,62 +109,35 @@
   size_t num_handles;
   IpczResult result = GetIpczAPI().BeginGet(
       parcel_.get(), IPCZ_NO_FLAGS, nullptr, &data, &num_bytes, &num_handles);
-  if (result != IPCZ_RESULT_OK) {
-    return false;
-  }
+
+  // We always pass a parcel object in, so Begin/EndGet() must always succeed.
+  DCHECK_EQ(result, IPCZ_RESULT_OK);
 
   // Grab only the handles.
   handles_.resize(num_handles);
   result = GetIpczAPI().EndGet(parcel_.get(), 0, num_handles, IPCZ_NO_FLAGS,
                                nullptr, handles_.data());
-  if (result != IPCZ_RESULT_OK) {
-    return false;
-  }
+  DCHECK_EQ(result, IPCZ_RESULT_OK);
 
   // Now start a new two-phase get, which we'll leave active indefinitely for
   // `data_` to reference.
   result = GetIpczAPI().BeginGet(parcel_.get(), IPCZ_NO_FLAGS, nullptr, &data,
                                  &num_bytes, &num_handles);
-  if (result != IPCZ_RESULT_OK) {
-    return false;
-  }
+  DCHECK_EQ(result, IPCZ_RESULT_OK);
 
   DCHECK_EQ(0u, num_handles);
   data_ = base::make_span(static_cast<uint8_t*>(const_cast<void*>(data)),
                           num_bytes);
 
-  // If there are any serialized DataPipe objects, accumulate them so we can
-  // pluck their portals off the end of `handles`. Their portals were
-  // attached the end of `handles` when the sender finalized the message in
-  // MojoWriteMessageIpcz().
-  std::vector<DataPipe*> data_pipes;
-  for (IpczHandle handle : handles_) {
-    if (auto* data_pipe = DataPipe::FromBox(handle)) {
-      data_pipes.push_back(data_pipe);
-    }
+  if (!FixUpDataPipeHandles(handles_)) {
+    // The handle list was malformed. Although this is a validation error, it
+    // is not safe to trigger MojoNotifyBadMessage from within MojoReadMessage,
+    // as this may result in unexpected application re-entrancy. Instead we wipe
+    // out all handles, which will effectively trigger a validation failure
+    // further up the stack when the application sees (e.g. via bindings
+    // validation) that expected handles are missing.
+    handles_.clear();
   }
-
-  if (handles_.size() / 2 < data_pipes.size()) {
-    // There must be at least enough handles for each DataPipe box AND its
-    // portal.
-    return false;
-  }
-
-  // The last N handles are portals for the pipes in `data_pipes`, in order.
-  // Remove them from the message's handles and give them to their data pipes.
-  const size_t first_data_pipe_portal = handles_.size() - data_pipes.size();
-  for (size_t i = 0; i < data_pipes.size(); ++i) {
-    const IpczHandle handle = handles_[first_data_pipe_portal + i];
-    if (ObjectBase::FromBox(handle)) {
-      // The handle in this position needs to be a portal. If it's a driver
-      // object, something is wrong.
-      return false;
-    }
-
-    data_pipes[i]->AdoptPortal(ScopedIpczHandle(handle));
-  }
-  handles_.resize(first_data_pipe_portal);
-  return true;
 }
 
 MojoResult MojoMessage::AppendData(uint32_t additional_num_bytes,
diff --git a/mojo/core/ipcz_driver/mojo_message.h b/mojo/core/ipcz_driver/mojo_message.h
index 65729d2..c9f2e2e 100644
--- a/mojo/core/ipcz_driver/mojo_message.h
+++ b/mojo/core/ipcz_driver/mojo_message.h
@@ -56,7 +56,7 @@
   IpczHandle parcel() const { return parcel_.get(); }
 
   // Sets the received parcel object backing this message.
-  bool SetParcel(ScopedIpczHandle parcel);
+  void SetParcel(ScopedIpczHandle parcel);
 
   // Appends data to a new or partially serialized message, effectively
   // implementing MojoAppendMessageData().
diff --git a/mojo/core/ipcz_driver/object.cc b/mojo/core/ipcz_driver/object.cc
index 5fb099fc..e0787aa 100644
--- a/mojo/core/ipcz_driver/object.cc
+++ b/mojo/core/ipcz_driver/object.cc
@@ -52,6 +52,9 @@
       .object = {.driver_object = IPCZ_INVALID_DRIVER_HANDLE},
   };
   GetIpczAPI().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &contents);
+  if (contents.type != IPCZ_BOX_TYPE_DRIVER_OBJECT) {
+    return IPCZ_INVALID_DRIVER_HANDLE;
+  }
   return contents.object.driver_object;
 }
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 7b6ae9ac..b18ca800b 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -546,6 +546,8 @@
     "first_party_sets/addition_overlaps_union_find.h",
     "first_party_sets/first_party_set_entry.cc",
     "first_party_sets/first_party_set_entry.h",
+    "first_party_sets/first_party_set_entry_override.cc",
+    "first_party_sets/first_party_set_entry_override.h",
     "first_party_sets/first_party_set_metadata.cc",
     "first_party_sets/first_party_set_metadata.h",
     "first_party_sets/first_party_sets_cache_filter.cc",
@@ -4421,6 +4423,7 @@
     "filter/filter_source_stream_unittest.cc",
     "filter/gzip_source_stream_unittest.cc",
     "first_party_sets/addition_overlaps_union_find_unittest.cc",
+    "first_party_sets/first_party_set_entry_override_unittest.cc",
     "first_party_sets/first_party_sets_cache_filter_unittest.cc",
     "first_party_sets/first_party_sets_context_config_unittest.cc",
     "first_party_sets/global_first_party_sets_unittest.cc",
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index 81d3a89..c6dd99bc6 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -4382,6 +4382,12 @@
   EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
 }
 
+TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentLeaf) {
+  chain_[0]->EraseExtension(der::Input(kBasicConstraintsOid));
+
+  EXPECT_THAT(Verify(), IsOk());
+}
+
 TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingRoot) {
   chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
                                         /*excluded_dns_names=*/{});
@@ -4423,6 +4429,17 @@
   }
 }
 
+TEST_P(CertVerifyProcConstraintsTest, NameConstraintsOnLeaf) {
+  chain_[0]->SetNameConstraintsDnsNames(
+      /*permitted_dns_names=*/{"example.com"},
+      /*excluded_dns_names=*/{});
+
+  // TODO(mattm): this should be an error
+  // RFC 5280 4.2.1.10 says: "The name constraints extension, which MUST be
+  // used only in a CA certificate, ..."
+  EXPECT_THAT(Verify(), IsOk());
+}
+
 TEST_P(CertVerifyProcConstraintsTest, ValidityExpiredRoot) {
   chain_[3]->SetValidity(base::Time::Now() - base::Days(14),
                          base::Time::Now() - base::Days(7));
@@ -4722,6 +4739,22 @@
   EXPECT_THAT(Verify(), IsOk());
 }
 
+TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoDigitalSignatureLeaf) {
+  // This test is mostly uninteresting since keyUsage on the end-entity is only
+  // checked at the TLS layer, not during cert verification.
+  chain_[0]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN});
+
+  EXPECT_THAT(Verify(), IsOk());
+}
+
+TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentLeaf) {
+  // This test is mostly uninteresting since keyUsage on the end-entity is only
+  // checked at the TLS layer, not during cert verification.
+  chain_[0]->EraseExtension(der::Input(kKeyUsageOid));
+
+  EXPECT_THAT(Verify(), IsOk());
+}
+
 TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthRoot) {
   chain_[3]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
 
@@ -4759,6 +4792,12 @@
   EXPECT_THAT(Verify(), IsOk());
 }
 
+TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthLeaf) {
+  chain_[0]->SetExtendedKeyUsages({der::Input(kCodeSigning)});
+
+  EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
+}
+
 TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmRoot) {
   chain_[3]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
 
@@ -4780,6 +4819,17 @@
   }
 }
 
+TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmLeaf) {
+  chain_[0]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV());
+
+  if (verify_proc_type() == CERT_VERIFY_PROC_MAC ||
+      verify_proc_type() == CERT_VERIFY_PROC_IOS) {
+    EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID));
+  } else {
+    EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
+  }
+}
+
 TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionRoot) {
   for (bool critical : {true, false}) {
     SCOPED_TRACE(critical);
@@ -4813,6 +4863,19 @@
   }
 }
 
+TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionLeaf) {
+  for (bool critical : {true, false}) {
+    SCOPED_TRACE(critical);
+    chain_[0]->SetExtension(TestOid0(), "hello world", critical);
+
+    if (critical) {
+      EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
+    } else {
+      EXPECT_THAT(Verify(), IsOk());
+    }
+  }
+}
+
 // A set of tests that check how various constraints are enforced when they
 // are applied to a directly trusted non-self-signed leaf certificate.
 class CertVerifyProcConstraintsTrustedLeafTest
diff --git a/net/cert/pki/DEPS b/net/cert/pki/DEPS
index 65c57edb..3a1afb4d 100644
--- a/net/cert/pki/DEPS
+++ b/net/cert/pki/DEPS
@@ -10,9 +10,3 @@
   "+base/path_service.h",
   "+base/supports_user_data.h",
 ]
-specific_include_rules = {
-  "path_builder_unittest\.cc": [
-    "+base/test/bind.h",
-  ],
-}
-
diff --git a/net/cert/pki/path_builder_unittest.cc b/net/cert/pki/path_builder_unittest.cc
index e1a9d71a..efe50c6 100644
--- a/net/cert/pki/path_builder_unittest.cc
+++ b/net/cert/pki/path_builder_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/callback_forward.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
-#include "base/test/bind.h"
 #include "build/build_config.h"
 #include "net/cert/pem.h"
 #include "net/cert/pki/cert_error_params.h"
@@ -86,7 +85,7 @@
 
   ~AsyncCertIssuerSourceStatic() override = default;
 
-  void SetAsyncGetCallback(base::RepeatingClosure closure) {
+  void SetAsyncGetCallback(std::function<void()> closure) {
     async_get_callback_ = std::move(closure);
   }
 
@@ -103,8 +102,9 @@
     static_cert_issuer_source_.SyncGetIssuersOf(cert, &issuers);
     auto req = std::make_unique<StaticAsyncRequest>(std::move(issuers));
     *out_req = std::move(req);
-    if (!async_get_callback_.is_null())
-      async_get_callback_.Run();
+    if (async_get_callback_) {
+      async_get_callback_();
+    }
   }
   int num_async_gets() const { return num_async_gets_; }
 
@@ -112,7 +112,7 @@
   CertIssuerSourceStatic static_cert_issuer_source_;
 
   int num_async_gets_ = 0;
-  base::RepeatingClosure async_get_callback_;
+  std::function<void()> async_get_callback_ = nullptr;
 };
 
 ::testing::AssertionResult ReadTestPem(const std::string& file_name,
@@ -754,8 +754,8 @@
   // returning the async result.
   AsyncCertIssuerSourceStatic async_certs;
   async_certs.AddCert(c_by_d_);
-  async_certs.SetAsyncGetCallback(base::BindLambdaForTesting(
-      [&] { delegate_.SetDeadlineExpiredForTesting(true); }));
+  async_certs.SetAsyncGetCallback(
+      [&] { delegate_.SetDeadlineExpiredForTesting(true); });
 
   CertPathBuilder path_builder(
       a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU,
diff --git a/net/first_party_sets/first_party_set_entry_override.cc b/net/first_party_sets/first_party_set_entry_override.cc
new file mode 100644
index 0000000..20cc41d
--- /dev/null
+++ b/net/first_party_sets/first_party_set_entry_override.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 "net/first_party_sets/first_party_set_entry_override.h"
+
+#include <utility>
+
+#include "net/first_party_sets/first_party_set_entry.h"
+
+namespace net {
+
+FirstPartySetEntryOverride::FirstPartySetEntryOverride() = default;
+FirstPartySetEntryOverride::FirstPartySetEntryOverride(FirstPartySetEntry entry)
+    : entry_(std::move(entry)) {}
+
+FirstPartySetEntryOverride::FirstPartySetEntryOverride(
+    FirstPartySetEntryOverride&& other) = default;
+FirstPartySetEntryOverride& FirstPartySetEntryOverride::operator=(
+    FirstPartySetEntryOverride&& other) = default;
+FirstPartySetEntryOverride::FirstPartySetEntryOverride(
+    const FirstPartySetEntryOverride& other) = default;
+FirstPartySetEntryOverride& FirstPartySetEntryOverride::operator=(
+    const FirstPartySetEntryOverride& other) = default;
+
+FirstPartySetEntryOverride::~FirstPartySetEntryOverride() = default;
+
+bool FirstPartySetEntryOverride::operator==(
+    const FirstPartySetEntryOverride& other) const {
+  return entry_ == other.entry_;
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const FirstPartySetEntryOverride& override) {
+  os << "{";
+  if (override.IsDeletion()) {
+    os << "<deleted>";
+  } else {
+    os << override.GetEntry();
+  }
+  os << "}";
+  return os;
+}
+
+}  // namespace net
diff --git a/net/first_party_sets/first_party_set_entry_override.h b/net/first_party_sets/first_party_set_entry_override.h
new file mode 100644
index 0000000..f7ab2b53
--- /dev/null
+++ b/net/first_party_sets/first_party_set_entry_override.h
@@ -0,0 +1,66 @@
+// 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 NET_FIRST_PARTY_SETS_FIRST_PARTY_SET_ENTRY_OVERRIDE_H_
+#define NET_FIRST_PARTY_SETS_FIRST_PARTY_SET_ENTRY_OVERRIDE_H_
+
+#include "net/base/net_export.h"
+#include "net/first_party_sets/first_party_set_entry.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace mojo {
+template <typename DataViewType, typename T>
+struct StructTraits;
+}  // namespace mojo
+namespace network::mojom {
+class FirstPartySetEntryOverrideDataView;
+}  // namespace network::mojom
+
+namespace net {
+
+// This class represents a single modification to be applied on top of the
+// global First-Party Sets list. A modifications may be a deletion, remapping,
+// or new mapping.
+class NET_EXPORT FirstPartySetEntryOverride {
+ public:
+  // Creates a new modification representing a deletion.
+  FirstPartySetEntryOverride();
+  // Creates a new modification representing a remapping/additional mapping.
+  explicit FirstPartySetEntryOverride(FirstPartySetEntry entry);
+
+  FirstPartySetEntryOverride(FirstPartySetEntryOverride&& other);
+  FirstPartySetEntryOverride& operator=(FirstPartySetEntryOverride&& other);
+  FirstPartySetEntryOverride(const FirstPartySetEntryOverride& other);
+  FirstPartySetEntryOverride& operator=(
+      const FirstPartySetEntryOverride& other);
+
+  ~FirstPartySetEntryOverride();
+
+  bool operator==(const FirstPartySetEntryOverride& other) const;
+
+  // Returns true iff this override is a deletion.
+  bool IsDeletion() const { return !entry_.has_value(); }
+
+  // Returns the new target entry, if this override is not a deletion. Must not
+  // be called if `IsDeletion()` is true.
+  const FirstPartySetEntry& GetEntry() const {
+    DCHECK(!IsDeletion());
+    return entry_.value();
+  }
+
+ private:
+  // mojo (de)serialization needs access to private details.
+  friend struct mojo::StructTraits<
+      network::mojom::FirstPartySetEntryOverrideDataView,
+      FirstPartySetEntryOverride>;
+
+  absl::optional<FirstPartySetEntry> entry_;
+};
+
+NET_EXPORT std::ostream& operator<<(std::ostream& os,
+                                    const FirstPartySetEntryOverride& override);
+
+}  // namespace net
+
+#endif  // NET_FIRST_PARTY_SETS_FIRST_PARTY_SET_ENTRY_OVERRIDE_H_
\ No newline at end of file
diff --git a/net/first_party_sets/first_party_set_entry_override_unittest.cc b/net/first_party_sets/first_party_set_entry_override_unittest.cc
new file mode 100644
index 0000000..e1384df4
--- /dev/null
+++ b/net/first_party_sets/first_party_set_entry_override_unittest.cc
@@ -0,0 +1,32 @@
+// 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 "net/first_party_sets/first_party_set_entry_override.h"
+
+#include "net/base/schemeful_site.h"
+#include "net/first_party_sets/first_party_set_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+TEST(FirstPartySetEntryOverrideTest, IsDeletion_true) {
+  EXPECT_TRUE(FirstPartySetEntryOverride().IsDeletion());
+}
+
+TEST(FirstPartySetEntryOverrideTest, IsDeletion_false) {
+  EXPECT_FALSE(
+      FirstPartySetEntryOverride(
+          FirstPartySetEntry(SchemefulSite(GURL("https://example.test")),
+                             SiteType::kPrimary, absl::nullopt))
+          .IsDeletion());
+}
+
+TEST(FirstPartySetEntryOverrideTest, GetEntry) {
+  FirstPartySetEntry entry(SchemefulSite(GURL("https://example.test")),
+                           SiteType::kPrimary, absl::nullopt);
+  EXPECT_EQ(FirstPartySetEntryOverride(entry).GetEntry(), entry);
+}
+
+}  // namespace net
diff --git a/net/first_party_sets/first_party_sets_context_config.cc b/net/first_party_sets/first_party_sets_context_config.cc
index 52ed3107..b8deadd 100644
--- a/net/first_party_sets/first_party_sets_context_config.cc
+++ b/net/first_party_sets/first_party_sets_context_config.cc
@@ -4,6 +4,8 @@
 
 #include "net/first_party_sets/first_party_sets_context_config.h"
 
+#include "net/first_party_sets/first_party_set_entry_override.h"
+
 namespace net {
 
 FirstPartySetsContextConfig::FirstPartySetsContextConfig() = default;
@@ -27,7 +29,7 @@
   return customizations_ == other.customizations_;
 }
 
-absl::optional<absl::optional<FirstPartySetEntry>>
+absl::optional<FirstPartySetEntryOverride>
 FirstPartySetsContextConfig::FindOverride(const SchemefulSite& site) const {
   if (const auto it = customizations_.find(site); it != customizations_.end()) {
     return it->second;
@@ -41,10 +43,9 @@
 
 bool FirstPartySetsContextConfig::ForEachCustomizationEntry(
     base::FunctionRef<bool(const SchemefulSite&,
-                           const absl::optional<FirstPartySetEntry>&)> f)
-    const {
-  for (const auto& [site, maybe_entry] : customizations_) {
-    if (!f(site, maybe_entry))
+                           const FirstPartySetEntryOverride&)> f) const {
+  for (const auto& [site, override] : customizations_) {
+    if (!f(site, override))
       return false;
   }
   return true;
diff --git a/net/first_party_sets/first_party_sets_context_config.h b/net/first_party_sets/first_party_sets_context_config.h
index 76f127a..aead998 100644
--- a/net/first_party_sets/first_party_sets_context_config.h
+++ b/net/first_party_sets/first_party_sets_context_config.h
@@ -8,7 +8,7 @@
 #include "base/containers/flat_map.h"
 #include "base/functional/function_ref.h"
 #include "net/base/schemeful_site.h"
-#include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace mojo {
@@ -26,7 +26,7 @@
 class NET_EXPORT FirstPartySetsContextConfig {
  public:
   using OverrideSets =
-      base::flat_map<SchemefulSite, absl::optional<FirstPartySetEntry>>;
+      base::flat_map<SchemefulSite, FirstPartySetEntryOverride>;
 
   FirstPartySetsContextConfig();
   explicit FirstPartySetsContextConfig(OverrideSets customizations);
@@ -44,10 +44,9 @@
 
   // Finds an override for the given site, in this context. Returns:
   // - nullopt if no override was found.
-  // - optional(nullopt) if an override was found, and it's a deletion.
-  // - optional(optional(entry)) if an override was found, and it's a
-  // modification/addition.
-  absl::optional<absl::optional<FirstPartySetEntry>> FindOverride(
+  // - optional(override) if an override was found. The override may be a
+  //     deletion or a modification/addition.
+  absl::optional<FirstPartySetEntryOverride> FindOverride(
       const SchemefulSite& site) const;
 
   // Returns whether an override can be found for the given site in this
@@ -55,17 +54,14 @@
   bool Contains(const SchemefulSite& site) const;
 
   // Synchronously iterate over all the override entries. Each iteration will be
-  // invoked with the relevant site and the override that applies to it. The
-  // override will be `nullopt` if it is a deletion, or `optional(entry)` if it
-  // is a modification of an existing entry, or an addition.
+  // invoked with the relevant site and the override that applies to it.
   //
   // Returns early if any of the iterations returns false. Returns false if
   // iteration was incomplete; true if all iterations returned true. No
   // guarantees are made re: iteration order.
   bool ForEachCustomizationEntry(
       base::FunctionRef<bool(const SchemefulSite&,
-                             const absl::optional<FirstPartySetEntry>&)> f)
-      const;
+                             const FirstPartySetEntryOverride&)> f) const;
 
  private:
   // mojo (de)serialization needs access to private details.
diff --git a/net/first_party_sets/first_party_sets_context_config_unittest.cc b/net/first_party_sets/first_party_sets_context_config_unittest.cc
index 743a5e5..8c93393 100644
--- a/net/first_party_sets/first_party_sets_context_config_unittest.cc
+++ b/net/first_party_sets/first_party_sets_context_config_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -15,6 +16,11 @@
 
 using ::testing::Optional;
 
+MATCHER_P(OverridesTo, entry, "") {
+  return !arg.IsDeletion() &&
+         testing::ExplainMatchResult(entry, arg.GetEntry(), result_listener);
+}
+
 namespace net {
 
 TEST(FirstPartySetsContextConfigTest, FindOverride_empty) {
@@ -28,32 +34,36 @@
   FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt);
   SchemefulSite foo(GURL("https://foo.test"));
 
-  EXPECT_EQ(FirstPartySetsContextConfig({{example, entry}}).FindOverride(foo),
+  EXPECT_EQ(FirstPartySetsContextConfig(
+                {{example, FirstPartySetEntryOverride(entry)}})
+                .FindOverride(foo),
             absl::nullopt);
 }
 
 TEST(FirstPartySetsContextConfigTest, FindOverride_deletion) {
   SchemefulSite example(GURL("https://example.test"));
 
-  EXPECT_THAT(FirstPartySetsContextConfig({{example, absl::nullopt}})
-                  .FindOverride(example),
-              Optional(absl::nullopt));
+  EXPECT_THAT(
+      FirstPartySetsContextConfig({{example, FirstPartySetEntryOverride()}})
+          .FindOverride(example),
+      Optional(FirstPartySetEntryOverride()));
 }
 
 TEST(FirstPartySetsContextConfigTest, FindOverride_modification) {
   SchemefulSite example(GURL("https://example.test"));
   FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt);
 
-  EXPECT_THAT(
-      FirstPartySetsContextConfig({{example, entry}}).FindOverride(example),
-      Optional(Optional(entry)));
+  EXPECT_THAT(FirstPartySetsContextConfig(
+                  {{example, FirstPartySetEntryOverride(entry)}})
+                  .FindOverride(example),
+              Optional(OverridesTo(entry)));
 }
 
 TEST(FirstPartySetsContextConfigTest, Contains) {
   SchemefulSite example(GURL("https://example.test"));
   SchemefulSite decoy(GURL("https://decoy.test"));
 
-  FirstPartySetsContextConfig config({{example, absl::nullopt}});
+  FirstPartySetsContextConfig config({{example, FirstPartySetEntryOverride()}});
 
   EXPECT_TRUE(config.Contains(example));
   EXPECT_FALSE(config.Contains(decoy));
@@ -63,13 +73,13 @@
   SchemefulSite example(GURL("https://example.test"));
   SchemefulSite foo(GURL("https://foo.test"));
 
-  FirstPartySetsContextConfig config(
-      {{example, absl::nullopt}, {foo, absl::nullopt}});
+  FirstPartySetsContextConfig config({{example, FirstPartySetEntryOverride()},
+                                      {foo, FirstPartySetEntryOverride()}});
 
   int count = 0;
   EXPECT_TRUE(config.ForEachCustomizationEntry(
       [&](const SchemefulSite& site,
-          const absl::optional<FirstPartySetEntry>& entry) {
+          const FirstPartySetEntryOverride& override) {
         ++count;
         return true;
       }));
@@ -80,13 +90,13 @@
   SchemefulSite example(GURL("https://example.test"));
   SchemefulSite foo(GURL("https://foo.test"));
 
-  FirstPartySetsContextConfig config(
-      {{example, absl::nullopt}, {foo, absl::nullopt}});
+  FirstPartySetsContextConfig config({{example, FirstPartySetEntryOverride()},
+                                      {foo, FirstPartySetEntryOverride()}});
 
   int count = 0;
   EXPECT_FALSE(config.ForEachCustomizationEntry(
       [&](const SchemefulSite& site,
-          const absl::optional<FirstPartySetEntry>& entry) {
+          const FirstPartySetEntryOverride& override) {
         ++count;
         return count < 1;
       }));
diff --git a/net/first_party_sets/global_first_party_sets.cc b/net/first_party_sets/global_first_party_sets.cc
index 1ff9fc41..2618851 100644
--- a/net/first_party_sets/global_first_party_sets.cc
+++ b/net/first_party_sets/global_first_party_sets.cc
@@ -18,6 +18,7 @@
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/addition_overlaps_union_find.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -53,7 +54,7 @@
 // from a site to its entry.
 void UpdateCustomizations(
     const std::vector<SingleSet>& set_list,
-    std::vector<std::pair<SchemefulSite, absl::optional<FirstPartySetEntry>>>&
+    std::vector<std::pair<SchemefulSite, FirstPartySetEntryOverride>>&
         site_to_entry) {
   for (const auto& set : set_list) {
     for (const auto& site_and_entry : set) {
@@ -63,7 +64,7 @@
 }
 
 const SchemefulSite& ProjectKey(
-    const std::pair<SchemefulSite, absl::optional<FirstPartySetEntry>>& p) {
+    const std::pair<SchemefulSite, FirstPartySetEntryOverride>& p) {
   return p.first;
 }
 
@@ -143,16 +144,19 @@
 
   // Check if `normalized_site` can be found in the customizations first.
   if (config) {
-    if (const auto entry = config->FindOverride(normalized_site);
-        entry.has_value()) {
-      return entry.value();
+    if (const auto override = config->FindOverride(normalized_site);
+        override.has_value()) {
+      return override->IsDeletion() ? absl::nullopt
+                                    : absl::make_optional(override->GetEntry());
     }
   }
 
   // Now see if it's in the manual config (with or without a manual alias).
-  if (const auto manual_entry = manual_config_.FindOverride(normalized_site);
-      manual_entry.has_value()) {
-    return manual_entry.value();
+  if (const auto manual_override = manual_config_.FindOverride(normalized_site);
+      manual_override.has_value()) {
+    return manual_override->IsDeletion()
+               ? absl::nullopt
+               : absl::make_optional(manual_override->GetEntry());
   }
 
   // Finally, look up in `entries_`, applying an alias if applicable.
@@ -258,8 +262,8 @@
     return FirstPartySetsContextConfig();
   }
 
-  // Maps a site to its new entry if it has one.
-  std::vector<std::pair<SchemefulSite, absl::optional<FirstPartySetEntry>>>
+  // Maps a site to its override.
+  std::vector<std::pair<SchemefulSite, FirstPartySetEntryOverride>>
       site_to_entry;
 
   std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>
@@ -349,7 +353,7 @@
           if (replaced_existing_owners.contains(set_entry.primary()) &&
               !flattened_replacements.contains(member) &&
               !addition_intersected_owners.contains(set_entry.primary())) {
-            site_to_entry.emplace_back(member, absl::nullopt);
+            site_to_entry.emplace_back(member, FirstPartySetEntryOverride());
           }
 
           return true;
@@ -358,7 +362,7 @@
     // Any owner remaining in `potential_singleton` is a real singleton, so
     // delete it:
     for (const auto& [owner, members] : potential_singletons) {
-      site_to_entry.emplace_back(owner, absl::nullopt);
+      site_to_entry.emplace_back(owner, FirstPartySetEntryOverride());
     }
   }
 
@@ -367,7 +371,7 @@
   for (const auto& [alias, site] : aliases_) {
     if (base::Contains(site_to_entry, site, ProjectKey) &&
         !base::Contains(site_to_entry, alias, ProjectKey)) {
-      site_to_entry.emplace_back(alias, absl::nullopt);
+      site_to_entry.emplace_back(alias, FirstPartySetEntryOverride());
     }
   }
 
@@ -445,8 +449,7 @@
 
 bool GlobalFirstPartySets::ForEachManualConfigEntry(
     base::FunctionRef<bool(const SchemefulSite&,
-                           const absl::optional<FirstPartySetEntry>&)> f)
-    const {
+                           const FirstPartySetEntryOverride&)> f) const {
   return manual_config_.ForEachCustomizationEntry(f);
 }
 
@@ -465,9 +468,9 @@
   if (config != nullptr) {
     if (!config->ForEachCustomizationEntry(
             [&](const SchemefulSite& site,
-                const absl::optional<FirstPartySetEntry>& maybe_entry) {
-              if (maybe_entry.has_value())
-                return f(site, *maybe_entry);
+                const FirstPartySetEntryOverride& override) {
+              if (!override.IsDeletion())
+                return f(site, override.GetEntry());
               return true;
             })) {
       return false;
@@ -477,9 +480,9 @@
   // Then the manual set:
   if (!manual_config_.ForEachCustomizationEntry(
           [&](const SchemefulSite& site,
-              const absl::optional<FirstPartySetEntry>& maybe_entry) {
-            if (maybe_entry.has_value() && (!config || !config->Contains(site)))
-              return f(site, *maybe_entry);
+              const FirstPartySetEntryOverride& override) {
+            if (!override.IsDeletion() && (!config || !config->Contains(site)))
+              return f(site, override.GetEntry());
             return true;
           })) {
     return false;
@@ -506,9 +509,8 @@
   os << "}, manual_config = {";
   sets.manual_config().ForEachCustomizationEntry(
       [&](const net::SchemefulSite& site,
-          const absl::optional<net::FirstPartySetEntry>& maybe_entry) {
-        os << "{" << site.Serialize() << ": ";
-        maybe_entry.has_value() ? os << maybe_entry.value() : os << "nullopt";
+          const FirstPartySetEntryOverride& override) {
+        os << "{" << site.Serialize() << ": " << override << "},";
         return true;
       });
   os << "}}";
diff --git a/net/first_party_sets/global_first_party_sets.h b/net/first_party_sets/global_first_party_sets.h
index 58539d2..5cbd7bc 100644
--- a/net/first_party_sets/global_first_party_sets.h
+++ b/net/first_party_sets/global_first_party_sets.h
@@ -14,6 +14,7 @@
 #include "net/base/net_export.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -117,8 +118,7 @@
   // order.
   bool ForEachManualConfigEntry(
       base::FunctionRef<bool(const SchemefulSite&,
-                             const absl::optional<FirstPartySetEntry>&)> f)
-      const;
+                             const FirstPartySetEntryOverride&)> f) const;
 
   // Synchronously iterate over all the effective entries (i.e. anything that
   // could be returned by `FindEntry` using this instance and `config`,
diff --git a/net/first_party_sets/global_first_party_sets_unittest.cc b/net/first_party_sets/global_first_party_sets_unittest.cc
index 4cc7397..e6a5a8a 100644
--- a/net/first_party_sets/global_first_party_sets_unittest.cc
+++ b/net/first_party_sets/global_first_party_sets_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/version.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
@@ -148,7 +149,8 @@
   FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt);
   FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1);
 
-  FirstPartySetsContextConfig config({{example, override_entry}});
+  FirstPartySetsContextConfig config(
+      {{example, net::FirstPartySetEntryOverride(override_entry)}});
 
   EXPECT_THAT(GlobalFirstPartySets(kVersion,
                                    {
@@ -163,7 +165,8 @@
   SchemefulSite example(GURL("https://example.test"));
   FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt);
 
-  FirstPartySetsContextConfig config({{example, absl::nullopt}});
+  FirstPartySetsContextConfig config(
+      {{example, net::FirstPartySetEntryOverride()}});
 
   EXPECT_THAT(GlobalFirstPartySets(kVersion,
                                    {
@@ -194,7 +197,8 @@
   FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt);
   FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1);
 
-  FirstPartySetsContextConfig config({{example_cctld, override_entry}});
+  FirstPartySetsContextConfig config(
+      {{example_cctld, net::FirstPartySetEntryOverride(override_entry)}});
 
   EXPECT_THAT(GlobalFirstPartySets(kVersion,
                                    {
@@ -210,7 +214,8 @@
   SchemefulSite example_cctld(GURL("https://example.cctld"));
   FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt);
 
-  FirstPartySetsContextConfig config({{example_cctld, absl::nullopt}});
+  FirstPartySetsContextConfig config(
+      {{example_cctld, net::FirstPartySetEntryOverride()}});
 
   EXPECT_THAT(GlobalFirstPartySets(kVersion,
                                    {
@@ -227,7 +232,8 @@
   FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt);
   FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1);
 
-  FirstPartySetsContextConfig config({{example, override_entry}});
+  FirstPartySetsContextConfig config(
+      {{example, net::FirstPartySetEntryOverride(override_entry)}});
 
   // FindEntry should ignore aliases when using the customizations. Public
   // aliases only apply to sites in the public sets.
@@ -1613,17 +1619,18 @@
   GlobalFirstPartySetsWithConfigTest()
       : config_({
             // New entry:
-            {kPrimary3,
-             {FirstPartySetEntry(kPrimary3,
-                                 SiteType::kPrimary,
-                                 absl::nullopt)}},
+            {kPrimary3, net::FirstPartySetEntryOverride(
+                            FirstPartySetEntry(kPrimary3,
+                                               SiteType::kPrimary,
+                                               absl::nullopt))},
             // Removed entry:
-            {kAssociated1, absl::nullopt},
+            {kAssociated1, net::FirstPartySetEntryOverride()},
             // Remapped entry:
             {kAssociated3,
-             {FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)}},
+             net::FirstPartySetEntryOverride(
+                 FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0))},
             // Removed alias:
-            {kAssociated1Cctld, absl::nullopt},
+            {kAssociated1Cctld, net::FirstPartySetEntryOverride()},
         }) {}
 
   FirstPartySetsContextConfig& config() { return config_; }
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index 6424968..caeaf4e 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -1172,11 +1172,11 @@
 
 void QuicChromiumClientSession::UpdateStreamPriority(
     quic::QuicStreamId id,
-    const spdy::SpdyStreamPrecedence& new_precedence) {
+    const quic::QuicStreamPriority& new_priority) {
   if (headers_include_h2_stream_dependency_ ||
       VersionUsesHttp3(connection()->transport_version())) {
-    auto updates = priority_dependency_state_.OnStreamUpdate(
-        id, new_precedence.spdy3_priority());
+    auto updates =
+        priority_dependency_state_.OnStreamUpdate(id, new_priority.urgency);
     for (auto update : updates) {
       if (!VersionUsesHttp3(connection()->transport_version())) {
         WritePriority(update.id, update.parent_stream_id, update.weight,
@@ -1184,7 +1184,7 @@
       }
     }
   }
-  quic::QuicSpdySession::UpdateStreamPriority(id, new_precedence);
+  quic::QuicSpdySession::UpdateStreamPriority(id, new_priority);
 }
 
 void QuicChromiumClientSession::OnHttp3GoAway(uint64_t id) {
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index 396364ef..48a5d85 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -658,7 +658,7 @@
   void UnregisterStreamPriority(quic::QuicStreamId id, bool is_static) override;
   void UpdateStreamPriority(
       quic::QuicStreamId id,
-      const spdy::SpdyStreamPrecedence& new_precedence) override;
+      const quic::QuicStreamPriority& new_priority) override;
   void OnHttp3GoAway(uint64_t id) override;
   void OnAcceptChFrameReceivedViaAlps(
       const quic::AcceptChFrame& frame) override;
diff --git a/net/quic/quic_chromium_client_stream.cc b/net/quic/quic_chromium_client_stream.cc
index 82b549f..168805d 100644
--- a/net/quic/quic_chromium_client_stream.cc
+++ b/net/quic/quic_chromium_client_stream.cc
@@ -301,7 +301,9 @@
 void QuicChromiumClientStream::Handle::SetPriority(
     const spdy::SpdyStreamPrecedence& precedence) {
   if (stream_)
-    stream_->SetPriority(precedence);
+    stream_->SetPriority(quic::QuicStreamPriority{
+        precedence.spdy3_priority(),
+        quic::QuicStreamPriority::kDefaultIncremental});
 }
 
 void QuicChromiumClientStream::Handle::Reset(
@@ -616,8 +618,8 @@
   net_log_.AddEvent(
       NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
       [&](NetLogCaptureMode capture_mode) {
-        return QuicRequestNetLogParams(
-            id(), &header_block, precedence().spdy3_priority(), capture_mode);
+        return QuicRequestNetLogParams(id(), &header_block, priority().urgency,
+                                       capture_mode);
       });
   size_t len = quic::QuicSpdyStream::WriteHeaders(std::move(header_block), fin,
                                                   std::move(ack_listener));
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index 3e5d0a5a..aa63e952 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -8761,7 +8761,7 @@
             packet_num++, GetNthClientInitiatedBidirectionalStreamId(0), true,
             false,
             VersionUsesHttp3(version_.transport_version)
-                ? quic::QuicStream::kDefaultUrgency
+                ? quic::QuicStreamPriority::kDefaultUrgency
                 : ConvertRequestPriorityToQuicPriority(
                       HttpProxyConnectJob::kH2QuicTunnelPriority),
             client_maker.ConnectRequestHeaders("mail.example.org:443"), 0,
@@ -8814,7 +8814,7 @@
             packet_num++, GetNthClientInitiatedBidirectionalStreamId(1), false,
             false,
             VersionUsesHttp3(version_.transport_version)
-                ? quic::QuicStream::kDefaultUrgency
+                ? quic::QuicStreamPriority::kDefaultUrgency
                 : ConvertRequestPriorityToQuicPriority(
                       HttpProxyConnectJob::kH2QuicTunnelPriority),
             std::move(headers), GetNthClientInitiatedBidirectionalStreamId(0),
@@ -9451,7 +9451,7 @@
             packet_num++, GetNthClientInitiatedBidirectionalStreamId(0), true,
             false,
             VersionUsesHttp3(version_.transport_version)
-                ? quic::QuicStream::kDefaultUrgency
+                ? quic::QuicStreamPriority::kDefaultUrgency
                 : ConvertRequestPriorityToQuicPriority(
                       HttpProxyConnectJob::kH2QuicTunnelPriority),
             ConnectRequestHeaders("mail.example.org:443"), 0, nullptr));
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index d4c0aeaf..0141f75 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -782,7 +782,7 @@
   if (quic::VersionUsesHttp3(version_.transport_version)) {
     MaybeAddHttp3SettingsFrames();
 
-    if (priority != quic::QuicStream::kDefaultUrgency) {
+    if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
       std::string priority_data =
           GenerateHttp3PriorityData(priority, stream_id);
       AddQuicStreamFrame(2, false, priority_data);
@@ -830,7 +830,7 @@
   if (quic::VersionUsesHttp3(version_.transport_version)) {
     MaybeAddHttp3SettingsFrames();
 
-    if (priority != quic::QuicStream::kDefaultUrgency) {
+    if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
       std::string priority_data =
           GenerateHttp3PriorityData(priority, stream_id);
       AddQuicStreamFrame(2, false, priority_data);
@@ -876,7 +876,7 @@
   if (quic::VersionUsesHttp3(version_.transport_version)) {
     MaybeAddHttp3SettingsFrames();
 
-    if (priority != quic::QuicStream::kDefaultUrgency) {
+    if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
       std::string priority_data =
           GenerateHttp3PriorityData(priority, stream_id);
       AddQuicStreamFrame(2, false, priority_data);
@@ -915,7 +915,7 @@
   if (quic::VersionUsesHttp3(version_.transport_version)) {
     MaybeAddHttp3SettingsFrames();
 
-    if (priority != quic::QuicStream::kDefaultUrgency) {
+    if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
       std::string priority_data =
           GenerateHttp3PriorityData(priority, stream_id);
       AddQuicStreamFrame(2, false, priority_data);
@@ -1052,7 +1052,7 @@
 
     return BuildPacket();
   }
-  if (priority != quic::QuicStream::kDefaultUrgency) {
+  if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
     std::string priority_data = GenerateHttp3PriorityData(priority, id);
     AddQuicStreamFrame(2, false, priority_data);
   }
@@ -1089,7 +1089,7 @@
 
     return BuildPacket();
   }
-  if (priority != quic::QuicStream::kDefaultUrgency) {
+  if (priority != quic::QuicStreamPriority::kDefaultUrgency) {
     std::string priority_data = GenerateHttp3PriorityData(priority, id);
     AddQuicStreamFrame(2, false, priority_data);
   }
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index 401d95dc..e4e8253 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -615,6 +615,8 @@
     "src/quiche/quic/core/quic_stream_frame_data_producer.h",
     "src/quiche/quic/core/quic_stream_id_manager.cc",
     "src/quiche/quic/core/quic_stream_id_manager.h",
+    "src/quiche/quic/core/quic_stream_priority.cc",
+    "src/quiche/quic/core/quic_stream_priority.h",
     "src/quiche/quic/core/quic_stream_send_buffer.cc",
     "src/quiche/quic/core/quic_stream_send_buffer.h",
     "src/quiche/quic/core/quic_stream_sequencer.cc",
@@ -1589,6 +1591,7 @@
     "src/quiche/quic/core/quic_session_test.cc",
     "src/quiche/quic/core/quic_socket_address_coder_test.cc",
     "src/quiche/quic/core/quic_stream_id_manager_test.cc",
+    "src/quiche/quic/core/quic_stream_priority_test.cc",
     "src/quiche/quic/core/quic_stream_send_buffer_test.cc",
     "src/quiche/quic/core/quic_stream_sequencer_buffer_test.cc",
     "src/quiche/quic/core/quic_stream_sequencer_test.cc",
diff --git a/remoting/host/me2me_desktop_environment.cc b/remoting/host/me2me_desktop_environment.cc
index acdd5fd1..383fb2b 100644
--- a/remoting/host/me2me_desktop_environment.cc
+++ b/remoting/host/me2me_desktop_environment.cc
@@ -124,12 +124,10 @@
     capabilities += protocol::kRemoteWebAuthnCapability;
   }
 
-#if (BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)) || BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)
   capabilities += " ";
   capabilities += protocol::kMultiStreamCapability;
-#endif  // (BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)) || ...
 
-#if BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)
   // Client-controlled layout is only supported with Xorg+video-dummy.
   if (UsingVideoDummyDriver()) {
     capabilities += " ";
diff --git a/sandbox/win/tests/validation_tests/suite.cc b/sandbox/win/tests/validation_tests/suite.cc
index 2997c934..9aa61406 100644
--- a/sandbox/win/tests/validation_tests/suite.cc
+++ b/sandbox/win/tests/validation_tests/suite.cc
@@ -223,12 +223,13 @@
 
 // Tests if the windows are correctly protected by the sandbox.
 TEST(ValidationSuite, TestWindows) {
-  // Due to a bug in Windows on builds based on the 19041 branch (20H1, 20H2 and
-  // 21H1) this test will fail on these versions. See crbug.com/1057656.
+  // Due to a bug in Windows on builds based on the 19041 branch (20H1, 20H2,
+  // 21H1 and 22H2) this test will fail on these versions. See
+  // crbug.com/1057656.
   base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
   if (os_info->version_number().build >= 19041 &&
-      os_info->version_number().build <= 19043) {
-    return;
+      os_info->version_number().build <= 19045) {
+    GTEST_SKIP() << "Skipping test for Win10 19041 branch, crbug.com/1057656.";
   }
 
   wchar_t command[1024] = {0};
diff --git a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
index f98668fa..4d5d23f 100644
--- a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
@@ -17,6 +17,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
@@ -299,11 +300,11 @@
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent(
       net::FirstPartySetsContextConfig({
           {kSet1Member1,
-           {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated,
-                                    0)}},
+           net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+               kSet3Owner, net::SiteType::kAssociated, 0))},
           {kSet3Owner,
-           {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
-                                    absl::nullopt)}},
+           net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+               kSet3Owner, net::SiteType::kPrimary, absl::nullopt))},
       }),
       /*cache_filter-*/ absl::nullopt));
 
@@ -320,8 +321,8 @@
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent(
       net::FirstPartySetsContextConfig({
           {kSet3Owner,
-           {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
-                                    absl::nullopt)}},
+           net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+               kSet3Owner, net::SiteType::kPrimary, absl::nullopt))},
       }),
       /*cache_filter-*/ absl::nullopt));
 
@@ -336,11 +337,11 @@
     delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent(
         net::FirstPartySetsContextConfig({
             {kSet3Member1,
-             {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated,
-                                      0)}},
+             net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                 kSet3Owner, net::SiteType::kAssociated, 0))},
             {kSet3Owner,
-             {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
-                                      absl::nullopt)}},
+             net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                 kSet3Owner, net::SiteType::kPrimary, absl::nullopt))},
         }),
         net::FirstPartySetsCacheFilter({{kSet1Owner, kClearAtRunId}},
                                        kBrowserRunId)));
@@ -544,8 +545,8 @@
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent(
       net::FirstPartySetsContextConfig(
           {{kSet1Member1,
-            {net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated,
-                                     0)}}}),
+            net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                kSet2Owner, net::SiteType::kAssociated, 0))}}),
       /*cache_filter-*/ absl::nullopt));
   EXPECT_EQ(future.Get(),
             net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty),
@@ -566,8 +567,8 @@
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent(
       net::FirstPartySetsContextConfig(
           {{kSet1Member1,
-            {net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated,
-                                     0)}}}),
+            net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+                kSet2Owner, net::SiteType::kAssociated, 0))}}),
       /*cache_filter-*/ absl::nullopt));
   EXPECT_EQ(
       future.Get(),
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.cc b/services/network/public/cpp/first_party_sets_mojom_traits.cc
index fed0c4a5..4d1e5842 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.cc
@@ -13,6 +13,7 @@
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
@@ -179,11 +180,27 @@
   return true;
 }
 
+bool StructTraits<network::mojom::FirstPartySetEntryOverrideDataView,
+                  net::FirstPartySetEntryOverride>::
+    Read(network::mojom::FirstPartySetEntryOverrideDataView override,
+         net::FirstPartySetEntryOverride* out) {
+  absl::optional<net::FirstPartySetEntry> entry;
+  if (!override.ReadEntry(&entry))
+    return false;
+
+  if (entry.has_value()) {
+    *out = net::FirstPartySetEntryOverride(entry.value());
+  } else {
+    *out = net::FirstPartySetEntryOverride();
+  }
+  return true;
+}
+
 bool StructTraits<network::mojom::FirstPartySetsContextConfigDataView,
                   net::FirstPartySetsContextConfig>::
     Read(network::mojom::FirstPartySetsContextConfigDataView config,
          net::FirstPartySetsContextConfig* out_config) {
-  base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
+  base::flat_map<net::SchemefulSite, net::FirstPartySetEntryOverride>
       customizations;
   if (!config.ReadCustomizations(&customizations))
     return false;
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.h b/services/network/public/cpp/first_party_sets_mojom_traits.h
index fd56019..2c0d432 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.h
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.h
@@ -11,6 +11,7 @@
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
@@ -137,10 +138,23 @@
 
 template <>
 struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
+    StructTraits<network::mojom::FirstPartySetEntryOverrideDataView,
+                 net::FirstPartySetEntryOverride> {
+  static const absl::optional<net::FirstPartySetEntry>& entry(
+      const net::FirstPartySetEntryOverride& override) {
+    return override.entry_;
+  }
+
+  static bool Read(network::mojom::FirstPartySetEntryOverrideDataView override,
+                   net::FirstPartySetEntryOverride* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
     StructTraits<network::mojom::FirstPartySetsContextConfigDataView,
                  net::FirstPartySetsContextConfig> {
   static const base::flat_map<net::SchemefulSite,
-                              absl::optional<net::FirstPartySetEntry>>&
+                              net::FirstPartySetEntryOverride>&
   customizations(const net::FirstPartySetsContextConfig& config) {
     return config.customizations();
   }
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
index b799972..4c4fed91 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
@@ -9,6 +9,7 @@
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "net/base/schemeful_site.h"
 #include "net/first_party_sets/first_party_set_entry.h"
+#include "net/first_party_sets/first_party_set_entry_override.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "net/first_party_sets/first_party_sets_cache_filter.h"
 #include "net/first_party_sets/first_party_sets_context_config.h"
@@ -199,9 +200,11 @@
   net::SchemefulSite c(GURL("https://c.test"));
 
   const net::FirstPartySetsContextConfig original({
-      {a, net::FirstPartySetEntry(a, net::SiteType::kPrimary, absl::nullopt)},
-      {b, net::FirstPartySetEntry(a, net::SiteType::kAssociated, 0)},
-      {c, absl::nullopt},
+      {a, net::FirstPartySetEntryOverride(net::FirstPartySetEntry(
+              a, net::SiteType::kPrimary, absl::nullopt))},
+      {b, net::FirstPartySetEntryOverride(
+              net::FirstPartySetEntry(a, net::SiteType::kAssociated, 0))},
+      {c, net::FirstPartySetEntryOverride()},
   });
 
   net::FirstPartySetsContextConfig round_tripped;
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index b07e36c..fa107688 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -872,6 +872,10 @@
               cpp = "::net::FirstPartySetsCacheFilter"
               move_only = true
             },
+            {
+              mojom = "network.mojom.FirstPartySetEntryOverride"
+              cpp = "::net::FirstPartySetEntryOverride"
+            },
           ]
           traits_headers = [
             "//services/network/public/cpp/first_party_sets_mojom_traits.h",
diff --git a/services/network/public/mojom/first_party_sets.mojom b/services/network/public/mojom/first_party_sets.mojom
index a852322f..150fed9 100644
--- a/services/network/public/mojom/first_party_sets.mojom
+++ b/services/network/public/mojom/first_party_sets.mojom
@@ -54,15 +54,21 @@
   FirstPartySetEntry? top_frame_entry;
 };
 
+struct FirstPartySetEntryOverride {
+  // If `entry` is null, that means the site is considered deleted from the
+  // existing First-Party Sets, for the given context. Otherwise, the site is
+  // considered re-mapped to a potentially different/new First-Party Set, using
+  // the given entry.
+  FirstPartySetEntry? entry;
+};
+
 // This struct must match the class fields defined in
 // //net/first_party_sets/first_party_sets_context_config.h.
 struct FirstPartySetsContextConfig {
   // This map must be computed against the public First-Party Sets, where the
   // keys are member sites and the values are the ultimate entries of the final
-  // First-Party Sets combining the public sets and the modifications. Map
-  // entries of site -> absl::nullopt means the key site is considered deleted
-  // from the existing First-Party Sets, for the given context.
-  map<SchemefulSite, FirstPartySetEntry?> customizations;
+  // First-Party Sets combining the public sets and the modifications.
+  map<SchemefulSite, FirstPartySetEntryOverride> customizations;
 };
 
 // This struct must match the class fields defined in
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index b330273..bdb246879 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -44,6 +44,7 @@
     "SK_UNTIL_CRBUG_1187654_IS_FIXED",
     "SK_USER_CONFIG_HEADER=\"../../skia/config/SkUserConfig.h\"",
     "SK_WIN_FONTMGR_NO_SIMULATIONS",
+    "SK_LEGACY_GET_FILL_PATH",
   ]
 
   include_dirs = [ "//third_party/skia" ]
diff --git a/storage/browser/file_system/native_file_util.cc b/storage/browser/file_system/native_file_util.cc
index 1af723f6..8e43c5b 100644
--- a/storage/browser/file_system/native_file_util.cc
+++ b/storage/browser/file_system/native_file_util.cc
@@ -44,9 +44,12 @@
                          S_IRWXU | S_IRGRP | S_IXGRP | S_IXOTH)) != 0) {
     return false;
   }
-  auto* group = getgrnam("chronos-access");
-  if (!group ||
-      HANDLE_EINTR(chown(dir_path.value().c_str(), -1, group->gr_gid)) != 0) {
+  struct group grp, *result = nullptr;
+  std::vector<char> buffer(16384);
+  getgrnam_r("chronos-access", &grp, buffer.data(), buffer.size(), &result);
+  // Ignoring as the group might not exist in tests.
+  if (result &&
+      HANDLE_EINTR(chown(dir_path.value().c_str(), -1, grp.gr_gid)) != 0) {
     return false;
   }
 #endif
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 35da04f..f68eaeb 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -2740,8 +2740,7 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.base_unittests.filter"
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
         ],
         "merge": {
           "args": [
@@ -6703,8 +6702,7 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android31.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.base_unittests.filter",
-          "--gtest_filter=-ModuleCacheTest.CheckAgainstProcMaps"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.base_unittests.filter"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index ffea668..92a2e31 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -2,9 +2,6 @@
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
   "mac-rel-cft": {
-    "additional_compile_targets": [
-      "all"
-    ],
     "gtest_tests": [
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index ed62952..2a8eb26 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1494,9 +1494,9 @@
       {
         "args": [],
         "bucket": "chromiumos-image-archive",
-        "cros_board": "eve",
-        "cros_img": "eve-public/R110-15264.0.0",
-        "name": "lacros_all_tast_tests EVE_PUBLIC",
+        "cros_board": "volteer",
+        "cros_img": "volteer-public/R110-15270.0.0",
+        "name": "lacros_all_tast_tests VOLTEER_PUBLIC",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -1506,7 +1506,7 @@
         "test": "lacros_all_tast_tests",
         "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
         "timeout_sec": 10800,
-        "variant_id": "EVE_PUBLIC"
+        "variant_id": "VOLTEER_PUBLIC"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index fc739a5..2de2fc1 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -2154,7 +2154,7 @@
       },
       {
         "args": [
-          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter;../../testing/buildbot/filters/fuchsia.lsan.service_unittests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index c9d0d8c..c7b571d2 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -15657,8 +15657,7 @@
       },
       {
         "args": [
-          "--use-gpu-in-tests",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/win.intel.gl_unittests.filter"
+          "--use-gpu-in-tests"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index b2ba859..cf6aba9 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -31,7 +31,7 @@
   testonly = true
 
   data = [
-    "//testing/buildbot/filters/android.emulator.base_unittests.filter",
+    "//testing/buildbot/filters/android.emulator_12.base_unittests.filter",
     "//testing/buildbot/filters/android.pie_tot.base_unittests.filter",
     "//testing/buildbot/filters/fuchsia.lsan.base_unittests.filter",
   ]
@@ -217,6 +217,7 @@
   data = [
     "//testing/buildbot/filters/android.pie_tot.services_unittests.filter",
     "//testing/buildbot/filters/fuchsia.services_unittests.filter",
+    "//testing/buildbot/filters/fuchsia.lsan.services_unittests.filter",
   ]
 }
 
@@ -269,10 +270,8 @@
 source_set("gl_unittests_filters") {
   testonly = true
 
-  data = [
-    "//testing/buildbot/filters/android.emulator_12.gl_unittests.filter",
-    "//testing/buildbot/filters/win.intel.gl_unittests.filter",
-  ]
+  data =
+      [ "//testing/buildbot/filters/android.emulator_12.gl_unittests.filter" ]
 }
 
 source_set("interactive_ui_tests_filters") {
diff --git a/testing/buildbot/filters/android.emulator.base_unittests.filter b/testing/buildbot/filters/android.emulator.base_unittests.filter
deleted file mode 100644
index 58d99754..0000000
--- a/testing/buildbot/filters/android.emulator.base_unittests.filter
+++ /dev/null
@@ -1,2 +0,0 @@
-# crbug.com/1188310
--IcuUtilTest.InitializeExtraIcuFromFdSucceeds
diff --git a/testing/buildbot/filters/android.emulator_12.base_unittests.filter b/testing/buildbot/filters/android.emulator_12.base_unittests.filter
new file mode 100644
index 0000000..b109d6a1
--- /dev/null
+++ b/testing/buildbot/filters/android.emulator_12.base_unittests.filter
@@ -0,0 +1,5 @@
+# crbug.com/1260521
+-ModuleCacheTest.CheckAgainstProcMaps
+
+# crbug.com/1399972
+-CheckExitCodeAfterSignalHandlerDeathTest.CheckSIGSEGVNonCanonicalAddress
diff --git a/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter b/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter
index 5ca7f21..c0f36c5 100644
--- a/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter
+++ b/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter
@@ -34,3 +34,6 @@
 -BrokerProcessIntegrationTest.UnlinkNonexistentNoPermissions
 -BrokerProcessIntegrationTest.UnlinkNonexistentROPermissions
 -BrokerProcessIntegrationTest.UnlinkNonExistentRWPermissions
+
+# crbug.com/1368296
+-BrokerProcessIntegrationTest.TestOpenFilePermsENOENT
diff --git a/testing/buildbot/filters/fuchsia.lsan.services_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.services_unittests.filter
new file mode 100644
index 0000000..80e1a813
--- /dev/null
+++ b/testing/buildbot/filters/fuchsia.lsan.services_unittests.filter
@@ -0,0 +1,6 @@
+# TODO(crbug.com/1396449): Enable these tests when their memory leaks are fixed.
+-AutomationInternalBindingsTest.CatchesExceptions
+-AutomationInternalBindingsTest.GetsFocusAndNodeData
+-AutomationInternalBindingsTest.GetsLocation
+-AutomationInternalBindingsTest.SpotCheckBindingsAdded
+-V8ManagerTest.ExecutesSimpleScript
diff --git a/testing/buildbot/filters/win.intel.gl_unittests.filter b/testing/buildbot/filters/win.intel.gl_unittests.filter
deleted file mode 100644
index 4764a92..0000000
--- a/testing/buildbot/filters/win.intel.gl_unittests.filter
+++ /dev/null
@@ -1,5 +0,0 @@
-# crbug.com/1376203
--DelegatedInkPointRendererGpuTest.DrawPointsAsTheyArrive
--DelegatedInkPointRendererGpuTest.MaximumPointerIds
--DelegatedInkPointRendererGpuTest.MultiplePointerIds
--DelegatedInkPointRendererGpuTest.StoreAndRemovePointsAndTokens
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index af75d7a..08c9ddc 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -144,15 +144,9 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_tot.base_unittests.filter',
         ],
       },
-      'android-11-x86-rel': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.base_unittests.filter',
-        ],
-      },
       'android-12-x64-rel': {
         'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.base_unittests.filter',
-          '--gtest_filter=-ModuleCacheTest.CheckAgainstProcMaps',  # crbug.com/1260521
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.base_unittests.filter',
         ],
       },
       'fuchsia-code-coverage': {
@@ -2237,11 +2231,6 @@
       'Linux MSan Tests',
     ],
     'modifications': {
-      'Win10 FYI x64 Exp Release (Intel HD 630)': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/win.intel.gl_unittests.filter',
-        ],
-      },
       'android-12-x64-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.gl_unittests.filter',
@@ -3236,6 +3225,11 @@
           'shards': 7,
         },
       },
+      'fuchsia-fyi-x64-asan': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.service_unittests.filter',
+        ],
+      },
     }
   },
   'snapshot_unittests': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 17b0f38..b68e458 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -7288,7 +7288,7 @@
     'lacros_skylab_tests_amd64_generic_rel': {
       'lacros_skylab_tests': {
         'variants': [
-          'CROS_EVE_PUBLIC',
+          'CROS_VOLTEER_PUBLIC',
         ]
       },
     },
diff --git a/testing/buildbot/tryserver.chromium.chromiumos.json b/testing/buildbot/tryserver.chromium.chromiumos.json
index bbc3872..1b15ec8a 100644
--- a/testing/buildbot/tryserver.chromium.chromiumos.json
+++ b/testing/buildbot/tryserver.chromium.chromiumos.json
@@ -9,9 +9,9 @@
       {
         "args": [],
         "bucket": "chromiumos-image-archive",
-        "cros_board": "eve",
-        "cros_img": "eve-public/R110-15264.0.0",
-        "name": "lacros_all_tast_tests EVE_PUBLIC",
+        "cros_board": "volteer",
+        "cros_img": "volteer-public/R110-15270.0.0",
+        "name": "lacros_all_tast_tests VOLTEER_PUBLIC",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -21,7 +21,7 @@
         "test": "lacros_all_tast_tests",
         "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
         "timeout_sec": 10800,
-        "variant_id": "EVE_PUBLIC"
+        "variant_id": "VOLTEER_PUBLIC"
       }
     ]
   }
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 621b412..52179ca 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -503,16 +503,6 @@
     'enabled': True,
     'identifier': 'DEDEDE_RELEASE_STABLE',
   },
-  'CROS_EVE_PUBLIC': {
-    'skylab': {
-      'cros_board': 'eve',
-      'cros_chrome_version': '110.0.5447.0',
-      'cros_img': 'eve-public/R110-15264.0.0',
-      'bucket': 'chromiumos-image-archive',
-    },
-    'enabled': True,
-    'identifier': 'EVE_PUBLIC',
-  },
   'CROS_EVE_RELEASE_LKGM': {
     'skylab': {
       'cros_board': 'eve',
@@ -752,6 +742,16 @@
     'enabled': True,
     'identifier': 'TROGDOR64_RELEASE_LKGM',
   },
+  'CROS_VOLTEER_PUBLIC': {
+    'skylab': {
+      'cros_board': 'volteer',
+      'cros_chrome_version': '110.0.5460.0',
+      'cros_img': 'volteer-public/R110-15270.0.0',
+      'bucket': 'chromiumos-image-archive',
+    },
+    'enabled': True,
+    'identifier': 'VOLTEER_PUBLIC',
+  },
   'LACROS_AMD64_GENERIC': {
     'args': [
       '--board=amd64-generic',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index dd9c39de..127616c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1270,9 +1270,14 @@
           'mac_12_x64',
           'isolate_profile_data',
         ],
-        'additional_compile_targets': [
-          'all',
-        ],
+        # TODO(crbug.com/1399527) - for some reason gcapi_example
+        # is failing to build in this config. For now, just try
+        # to build the test binaries and we can decide if we really
+        # need everything to build later.
+        #
+        # 'additional_compile_targets': [
+        #   'all',
+        # ],
         'test_suites': {
           'gtest_tests': 'chromium_mac_gtests_no_nacl',
           'isolated_scripts': 'chromium_mac_rel_isolated_scripts',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0124dddddc..8551006 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4529,6 +4529,26 @@
             ]
         }
     ],
+    "ExcludeLowEntropyImagesFromLCP": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Control_V1",
+                    "disable_features": [
+                        "ExcludeLowEntropyImagesFromLCP"
+                    ]
+                }
+            ]
+        }
+    ],
     "ExploreSitesDense": [
         {
             "platforms": [
@@ -10955,6 +10975,21 @@
             ]
         }
     ],
+    "TabSwitchMetrics2Android": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "OnShowWithPageVisibility"
+                    ]
+                }
+            ]
+        }
+    ],
     "TailoredSecurityIntegration": [
         {
             "platforms": [
@@ -11267,21 +11302,6 @@
             ]
         }
     ],
-    "UnicornAccountSupport": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableUnicornAccountSupport"
-                    ]
-                }
-            ]
-        }
-    ],
     "UnifiedAutoplay": [
         {
             "platforms": [
diff --git a/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h b/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
index 10b0957..976ee35f 100644
--- a/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
+++ b/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
@@ -95,6 +95,7 @@
   RedactedFencedFrameConfig();
   ~RedactedFencedFrameConfig();
 
+  const absl::optional<GURL>& urn() const { return urn_; }
   const absl::optional<RedactedFencedFrameProperty<GURL>>& mapped_url() const {
     return mapped_url_;
   }
@@ -126,6 +127,7 @@
   FRIEND_TEST_ALL_PREFIXES(::content::FencedFrameConfigMojomTraitsTest,
                            ConfigMojomTraitsTest);
 
+  absl::optional<GURL> urn_;
   absl::optional<RedactedFencedFrameProperty<GURL>> mapped_url_;
   absl::optional<RedactedFencedFrameProperty<AdAuctionData>> ad_auction_data_;
   absl::optional<
@@ -147,6 +149,7 @@
   RedactedFencedFrameProperties();
   ~RedactedFencedFrameProperties();
 
+  const absl::optional<GURL>& urn() const { return urn_; }
   const absl::optional<RedactedFencedFrameProperty<GURL>>& mapped_url() const {
     return mapped_url_;
   }
@@ -178,6 +181,7 @@
   FRIEND_TEST_ALL_PREFIXES(::content::FencedFrameConfigMojomTraitsTest,
                            ConfigMojomTraitsTest);
 
+  absl::optional<GURL> urn_;
   absl::optional<RedactedFencedFrameProperty<GURL>> mapped_url_;
   absl::optional<RedactedFencedFrameProperty<AdAuctionData>> ad_auction_data_;
   absl::optional<RedactedFencedFrameProperty<
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index dbb21d9..46e3314 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3764,6 +3764,7 @@
   kDialogCloseWatcherCancelSkippedAndDefaultPrevented = 4423,
   kDialogCloseWatcherCloseSignalClosedMultiple = 4424,
   kNoVarySearch = 4425,
+  kFedCmUserInfo = 4426,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 400653a90..1ba87c3 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -545,6 +545,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_request_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_user_info.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_user_info.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idle_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idle_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_iir_filter_options.cc",
@@ -1943,6 +1945,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idb_version_change_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idle_detector.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_idle_detector.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_iir_filter_node.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index e11afdb6..293f44c 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -164,6 +164,7 @@
           "//third_party/blink/renderer/modules/credentialmanagement/identity_credential.idl",
           "//third_party/blink/renderer/modules/credentialmanagement/identity_credential_logout_r_ps_request.idl",
           "//third_party/blink/renderer/modules/credentialmanagement/identity_credential_request_options.idl",
+          "//third_party/blink/renderer/modules/credentialmanagement/identity_provider.idl",
           "//third_party/blink/renderer/modules/credentialmanagement/identity_provider_config.idl",
           "//third_party/blink/renderer/modules/credentialmanagement/navigator_credentials.idl",
           "//third_party/blink/renderer/modules/credentialmanagement/otp_credential.idl",
diff --git a/third_party/blink/renderer/core/clipboard/data_transfer.cc b/third_party/blink/renderer/core/clipboard/data_transfer.cc
index bc66bd5..7659110 100644
--- a/third_party/blink/renderer/core/clipboard/data_transfer.cc
+++ b/third_party/blink/renderer/core/clipboard/data_transfer.cc
@@ -50,7 +50,6 @@
 #include "third_party/blink/renderer/core/page/drag_image.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/cull_rect_updater.h"
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
@@ -130,8 +129,6 @@
         gfx::Vector2dF(layer->GetLayoutObject().FirstFragment().PaintOffset()));
     OverriddenCullRectScope cull_rect_scope(
         *layer, CullRect(gfx::ToEnclosingRect(cull_rect)));
-    OverriddenOldCullRectScope old_cull_rect_scope(
-        *layer, CullRect(gfx::ToEnclosingRect(cull_rect)));
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
 
     dragged_layout_object->GetDocument().Lifecycle().AdvanceTo(
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
index 2d27e89..bbead1ed 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
@@ -617,6 +617,12 @@
   }
 }
 
+// static
+bool DisplayLockUtilities::NeedsSelectionChangedUpdate(
+    const Document& document) {
+  return document.GetDisplayLockDocumentState().DisplayLockCount() > 0;
+}
+
 void DisplayLockUtilities::SelectionChanged(
     const EphemeralRangeInFlatTree& old_selection,
     const EphemeralRangeInFlatTree& new_selection) {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
index 471c2bf..cfde6cc 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -293,6 +293,8 @@
   static void ElementLostFocus(Element*);
   static void ElementGainedFocus(Element*);
 
+  // Returns true if the selection changed functions need to be called.
+  static bool NeedsSelectionChangedUpdate(const Document& document);
   static void SelectionChanged(const EphemeralRangeInFlatTree& old_selection,
                                const EphemeralRangeInFlatTree& new_selection);
   static void SelectionRemovedFromDocument(Document& document);
diff --git a/third_party/blink/renderer/core/dom/container_node.cc b/third_party/blink/renderer/core/dom/container_node.cc
index 32c67de1d..79b6586 100644
--- a/third_party/blink/renderer/core/dom/container_node.cc
+++ b/third_party/blink/renderer/core/dom/container_node.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/html/forms/radio_node_list.h"
 #include "third_party/blink/renderer/core/html/html_collection.h"
+#include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/html/html_document.h"
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 #include "third_party/blink/renderer/core/html/html_tag_collection.h"
@@ -1628,18 +1629,28 @@
 
 // https://html.spec.whatwg.org/C/#autofocus-delegate
 Element* ContainerNode::GetAutofocusDelegate() const {
-  for (Node& node : NodeTraversal::DescendantsOf(*this)) {
-    auto* element = DynamicTo<Element>(node);
-    if (!element)
-      continue;
+  Element* element = ElementTraversal::Next(*this, this);
+  while (element) {
+    // TODO(jarhar): Add this to the HTML spec as a followup to the popover PR
+    if (auto* html_element = DynamicTo<HTMLElement>(element)) {
+      if (DynamicTo<HTMLDialogElement>(html_element) ||
+          html_element->HasPopoverAttribute()) {
+        element = ElementTraversal::NextSkippingChildren(*element, this);
+        continue;
+      }
+    }
 
-    if (!element->IsAutofocusable())
+    if (!element->IsAutofocusable()) {
+      element = ElementTraversal::Next(*element, this);
       continue;
+    }
 
     Element* focusable_area =
         element->IsFocusable() ? element : element->GetFocusableArea();
-    if (!focusable_area)
+    if (!focusable_area) {
+      element = ElementTraversal::Next(*element, this);
       continue;
+    }
 
     // The spec says to continue instead of returning focusable_area if
     // focusable_area is not click-focusable and the call was initiated by the
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 28d55b2a..f26a9fa1 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4744,13 +4744,14 @@
 
   DCHECK(AuthorShadowRoot());
   if (RuntimeEnabledFeatures::DialogNewFocusBehaviorEnabled()) {
-    return GetFocusDelegate(in_descendant_traversal);
+    return GetFocusDelegate(/*autofocus_only=*/false, in_descendant_traversal);
   } else {
     return FocusController::FindFocusableElementInShadowHost(*this);
   }
 }
 
-Element* Element::GetFocusDelegate(bool in_descendant_traversal) const {
+Element* Element::GetFocusDelegate(bool autofocus_only,
+                                   bool in_descendant_traversal) const {
   ShadowRoot* shadowroot = AuthorShadowRoot();
   if (shadowroot && !shadowroot->delegatesFocus())
     return nullptr;
@@ -4762,6 +4763,9 @@
   if (Element* autofocus_delegate = where_to_look->GetAutofocusDelegate())
     return autofocus_delegate;
 
+  if (autofocus_only)
+    return nullptr;
+
   for (Element& descendant : ElementTraversal::DescendantsOf(*where_to_look)) {
     if (descendant.IsFocusable())
       return &descendant;
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 8ee09ee..4f0af82 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -748,7 +748,8 @@
   // GetFocusableArea calls GetFocusDelegate, this allows us to skip redundant
   // recursive calls to the same descendants.
   Element* GetFocusableArea(bool in_descendant_traversal = false) const;
-  Element* GetFocusDelegate(bool in_descendant_traversal = false) const;
+  Element* GetFocusDelegate(bool autofocus_only,
+                            bool in_descendant_traversal = false) const;
   // Element focus function called through IDL (i.e. element.focus() in JS)
   // Delegates to Focus() with focus type set to kScript
   void focusForBindings(const FocusOptions*);
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
index cd856a5b..6b89a5a 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
@@ -148,30 +148,34 @@
 }
 
 ContainerNode* FlatTreeTraversal::TraverseParent(const Node& node) {
+  // This code is called extensively, so it minimizes repetitive work (such
+  // as avoiding multiple calls to parentElement()).
+
   // TODO(hayato): Stop this hack for a pseudo element because a pseudo element
   // is not a child of its parentOrShadowHostNode() in a flat tree.
   if (node.IsPseudoElement())
     return node.ParentOrShadowHostNode();
 
-  if (node.IsChildOfShadowHost())
-    return node.AssignedSlot();
-
-  if (auto* parent_slot =
-          ToHTMLSlotElementIfSupportsAssignmentOrNull(node.parentElement())) {
-    if (!parent_slot->AssignedNodes().empty())
-      return nullptr;
-    return parent_slot;
-  }
-  return TraverseParentOrHost(node);
-}
-
-ContainerNode* FlatTreeTraversal::TraverseParentOrHost(const Node& node) {
-  ContainerNode* parent = node.parentNode();
-  if (!parent)
+  ContainerNode* parent_node = node.parentNode();
+  if (!parent_node)
     return nullptr;
-  auto* shadow_root = DynamicTo<ShadowRoot>(parent);
+
+  if (Element* parent_element = DynamicTo<Element>(parent_node)) {
+    if (parent_element->GetShadowRoot())
+      return node.AssignedSlot();
+
+    if (auto* parent_slot =
+            ToHTMLSlotElementIfSupportsAssignmentOrNull(*parent_element)) {
+      if (!parent_slot->AssignedNodes().empty())
+        return nullptr;
+      return parent_slot;
+    }
+  }
+
+  auto* shadow_root = DynamicTo<ShadowRoot>(parent_node);
   if (!shadow_root)
-    return parent;
+    return parent_node;
+
   return &shadow_root->host();
 }
 
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.h b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
index bb1d8b4..ccfbb778 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.h
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
@@ -193,7 +193,6 @@
   static Node* TraverseChild(const Node&, TraversalDirection);
 
   static ContainerNode* TraverseParent(const Node&);
-  static ContainerNode* TraverseParentOrHost(const Node&);
 
   static Node* TraverseNextSibling(const Node&);
   static Node* TraversePreviousSibling(const Node&);
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index 3241039..d1bfbde0 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -338,7 +338,11 @@
   }
 
   DISABLE_CFI_PERF bool IsPseudoElement() const {
-    return GetPseudoId() != kPseudoIdNone;
+#if DCHECK_IS_ON()
+    DCHECK_EQ(HasRareData() && DataAsNodeRareData()->IsPseudoElement(),
+              GetPseudoId() != kPseudoIdNone);
+#endif
+    return HasRareData() && DataAsNodeRareData()->IsPseudoElement();
   }
   DISABLE_CFI_PERF bool IsBeforePseudoElement() const {
     return GetPseudoId() == kPseudoIdBefore;
diff --git a/third_party/blink/renderer/core/dom/node_rare_data.h b/third_party/blink/renderer/core/dom/node_rare_data.h
index 56e1ec3..5d7202f 100644
--- a/third_party/blink/renderer/core/dom/node_rare_data.h
+++ b/third_party/blink/renderer/core/dom/node_rare_data.h
@@ -106,6 +106,7 @@
   explicit NodeData(ClassType sub_type)
       : connected_frame_count_(0),
         element_flags_(0),
+        is_pseudo_element_(false),
         bit_field_(RestyleFlags::encode(0) |
                    ClassTypeData::encode(static_cast<uint8_t>(sub_type))) {}
 
@@ -115,6 +116,7 @@
 
   uint16_t connected_frame_count_ : kConnectedFrameCountBits;
   uint16_t element_flags_ : kNumberOfElementFlags;
+  bool is_pseudo_element_ : 1;
   BitField bit_field_;
 
   friend struct DowncastTraits<NodeRareData>;
@@ -243,6 +245,9 @@
   void UnregisterScrollTimeline(ScrollTimeline*);
   void InvalidateAssociatedAnimationEffects();
 
+  void SetIsPseudoElement(bool value) { is_pseudo_element_ = value; }
+  bool IsPseudoElement() const { return is_pseudo_element_; }
+
   void Trace(blink::Visitor*) const override;
 
  protected:
diff --git a/third_party/blink/renderer/core/dom/pseudo_element.cc b/third_party/blink/renderer/core/dom/pseudo_element.cc
index f3441ad7..46b4ebf 100644
--- a/third_party/blink/renderer/core/dom/pseudo_element.cc
+++ b/third_party/blink/renderer/core/dom/pseudo_element.cc
@@ -178,6 +178,7 @@
   parent->GetTreeScope().AdoptIfNeeded(*this);
   SetParentOrShadowHostNode(parent);
   SetHasCustomStyleCallbacks();
+  EnsureRareData().SetIsPseudoElement(true);
   if ((pseudo_id == kPseudoIdBefore || pseudo_id == kPseudoIdAfter) &&
       parent->HasTagName(html_names::kInputTag)) {
     UseCounter::Count(parent->GetDocument(),
diff --git a/third_party/blink/renderer/core/dom/range.cc b/third_party/blink/renderer/core/dom/range.cc
index 08d5c123..20207702 100644
--- a/third_party/blink/renderer/core/dom/range.cc
+++ b/third_party/blink/renderer/core/dom/range.cc
@@ -1414,19 +1414,21 @@
   BoundaryShadowNodeChildrenWillBeRemoved(end_, container);
 }
 
-static inline void BoundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary,
+// Returns true if `boundary` was modified.
+static inline bool BoundaryNodeWillBeRemoved(RangeBoundaryPoint& boundary,
                                              Node& node_to_be_removed) {
   if (boundary.ChildBefore() == node_to_be_removed) {
     boundary.ChildBeforeWillBeRemoved();
-    return;
+    return true;
   }
 
   for (Node* n = &boundary.Container(); n; n = n->parentNode()) {
     if (n == node_to_be_removed) {
       boundary.SetToBeforeChild(node_to_be_removed);
-      return;
+      return true;
     }
   }
+  return false;
 }
 
 static inline void BoundaryShadowNodeWillBeRemoved(RangeBoundaryPoint& boundary,
@@ -1450,8 +1452,14 @@
   // should change following if-statement to DCHECK(!node->parentNode).
   if (!node.parentNode())
     return;
-  BoundaryNodeWillBeRemoved(start_, node);
-  BoundaryNodeWillBeRemoved(end_, node);
+  const bool is_collapsed = collapsed();
+  const bool start_updated = BoundaryNodeWillBeRemoved(start_, node);
+  if (is_collapsed) {
+    if (start_updated)
+      end_ = start_;
+  } else {
+    BoundaryNodeWillBeRemoved(end_, node);
+  }
 }
 
 void Range::FixupRemovedNodeAcrossShadowBoundary(Node& node) {
diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 518ad4e4..90ee0fcc 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -28,6 +28,7 @@
 #include <stdio.h>
 
 #include "base/auto_reset.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/accessibility/blink_ax_event_intent.h"
@@ -257,29 +258,18 @@
   if (!is_changed && is_handle_visible_ == should_show_handle &&
       is_directional_ == options.IsDirectional())
     return false;
+  Document& current_document = GetDocument();
   if (is_changed) {
     AssertUserSelection(new_selection, options);
     selection_editor_->SetSelectionAndEndTyping(new_selection);
-
-    // The old selection might not be valid, and thus not iteratable. If that's
-    // the case, notify that all selection was removed and use an empty range as
-    // the old selection.
-    EphemeralRangeInFlatTree old_range;
-    if (old_selection_in_dom_tree.IsValidFor(GetDocument())) {
-      old_range =
-          ToEphemeralRangeInFlatTree(old_selection_in_dom_tree.ComputeRange());
-    } else {
-      DisplayLockUtilities::SelectionRemovedFromDocument(GetDocument());
-    }
-    DisplayLockUtilities::SelectionChanged(
-        old_range, ToEphemeralRangeInFlatTree(new_selection.ComputeRange()));
+    NotifyDisplayLockForSelectionChange(
+        current_document, old_selection_in_dom_tree, new_selection);
   }
   is_directional_ = options.IsDirectional();
   should_shrink_next_tap_ = options.ShouldShrinkNextTap();
   is_handle_visible_ = should_show_handle;
   ScheduleVisualUpdateForPaintInvalidationIfNeeded();
 
-  const Document& current_document = GetDocument();
   frame_->GetEditor().RespondToChangedSelection();
   DCHECK_EQ(current_document, GetDocument());
   return true;
@@ -297,15 +287,17 @@
   // If the selection is currently being modified via the "Modify" method, we
   // should already have more detailed information on the stack than can be
   // deduced in this method.
-  ScopedBlinkAXEventIntent scoped_blink_ax_event_intent(
-      is_being_modified_
-          ? BlinkAXEventIntent()
-          : new_selection.IsNone()
-                ? BlinkAXEventIntent::FromClearedSelection(set_selection_by)
-                : BlinkAXEventIntent::FromNewSelection(
-                      options.Granularity(), new_selection.IsBaseFirst(),
-                      set_selection_by),
-      &current_document);
+  absl::optional<ScopedBlinkAXEventIntent> scoped_blink_ax_event_intent;
+  if (current_document.ExistingAXObjectCache()) {
+    scoped_blink_ax_event_intent.emplace(
+        is_being_modified_ ? BlinkAXEventIntent()
+        : new_selection.IsNone()
+            ? BlinkAXEventIntent::FromClearedSelection(set_selection_by)
+            : BlinkAXEventIntent::FromNewSelection(options.Granularity(),
+                                                   new_selection.IsBaseFirst(),
+                                                   set_selection_by),
+        &current_document);
+  }
 
   if (!new_selection.IsNone() && !options.DoNotSetFocus()) {
     SetFocusedNodeIfNeeded();
@@ -452,11 +444,15 @@
       frame_->GetEditor().Behavior().ShouldSkipSpaceWhenMovingRight()
           ? PlatformWordBehavior::kWordSkipSpaces
           : PlatformWordBehavior::kWordDontSkipSpaces;
-  ScopedBlinkAXEventIntent scoped_blink_ax_event_intent(
-      BlinkAXEventIntent::FromModifiedSelection(
-          alter, direction, granularity, set_selection_by,
-          selection_modifier.DirectionOfSelection(), platform_word_behavior),
-      &GetDocument());
+  Document& document = GetDocument();
+  absl::optional<ScopedBlinkAXEventIntent> scoped_blink_ax_event_intent;
+  if (document.ExistingAXObjectCache()) {
+    scoped_blink_ax_event_intent.emplace(
+        BlinkAXEventIntent::FromModifiedSelection(
+            alter, direction, granularity, set_selection_by,
+            selection_modifier.DirectionOfSelection(), platform_word_behavior),
+        &document);
+  }
 
   // For MacOS only selection is directionless at the beginning.
   // Selection gets direction on extent.
@@ -875,6 +871,28 @@
   frame_->GetEventHandler().GetSelectionController().NotifySelectionChanged();
 }
 
+void FrameSelection::NotifyDisplayLockForSelectionChange(
+    Document& document,
+    const SelectionInDOMTree& old_selection,
+    const SelectionInDOMTree& new_selection) {
+  if (DisplayLockUtilities::NeedsSelectionChangedUpdate(document) ||
+      (!old_selection.IsNone() && old_selection.GetDocument() != document &&
+       DisplayLockUtilities::NeedsSelectionChangedUpdate(
+           *old_selection.GetDocument()))) {
+    // The old selection might not be valid, and thus not iterable. If
+    // that's the case, notify that all selection was removed and use an empty
+    // range as the old selection.
+    EphemeralRangeInFlatTree old_range;
+    if (old_selection.IsValidFor(document)) {
+      old_range = ToEphemeralRangeInFlatTree(old_selection.ComputeRange());
+    } else {
+      DisplayLockUtilities::SelectionRemovedFromDocument(document);
+    }
+    DisplayLockUtilities::SelectionChanged(
+        old_range, ToEphemeralRangeInFlatTree(new_selection.ComputeRange()));
+  }
+}
+
 void FrameSelection::FocusedOrActiveStateChanged() {
   bool active_and_focused = FrameIsFocusedAndActive();
 
diff --git a/third_party/blink/renderer/core/editing/frame_selection.h b/third_party/blink/renderer/core/editing/frame_selection.h
index 910beda2..ade2439 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.h
+++ b/third_party/blink/renderer/core/editing/frame_selection.h
@@ -329,6 +329,10 @@
   void NotifyAccessibilityForSelectionChange();
   void NotifyCompositorForSelectionChange();
   void NotifyEventHandlerForSelectionChange();
+  void NotifyDisplayLockForSelectionChange(
+      Document& document,
+      const SelectionInDOMTree& old_selection,
+      const SelectionInDOMTree& new_selection);
 
   void FocusedOrActiveStateChanged();
 
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 926b4d2..21e0f38 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -700,6 +700,13 @@
   return widget_base_->LayerTreeHost();
 }
 
+void WebPagePopupImpl::OnCommitRequested() {
+  if (page_ && page_->MainFrame()) {
+    if (auto* view = MainFrame().View())
+      view->OnCommitRequested();
+  }
+}
+
 void WebPagePopupImpl::BeginMainFrame(base::TimeTicks last_frame_time) {
   if (!page_)
     return;
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.h b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
index 07001d3..db0a7ba 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.h
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
@@ -136,6 +136,7 @@
 
  private:
   // WidgetBaseClient overrides:
+  void OnCommitRequested() override;
   void BeginMainFrame(base::TimeTicks last_frame_time) override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
   WebInputEventResult DispatchBufferedTouchEvents() override;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 299ff3e..c1001047 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1110,8 +1110,8 @@
 
 LocalFrame& LocalFrame::LocalFrameRoot() const {
   const LocalFrame* cur_frame = this;
-  while (cur_frame && IsA<LocalFrame>(cur_frame->Tree().Parent()))
-    cur_frame = To<LocalFrame>(cur_frame->Tree().Parent());
+  while (cur_frame && IsA<LocalFrame>(cur_frame->Parent()))
+    cur_frame = To<LocalFrame>(cur_frame->Parent());
 
   return const_cast<LocalFrame&>(*cur_frame);
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
index fca6a62a..fa2a8dd 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
@@ -224,6 +224,8 @@
 void LocalFrameUkmAggregator::BeginMainFrame() {
   DCHECK(!in_main_frame_update_);
   in_main_frame_update_ = true;
+  request_timestamp_for_current_frame_ = animation_request_timestamp_;
+  animation_request_timestamp_.reset();
 }
 
 std::unique_ptr<cc::BeginMainFrameMetrics>
@@ -458,6 +460,9 @@
     base::TimeTicks start,
     base::TimeTicks end,
     cc::ActiveFrameSequenceTrackers trackers) {
+  last_frame_request_timestamp_for_test_ =
+      request_timestamp_for_current_frame_.value_or(base::TimeTicks());
+
   const int64_t count = (end - start).InMicroseconds();
   const bool have_valid_metrics =
       // Any of the early outs in LocalFrameView::UpdateLifecyclePhases() will
@@ -475,6 +480,11 @@
     return;
   }
 
+  if (request_timestamp_for_current_frame_.has_value()) {
+    RecordTimerSample(kVisualUpdateDelay,
+                      request_timestamp_for_current_frame_.value(), start);
+  }
+
   bool report_as_pre_fcp = (fcp_state_ != kHavePassedFCP);
   bool report_fcp_metrics = (fcp_state_ == kThisFrameReachedFCP);
 
@@ -587,6 +597,7 @@
   RECORD_METRIC(MediaIntersectionObserver);
   RECORD_METRIC(AnchorElementMetricsIntersectionObserver);
   RECORD_METRIC(UpdateViewportIntersection);
+  RECORD_METRIC(VisualUpdateDelay);
   RECORD_METRIC(UserDrivenDocumentUpdate);
   RECORD_METRIC(ServiceDocumentUpdate);
   RECORD_METRIC(ContentDocumentUpdate);
@@ -641,6 +652,7 @@
   RECORD_METRIC(MediaIntersectionObserver);
   RECORD_METRIC(AnchorElementMetricsIntersectionObserver);
   RECORD_METRIC(UpdateViewportIntersection);
+  RECORD_METRIC(VisualUpdateDelay);
   RECORD_METRIC(UserDrivenDocumentUpdate);
   RECORD_METRIC(ServiceDocumentUpdate);
   RECORD_METRIC(ContentDocumentUpdate);
@@ -662,6 +674,7 @@
   primary_metric_.reset();
   for (auto& record : absolute_metric_records_)
     record.reset();
+  request_timestamp_for_current_frame_.reset();
 }
 
 void LocalFrameUkmAggregator::ChooseNextFrameForTest() {
@@ -676,4 +689,12 @@
   return fcp_state_ == kBeforeFCPSignal;
 }
 
+void LocalFrameUkmAggregator::OnCommitRequested() {
+  // This can't be a DCHECK because this method can be called during the early
+  // stages of cc::ProxyMain::BeginMainFrame, before
+  // LocalFrameUkmAggregator::BeginMainFrame() has been invoked.
+  if (!animation_request_timestamp_.has_value())
+    animation_request_timestamp_.emplace(clock_->NowTicks());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
index f6857b5..fafdb2af 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
@@ -144,6 +144,7 @@
     kMediaIntersectionObserver,
     kAnchorElementMetricsIntersectionObserver,
     kUpdateViewportIntersection,
+    kVisualUpdateDelay,
     kForcedStyleAndLayout,
     kContentDocumentUpdate,
     kHitTestDocumentUpdate,
@@ -196,6 +197,7 @@
         {"Blink.MediaIntersectionObserver.UpdateTime", true},
         {"Blink.AnchorElementMetricsIntersectionObserver.UpdateTime", true},
         {"Blink.UpdateViewportIntersection.UpdateTime", true},
+        {"Blink.VisualUpdateDelay.UpdateTime", true},
         {"Blink.ForcedStyleAndLayout.UpdateTime", true},
         {"Blink.ContentDocumentUpdate.UpdateTime", true},
         {"Blink.HitTestDocumentUpdate.UpdateTime", true},
@@ -332,6 +334,12 @@
   // RecordEndOfFrameMetrics.
   std::unique_ptr<cc::BeginMainFrameMetrics> GetBeginMainFrameMetrics();
 
+  void OnCommitRequested();
+
+  base::TimeTicks LastFrameRequestTimeForTest() const {
+    return last_frame_request_timestamp_for_test_;
+  }
+
  private:
   struct AbsoluteMetricRecord {
     std::unique_ptr<CustomCountHistogram> pre_fcp_uma_counter;
@@ -435,6 +443,10 @@
   // frequently we collect granular IntersectionObserver metrics.
   size_t intersection_observer_sample_period_ = 10;
 
+  absl::optional<base::TimeTicks> animation_request_timestamp_;
+  absl::optional<base::TimeTicks> request_timestamp_for_current_frame_;
+  base::TimeTicks last_frame_request_timestamp_for_test_;
+
   // True if the local frame root that instantiated this is the main frame.
   bool is_for_main_frame_ = false;
 };
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
index 79a3afe3..d23c103 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
@@ -946,6 +946,47 @@
   histogram_tester.ExpectTotalCount("Blink.MainFrame.UpdateTime.PostFCP", 1);
 }
 
+TEST_F(LocalFrameUkmAggregatorSimTest, VisualUpdateDelay) {
+  base::HistogramTester histogram_tester;
+
+  WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
+  SimRequest main_resource("https://example.com/", "text/html");
+  LoadURL("https://example.com/");
+  main_resource.Complete(R"HTML(
+    <!doctype html>
+    <div id=target></div>
+  )HTML");
+
+  // The first main frame will not record VisualUpdateDelay because it was
+  // requested before the current document was installed.
+  Compositor().BeginFrame();
+  histogram_tester.ExpectTotalCount("Blink.VisualUpdateDelay.UpdateTime.PreFCP",
+                                    0);
+
+  // This is necessary to ensure that the invalidation timestamp is later than
+  // the previous frame time.
+  Compositor().ResetLastFrameTime();
+
+  // This is the code path for a normal invalidation from blink
+  WebView().MainFrameViewWidget()->RequestAnimationAfterDelay(
+      base::TimeDelta());
+
+  base::PlatformThread::Sleep(base::Microseconds(3000));
+
+  // Service the frame; it should record a sample.
+  Compositor().BeginFrame();
+  histogram_tester.ExpectTotalCount("Blink.VisualUpdateDelay.UpdateTime.PreFCP",
+                                    1);
+  base::HistogramBase::Sample delay =
+      base::saturated_cast<base::HistogramBase::Sample>(
+          (Compositor().LastFrameTime() -
+           local_root_aggregator().LastFrameRequestTimeForTest())
+              .InMicroseconds());
+  EXPECT_GT(delay, 3000);
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisualUpdateDelay.UpdateTime.PreFCP", delay, 1);
+}
+
 TEST_F(LocalFrameUkmAggregatorSimTest, SVGImageMetricsAreNotRecorded) {
   base::HistogramTester histogram_tester;
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 7af05b3..212c6ba 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -144,7 +144,6 @@
 #include "third_party/blink/renderer/core/paint/block_paint_invalidator.h"
 #include "third_party/blink/renderer/core/paint/cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/frame_painter.h"
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
@@ -2005,9 +2004,8 @@
   if (background_source->StyleRef().ColorSchemeForced()) {
     // TODO(https://crbug.com/1351544): The DarkModeFilter operate on SkColor4f,
     // and DocumentBackgroundColor should return an SkColor4f.
-    doc_bg = Color::FromSkColor(EnsureDarkModeFilter().InvertColorIfNeeded(
-        doc_bg.ToSkColorDeprecated(),
-        DarkModeFilter::ElementRole::kBackground));
+    doc_bg = Color::FromSkColor4f(EnsureDarkModeFilter().InvertColorIfNeeded(
+        doc_bg.toSkColor4f(), DarkModeFilter::ElementRole::kBackground));
   }
   if (blend_with_base)
     return result.Blend(doc_bg);
@@ -2718,14 +2716,6 @@
                 layout_view->DescendantBlockingWheelEventHandlerChanged()) {
               owner->MarkDescendantBlockingWheelEventHandlerChanged();
             }
-            if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-              // Issue additional invalidations for the old cull rect updater.
-              if (layout_view->Layer()->NeedsCullRectUpdate() ||
-                  layout_view->Layer()->DescendantNeedsCullRectUpdate()) {
-                layout_view->Layer()
-                    ->MarkCompositingContainerChainForNeedsCullRectUpdate();
-              }
-            }
           }
         }
       },
@@ -2918,10 +2908,7 @@
   auto* layout_view = GetLayoutView();
   DCHECK(layout_view);
 
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    CullRectUpdater(*layout_view->Layer()).Update();
-  else
-    OldCullRectUpdater(*layout_view->Layer()).Update();
+  CullRectUpdater(*layout_view->Layer()).Update();
 
   bool debug_info_newly_enabled =
       UpdateLayerDebugInfoEnabled() && PaintDebugInfoEnabled();
@@ -3655,6 +3642,14 @@
     client->ScheduleAnimation(this, delay);
 }
 
+void LocalFrameView::OnCommitRequested() {
+  DCHECK(frame_->IsLocalRoot());
+  if (frame_->GetDocument() &&
+      !frame_->GetDocument()->IsInitialEmptyDocument() && GetUkmAggregator()) {
+    GetUkmAggregator()->OnCommitRequested();
+  }
+}
+
 void LocalFrameView::AddScrollAnchoringScrollableArea(
     PaintLayerScrollableArea* scrollable_area) {
   DCHECK(scrollable_area);
@@ -4068,8 +4063,6 @@
   {
     OverriddenCullRectScope force_cull_rect(*GetLayoutView()->Layer(),
                                             cull_rect);
-    OverriddenOldCullRectScope force_old_cull_rect(*GetLayoutView()->Layer(),
-                                                   cull_rect);
     PaintControllerCycleScope cycle_scope(context.GetPaintController(),
                                           PaintDebugInfoEnabled());
     PaintFrame(context, paint_flags);
@@ -4091,10 +4084,7 @@
 void LocalFrameView::PaintForTest(const CullRect& cull_rect) {
   AllowThrottlingScope allow_throttling(*this);
   Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint);
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    CullRectUpdater(*GetLayoutView()->Layer()).UpdateForTesting(cull_rect);
-  OverriddenOldCullRectScope force_old_cull_rect(*GetLayoutView()->Layer(),
-                                                 cull_rect);
+  CullRectUpdater(*GetLayoutView()->Layer()).UpdateForTesting(cull_rect);
   PaintController& paint_controller = EnsurePaintController();
   if (GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()) {
     PaintControllerCycleScope cycle_scope(paint_controller,
@@ -4104,10 +4094,8 @@
     paint_controller.CommitNewDisplayItems();
   }
   Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean);
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    CullRectUpdater(*GetLayoutView()->Layer())
-        .UpdateForTesting(CullRect::Infinite());
-  }
+  CullRectUpdater(*GetLayoutView()->Layer())
+      .UpdateForTesting(CullRect::Infinite());
 }
 
 sk_sp<PaintRecord> LocalFrameView::GetPaintRecord() const {
@@ -4923,7 +4911,6 @@
 }
 
 void LocalFrameView::PropagateCullRectNeedsUpdateForFrames() {
-  DCHECK(RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
   ForAllNonThrottledLocalFrameViews(
       [](LocalFrameView& frame_view) {
         // Propagate child frame PaintLayer NeedsCullRectUpdate flag into the
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index dac6f3c..9731dcc 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -473,6 +473,8 @@
   void ScheduleAnimation(base::TimeDelta = base::TimeDelta(),
                          base::Location location = base::Location::Current());
 
+  void OnCommitRequested();
+
   // FIXME: This should probably be renamed as the 'inSubtreeLayout' parameter
   // passed around the LocalFrameView layout methods can be true while this
   // returns false.
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index ac6ab24..1a2d76d 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -2135,6 +2135,11 @@
   view->Resize(*size_);
 }
 
+void WebFrameWidgetImpl::OnCommitRequested() {
+  if (auto* view = LocalRootImpl()->GetFrame()->View())
+    view->OnCommitRequested();
+}
+
 void WebFrameWidgetImpl::BeginMainFrame(base::TimeTicks last_frame_time) {
   TRACE_EVENT1("blink", "WebFrameWidgetImpl::BeginMainFrame", "frameTime",
                last_frame_time);
@@ -3973,9 +3978,10 @@
 
   // Calculate the bounding box of the selection area.
   if (bounding_box_in_root_frame) {
-    const gfx::Rect bounding_box = ToEnclosingRect(
-        CreateRange(selection.GetSelectionInDOMTree().ComputeRange())
-            ->BoundingRect());
+    Range* range =
+        CreateRange(selection.GetSelectionInDOMTree().ComputeRange());
+    const gfx::Rect bounding_box = ToEnclosingRect(range->BoundingRect());
+    range->Dispose();
     *bounding_box_in_root_frame = visual_viewport.RootFrameToViewport(
         local_frame->View()->ConvertToRootFrame(bounding_box));
   }
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 38ad774..7e9e0b1d 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -410,6 +410,7 @@
   float GetEmulatorScale() override;
 
   // WidgetBaseClient overrides:
+  void OnCommitRequested() override;
   void BeginMainFrame(base::TimeTicks last_frame_time) override;
   void UpdateLifecycle(WebLifecycleUpdate requested_update,
                        DocumentUpdateReason reason) override;
diff --git a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
index 42ba85db..ef161dd3 100644
--- a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -435,6 +435,13 @@
   return c == '\r' || c == '\n';
 }
 
+// Returns true if `c` may contain a line break. This is an inexact comparison.
+// This is used as the common case is the text does not contain a newline.
+static bool MayBeASCIILineBreak(UChar c) {
+  static_assert('\n' < '\r');
+  return c <= '\r';
+}
+
 static String LimitLength(const String& string, unsigned max_length) {
   unsigned new_length = std::min(max_length, string.length());
   if (new_length == string.length())
@@ -445,6 +452,13 @@
 }
 
 String TextFieldInputType::SanitizeValue(const String& proposed_value) const {
+  // Typical case is the string doesn't contain a break and fits. The Find()
+  // is not exact (meaning it'll match many other characters), but is a good
+  // approximation for a fast path.
+  if (proposed_value.Find(MayBeASCIILineBreak) == kNotFound &&
+      proposed_value.length() < std::numeric_limits<int>::max()) {
+    return proposed_value;
+  }
   return LimitLength(proposed_value.RemoveCharacters(IsASCIILineBreak),
                      std::numeric_limits<int>::max());
 }
diff --git a/third_party/blink/renderer/core/html/html_dialog_element.cc b/third_party/blink/renderer/core/html/html_dialog_element.cc
index 537eda7..0c4773e 100644
--- a/third_party/blink/renderer/core/html/html_dialog_element.cc
+++ b/third_party/blink/renderer/core/html/html_dialog_element.cc
@@ -353,7 +353,7 @@
         HidePopoverForcingLevel::kHideAfterAnimations);
   }
 
-  Element* control = GetFocusDelegate();
+  Element* control = GetFocusDelegate(/*autofocus_only=*/false);
   if (!control)
     control = this;
 
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 27b4be4f..0d4a310 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1591,14 +1591,6 @@
   }
 }
 
-Element* HTMLElement::GetPopoverFirstFocusableElement(bool autofocus_only) {
-  // If the popover has autofocus, focus it.
-  if (IsAutofocusable())
-    return this;
-  // Otherwise, look for a child control that has the autofocus attribute.
-  return GetPopoverFocusableArea(autofocus_only);
-}
-
 void HTMLElement::SetPopoverFocusOnShow() {
   DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
       GetDocument().GetExecutionContext()));
@@ -1606,7 +1598,8 @@
   // which requires an up-to-date layout.
   GetDocument().UpdateStyleAndLayoutTreeForNode(this);
 
-  Element* control = GetPopoverFirstFocusableElement(/*autofocus_only*/ true);
+  Element* control =
+      IsAutofocusable() ? this : GetFocusDelegate(/*autofocus_only=*/true);
 
   // If the popover does not use autofocus, then the focus should remain on the
   // currently active element.
@@ -1635,31 +1628,6 @@
   doc.TopDocument().FinalizeAutofocus();
 }
 
-// TODO(masonf) This should really be combined with Element::GetFocusableArea(),
-// and can possibly be merged with the similar logic for <dialog>. The spec for
-// https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
-// does not include dialogs or popovers yet.
-Element* HTMLElement::GetPopoverFocusableArea(bool autofocus_only) const {
-  DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
-      GetDocument().GetExecutionContext()));
-  Node* next = nullptr;
-  for (Node* node = FlatTreeTraversal::FirstChild(*this); node; node = next) {
-    next = FlatTreeTraversal::Next(*node, this);
-    auto* element = DynamicTo<HTMLElement>(node);
-    if (!element)
-      continue;
-    if (element->HasPopoverAttribute() || IsA<HTMLDialogElement>(*element)) {
-      next = FlatTreeTraversal::NextSkippingChildren(*element, this);
-      continue;
-    }
-    if (element->IsFocusable() &&
-        (!autofocus_only || element->IsAutofocusable())) {
-      return element;
-    }
-  }
-  return nullptr;
-}
-
 using PopoverPositionMap = HeapHashMap<Member<const Element>, int>;
 using PopoverAnchorMap =
     HeapHashMap<Member<const Element>, Member<const Element>>;
@@ -1680,7 +1648,8 @@
   int position = -1;
   auto update = [&ancestor, &position, &popover_positions,
                  upper_bound](const HTMLElement* popover) {
-    if (popover && popover->popoverOpen() &&
+    DCHECK(popover);
+    if (popover->popoverOpen() &&
         popover->PopoverType() != PopoverValueType::kManual) {
       DCHECK(popover_positions.Contains(popover));
       int new_position = popover_positions.at(popover);
@@ -1692,8 +1661,9 @@
   };
   auto recurse_and_update = [&update, &popover_positions, upper_bound,
                              &anchors_to_popovers, &seen](const Node* node) {
-    update(NearestOpenAncestralPopoverRecursive(
-        node, popover_positions, anchors_to_popovers, upper_bound, seen));
+    if (auto* popover = NearestOpenAncestralPopoverRecursive(
+            node, popover_positions, anchors_to_popovers, upper_bound, seen))
+      update(popover);
   };
 
   if (auto* element = DynamicTo<HTMLElement>(node)) {
diff --git a/third_party/blink/renderer/core/html/html_element.h b/third_party/blink/renderer/core/html/html_element.h
index ec66d1db..7593fa0 100644
--- a/third_party/blink/renderer/core/html/html_element.h
+++ b/third_party/blink/renderer/core/html/html_element.h
@@ -239,7 +239,6 @@
   void PopoverAnchorElementChanged();
   static void HandlePopoverLightDismiss(const Event& event, const Node& node);
   void InvokePopover(Element* invoker);
-  Element* GetPopoverFirstFocusableElement(bool autofocus_only);
   void SetPopoverFocusOnShow();
   // This hides all visible popovers up to, but not including,
   // |endpoint|. If |endpoint| is nullptr, all popovers are hidden.
@@ -337,9 +336,6 @@
   static AttributeTriggers* TriggersForAttributeName(
       const QualifiedName& attr_name);
 
-  // Special focus handling for popovers.
-  Element* GetPopoverFocusableArea(bool autofocus_only) const;
-
   void OnDirAttrChanged(const AttributeModificationParams&);
   void OnFormAttrChanged(const AttributeModificationParams&);
   void OnLangAttrChanged(const AttributeModificationParams&);
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index f3fd665..90bd1d8 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -796,6 +796,11 @@
       SetNeedsPaintPropertyUpdate();
     }
 
+    if (old_style->OverflowX() != new_style.OverflowX() ||
+        old_style->OverflowY() != new_style.OverflowY()) {
+      SetNeedsPaintPropertyUpdate();
+    }
+
     if (old_style->OverflowClipMargin() != new_style.OverflowClipMargin())
       SetNeedsPaintPropertyUpdate();
 
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 9b31c76..30d84870 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -1005,7 +1005,10 @@
   if (!CancelProvisionalLoaderForNewNavigation())
     return;
 
-  if (navigation_params->frame_load_type == WebFrameLoadType::kBackForward) {
+  auto url_origin = SecurityOrigin::Create(navigation_params->url);
+  if (navigation_params->frame_load_type == WebFrameLoadType::kBackForward &&
+      frame_->DomWindow()->GetSecurityOrigin()->IsSameOriginWith(
+          url_origin.get())) {
     auto* params = MakeGarbageCollected<NavigateEventDispatchParams>(
         navigation_params->url, NavigateEventType::kCrossDocument,
         WebFrameLoadType::kBackForward);
@@ -1077,8 +1080,7 @@
   // so that the old document can access it and fill in the information as it
   // is being unloaded/swapped out.
   ScopedOldDocumentInfoForCommitCapturer scoped_old_document_info(
-      MakeGarbageCollected<OldDocumentInfoForCommit>(
-          SecurityOrigin::Create(navigation_params->url)));
+      MakeGarbageCollected<OldDocumentInfoForCommit>(url_origin));
 
   FrameSwapScope frame_swap_scope(frame_owner);
   {
diff --git a/third_party/blink/renderer/core/paint/block_painter.cc b/third_party/blink/renderer/core/paint/block_painter.cc
index 4525930..7663e73 100644
--- a/third_party/blink/renderer/core/paint/block_painter.cc
+++ b/third_party/blink/renderer/core/paint/block_painter.cc
@@ -70,10 +70,8 @@
     // it easier to merge scrolling background and scrolling contents into the
     // same layer. The function checks if it's appropriate to paint overflow
     // controls now.
-    if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-      painted_overflow_controls =
-          PaintOverflowControls(local_paint_info, paint_offset);
-    }
+    painted_overflow_controls =
+        PaintOverflowControls(local_paint_info, paint_offset);
 
     if (paint_location & kBackgroundPaintInContentsSpace) {
       local_paint_info.SetIsPaintingBackgroundInContentsSpace(true);
diff --git a/third_party/blink/renderer/core/paint/box_painter_base.cc b/third_party/blink/renderer/core/paint/box_painter_base.cc
index 6dd053c..38c1bb6 100644
--- a/third_party/blink/renderer/core/paint/box_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/box_painter_base.cc
@@ -175,9 +175,9 @@
     // looper used for shadows so we need to apply dark mode to the color here.
     const Color shadow_color =
         style.ForceDark()
-            ? Color::FromSkColor(
+            ? Color::FromSkColor4f(
                   context.GetDarkModeFilter()->InvertColorIfNeeded(
-                      resolved_shadow_color.ToSkColorDeprecated(),
+                      resolved_shadow_color.toSkColor4f(),
                       DarkModeFilter::ElementRole::kBackground))
             : resolved_shadow_color;
 
@@ -345,9 +345,9 @@
     // looper used for shadows so we need to apply dark mode to the color here.
     const Color& shadow_color =
         style.ForceDark()
-            ? Color::FromSkColor(
+            ? Color::FromSkColor4f(
                   context.GetDarkModeFilter()->InvertColorIfNeeded(
-                      resolved_shadow_color.ToSkColorDeprecated(),
+                      resolved_shadow_color.toSkColor4f(),
                       DarkModeFilter::ElementRole::kBackground))
             : resolved_shadow_color;
 
diff --git a/third_party/blink/renderer/core/paint/box_painter_test.cc b/third_party/blink/renderer/core/paint/box_painter_test.cc
index 25b7e360..96c04a64 100644
--- a/third_party/blink/renderer/core/paint/box_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/box_painter_test.cc
@@ -282,11 +282,11 @@
   size_t count = 0;
   for (cc::PaintOpBuffer::Iterator it(buffer); it; ++it) {
     if (it->GetType() == cc::PaintOpType::DrawImageRect) {
-      const auto& image_op = static_cast<cc::DrawImageRectOp&>(*it);
+      const auto& image_op = static_cast<const cc::DrawImageRectOp&>(*it);
       if (image_op.constraint == constraint)
         ++count;
     } else if (it->GetType() == cc::PaintOpType::DrawRecord) {
-      const auto& record_op = static_cast<cc::DrawRecordOp&>(*it);
+      const auto& record_op = static_cast<const cc::DrawRecordOp&>(*it);
       count +=
           CountDrawImagesWithConstraint(record_op.record.get(), constraint);
     }
diff --git a/third_party/blink/renderer/core/paint/build.gni b/third_party/blink/renderer/core/paint/build.gni
index 506e56c..0338f06 100644
--- a/third_party/blink/renderer/core/paint/build.gni
+++ b/third_party/blink/renderer/core/paint/build.gni
@@ -136,8 +136,6 @@
   "object_paint_invalidator.cc",
   "object_paint_invalidator.h",
   "object_paint_properties.h",
-  "old_cull_rect_updater.cc",
-  "old_cull_rect_updater.h",
   "outline_painter.cc",
   "outline_painter.h",
   "paint_auto_dark_mode.cc",
@@ -288,7 +286,6 @@
   "ng/ng_text_fragment_painter_test.cc",
   "nine_piece_image_grid_test.cc",
   "object_paint_invalidator_test.cc",
-  "old_cull_rect_updater_test.cc",
   "outline_painter_test.cc",
   "paint_and_raster_invalidation_test.cc",
   "paint_and_raster_invalidation_test.h",
diff --git a/third_party/blink/renderer/core/paint/cull_rect_updater.cc b/third_party/blink/renderer/core/paint/cull_rect_updater.cc
index 909f031..366616a8 100644
--- a/third_party/blink/renderer/core/paint/cull_rect_updater.cc
+++ b/third_party/blink/renderer/core/paint/cull_rect_updater.cc
@@ -173,7 +173,6 @@
 
 CullRectUpdater::CullRectUpdater(PaintLayer& starting_layer)
     : starting_layer_(starting_layer) {
-  DCHECK(RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
   view_transition_supplement_ = ViewTransitionSupplement::FromIfExists(
       starting_layer.GetLayoutObject().GetDocument());
 }
@@ -482,8 +481,6 @@
 void CullRectUpdater::PaintPropertiesChanged(
     const LayoutObject& object,
     const PaintPropertiesChangeInfo& properties_changed) {
-  DCHECK(RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-
   // We don't need to update cull rect for kChangedOnlyCompositedValues (except
   // for some paint translation changes, see below) because we expect no repaint
   // or PAC update for performance.
@@ -569,9 +566,6 @@
 
 OverriddenCullRectScope::OverriddenCullRectScope(PaintLayer& starting_layer,
                                                  const CullRect& cull_rect) {
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-
   outer_original_cull_rects_ = g_original_cull_rects;
 
   if (starting_layer.IsRootLayer() &&
@@ -589,9 +583,6 @@
 }
 
 OverriddenCullRectScope::~OverriddenCullRectScope() {
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-
   if (outer_original_cull_rects_ == g_original_cull_rects)
     return;
 
diff --git a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
index cae93c4..14b68b2f 100644
--- a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
+++ b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
@@ -34,9 +34,7 @@
 
 INSTANTIATE_TEST_SUITE_P(All,
                          CullRectUpdaterTest,
-                         ::testing::Values(kScrollUpdateOptimizations |
-                                               kScrollUnification,
-                                           kScrollUpdateOptimizations));
+                         ::testing::Values(0, kScrollUnification));
 
 TEST_P(CullRectUpdaterTest, SimpleCullRect) {
   SetBodyInnerHTML(R"HTML(
@@ -924,9 +922,7 @@
 
 INSTANTIATE_TEST_SUITE_P(All,
                          CullRectUpdateOnPaintPropertyChangeTest,
-                         ::testing::Values(kScrollUpdateOptimizations |
-                                               kScrollUnification,
-                                           kScrollUpdateOptimizations));
+                         ::testing::Values(0, kScrollUnification));
 
 TEST_P(CullRectUpdateOnPaintPropertyChangeTest, Opacity) {
   TestTargetChange("opacity: 0.2", "opacity: 0.8", false, false, false);
@@ -1016,7 +1012,7 @@
        LargeContentsScrollSmallDeltaOrNotExposingNewContents) {
   html_ = html_ + "<style>#child { width: 10000px; height: 10000px; }</style>";
   // Scroll offset changes that are small or won't expose new contents don't
-  // need cull rect update when ScrollUpdateOptimizationsEnabled.
+  // need cull rect update.
   bool needs_cull_rect_update = false;
   TestTargetScroll(ScrollOffset(), ScrollOffset(200, 200), false,
                    needs_cull_rect_update, false);
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 48cb8b5..d3d1f9d 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -510,8 +510,7 @@
       // make it easier to merge scrolling background and scrolling contents
       // into the same layer. The function checks if it's appropriate to paint
       // overflow controls now.
-      if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-        painted_overflow_controls = PaintOverflowControls(info, paint_offset);
+      painted_overflow_controls = PaintOverflowControls(info, paint_offset);
 
       info.SetIsPaintingBackgroundInContentsSpace(true);
       PaintObject(info, paint_offset);
diff --git a/third_party/blink/renderer/core/paint/old_cull_rect_updater.cc b/third_party/blink/renderer/core/paint/old_cull_rect_updater.cc
deleted file mode 100644
index 4dbfb6e..0000000
--- a/third_party/blink/renderer/core/paint/old_cull_rect_updater.cc
+++ /dev/null
@@ -1,540 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
-
-#include "base/auto_reset.h"
-#include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/frame/local_frame_view.h"
-#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
-#include "third_party/blink/renderer/core/layout/layout_view.h"
-#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
-#include "third_party/blink/renderer/core/paint/paint_layer.h"
-#include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h"
-#include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
-#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
-#include "third_party/blink/renderer/core/paint/paint_property_tree_builder.h"
-#include "third_party/blink/renderer/core/view_transition/view_transition_utils.h"
-#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-
-namespace blink {
-
-namespace {
-
-void SetLayerNeedsRepaintOnCullRectChange(PaintLayer& layer) {
-  if (layer.PreviousPaintResult() == kMayBeClippedByCullRect ||
-      RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled()) {
-    layer.SetNeedsRepaint();
-  }
-}
-
-void SetFragmentCullRect(PaintLayer& layer,
-                         FragmentData& fragment,
-                         const CullRect& cull_rect) {
-  if (cull_rect == fragment.GetCullRect())
-    return;
-
-  fragment.SetCullRect(cull_rect);
-  SetLayerNeedsRepaintOnCullRectChange(layer);
-}
-
-// Returns true if the contents cull rect changed.
-bool SetFragmentContentsCullRect(PaintLayer& layer,
-                                 FragmentData& fragment,
-                                 const CullRect& contents_cull_rect) {
-  if (contents_cull_rect == fragment.GetContentsCullRect())
-    return false;
-
-  fragment.SetContentsCullRect(contents_cull_rect);
-  SetLayerNeedsRepaintOnCullRectChange(layer);
-  return true;
-}
-
-bool ShouldUseInfiniteCullRect(const PaintLayer& layer,
-                               bool& subtree_should_use_infinite_cull_rect) {
-  if (RuntimeEnabledFeatures::InfiniteCullRectEnabled())
-    return true;
-
-  if (subtree_should_use_infinite_cull_rect)
-    return true;
-
-  const LayoutObject& object = layer.GetLayoutObject();
-  bool is_printing = object.GetDocument().Printing();
-  if (IsA<LayoutView>(object) && !object.GetFrame()->ClipsContent() &&
-      // We use custom top cull rect per page when printing.
-      !is_printing) {
-    return true;
-  }
-
-  if (const auto* properties = object.FirstFragment().PaintProperties()) {
-    // Cull rects and clips can't be propagated across a filter which moves
-    // pixels, since the input of the filter may be outside the cull rect /
-    // clips yet still result in painted output.
-    if (properties->Filter() &&
-        properties->Filter()->HasFilterThatMovesPixels() &&
-        // However during printing, we don't want filter outset to cross page
-        // boundaries. This also avoids performance issue because the PDF
-        // renderer is super slow for big filters.
-        !is_printing) {
-      return true;
-    }
-
-    // Cull rect mapping doesn't work under perspective in some cases.
-    // See http://crbug.com/887558 for details.
-    if (properties->Perspective()) {
-      subtree_should_use_infinite_cull_rect = true;
-      return true;
-    }
-
-    const TransformPaintPropertyNode* transform_nodes[] = {
-        properties->Transform(), properties->Offset(), properties->Scale(),
-        properties->Rotate(), properties->Translate()};
-    for (const auto* transform : transform_nodes) {
-      if (!transform)
-        continue;
-
-      // A CSS transform can also have perspective like
-      // "transform: perspective(100px) rotateY(45deg)". In these cases, we
-      // also want to skip cull rect mapping. See http://crbug.com/887558 for
-      // details.
-      if (transform->Matrix().HasPerspective()) {
-        subtree_should_use_infinite_cull_rect = true;
-        return true;
-      }
-
-      // Ensure content under animating transforms is not culled out.
-      if (transform->HasActiveTransformAnimation())
-        return true;
-
-      // As an optimization, skip cull rect updating for non-composited
-      // transforms which have already been painted. This is because the cull
-      // rect update, which needs to do complex mapping of the cull rect, can
-      // be more expensive than over-painting.
-      if (!transform->HasDirectCompositingReasons() &&
-          layer.PreviousPaintResult() == kFullyPainted) {
-        return true;
-      }
-    }
-  }
-
-  if (auto* transition =
-          ViewTransitionUtils::GetActiveTransition(object.GetDocument())) {
-    // This means that the contents of the object are drawn elsewhere, so we
-    // shouldn't cull it.
-    if (transition->IsRepresentedViaPseudoElements(object)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-}  // anonymous namespace
-
-OldCullRectUpdater::OldCullRectUpdater(PaintLayer& starting_layer)
-    : starting_layer_(starting_layer) {
-  DCHECK(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-}
-
-void OldCullRectUpdater::Update() {
-  TRACE_EVENT0("blink,benchmark", "CullRectUpdate");
-  SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Blink.CullRect.UpdateTime");
-
-  DCHECK(starting_layer_.IsRootLayer());
-  UpdateInternal(CullRect::Infinite());
-#if DCHECK_IS_ON()
-  if (VLOG_IS_ON(2)) {
-    VLOG(2) << "PaintLayer tree after cull rect update:";
-    ShowLayerTree(&starting_layer_);
-  }
-#endif
-}
-
-void OldCullRectUpdater::UpdateInternal(const CullRect& input_cull_rect) {
-  const auto& object = starting_layer_.GetLayoutObject();
-  if (object.GetFrameView()->ShouldThrottleRendering())
-    return;
-
-  root_state_ =
-      object.View()->FirstFragment().LocalBorderBoxProperties().Unalias();
-  bool should_use_infinite = ShouldUseInfiniteCullRect(
-      starting_layer_, subtree_should_use_infinite_cull_rect_);
-  auto& fragment = object.GetMutableForPainting().FirstFragment();
-  SetFragmentCullRect(
-      starting_layer_, fragment,
-      should_use_infinite ? CullRect::Infinite() : input_cull_rect);
-  bool force_update_children = SetFragmentContentsCullRect(
-      starting_layer_, fragment,
-      should_use_infinite ? CullRect::Infinite()
-                          : ComputeFragmentContentsCullRect(
-                                starting_layer_, fragment, input_cull_rect));
-  UpdateForDescendants(starting_layer_, force_update_children);
-}
-
-// See UpdateForDescendants for how |force_update_self| is propagated.
-void OldCullRectUpdater::UpdateRecursively(
-    PaintLayer& layer,
-    const PaintLayer& parent_painting_layer,
-    bool force_update_self) {
-  if (layer.IsUnderSVGHiddenContainer())
-    return;
-
-  bool should_proactively_update = ShouldProactivelyUpdate(layer);
-  bool force_update_children =
-      should_proactively_update || layer.ForcesChildrenCullRectUpdate() ||
-      !layer.GetLayoutObject().IsStackingContext() ||
-      // |force_update_self| is true if the contents cull rect of the containing
-      // block of |layer| changed, so we need to propagate the flag to
-      // non-contained absolute-position descendants whose cull rects are also
-      // affected by the containing block.
-      (force_update_self && layer.HasNonContainedAbsolutePositionDescendant());
-
-  // This defines the scope of force_proactive_update_ (which may be set by
-  // ComputeFragmentCullRect() and ComputeFragmentContentsCullRect()) to the
-  // subtree.
-  base::AutoReset<bool> reset_force_update(&force_proactive_update_,
-                                           force_proactive_update_);
-  // Similarly for subtree_should_use_infinite_cull_rect_.
-  base::AutoReset<bool> reset_subtree_infinite_cull_rect(
-      &subtree_should_use_infinite_cull_rect_,
-      subtree_should_use_infinite_cull_rect_);
-
-  if (force_update_self || should_proactively_update ||
-      layer.NeedsCullRectUpdate())
-    force_update_children |= UpdateForSelf(layer, parent_painting_layer);
-
-  absl::optional<base::AutoReset<bool>> reset_subtree_is_out_of_cull_rect;
-  if (!subtree_is_out_of_cull_rect_ && layer.KnownToClipSubtreeToPaddingBox() &&
-      !layer.GetLayoutObject().FirstFragment().NextFragment()) {
-    const auto* box = layer.GetLayoutBox();
-    DCHECK(box);
-    PhysicalRect overflow_rect = box->PhysicalSelfVisualOverflowRect();
-    overflow_rect.Move(box->FirstFragment().PaintOffset());
-    if (!box->FirstFragment().GetCullRect().Intersects(
-            ToEnclosingRect(overflow_rect))) {
-      reset_subtree_is_out_of_cull_rect.emplace(&subtree_is_out_of_cull_rect_,
-                                                true);
-    }
-  }
-
-  if (force_update_children || layer.DescendantNeedsCullRectUpdate() ||
-      // A change of non-stacking-context layer may affect cull rect of
-      // descendants even if the contents cull rect doesn't change.
-      !layer.GetLayoutObject().IsStackingContext()) {
-    UpdateForDescendants(layer, force_update_children);
-  }
-
-  layer.ClearNeedsCullRectUpdate();
-}
-
-// "Children" in |force_update_children| means children in the containing block
-// tree. The flag is set by the containing block whose contents cull rect
-// changed.
-void OldCullRectUpdater::UpdateForDescendants(PaintLayer& layer,
-                                              bool force_update_children) {
-  const auto& object = layer.GetLayoutObject();
-
-  // DisplayLockContext will force cull rect update of the subtree on unlock.
-  if (object.ChildPaintBlockedByDisplayLock())
-    return;
-
-  if (auto* embedded_content = DynamicTo<LayoutEmbeddedContent>(object)) {
-    if (auto* embedded_view = embedded_content->GetEmbeddedContentView()) {
-      if (auto* embedded_frame_view =
-              DynamicTo<LocalFrameView>(embedded_view)) {
-        PaintLayer* subframe_root_layer = nullptr;
-        if (auto* sub_layout_view = embedded_frame_view->GetLayoutView())
-          subframe_root_layer = sub_layout_view->Layer();
-        if (embedded_frame_view->ShouldThrottleRendering()) {
-          if (force_update_children && subframe_root_layer)
-            subframe_root_layer->SetNeedsCullRectUpdate();
-        } else {
-          DCHECK(subframe_root_layer);
-          UpdateRecursively(*subframe_root_layer, layer, force_update_children);
-        }
-      }
-    }
-  }
-
-  // Update non-stacked direct children first. In the following case:
-  // <div id="layer" style="stacking-context">
-  //   <div id="child" style="overflow: hidden; ...">
-  //     <div id="stacked-child" style="position: relative"></div>
-  //   </div>
-  // </div>
-  // If |child| needs cull rect update, we also need to update |stacked-child|'s
-  // cull rect because it's clipped by |child|. The is done in the following
-  // order:
-  //   UpdateForDescendants(|layer|)
-  //     UpdateRecursively(|child|) (in the following loop)
-  //       |stacked-child|->SetNeedsCullRectUpdate()
-  //     UpdateRecursively(stacked-child) (in the next loop)
-  // Note that this iterates direct children (including non-stacked, and
-  // stacked children which may not be paint-order children of |layer|, e.g.
-  // |stacked-child| is not a paint-order child of |child|), which is
-  // different from PaintLayerPaintOrderIterator(kAllChildren) which iterates
-  // children in paint order.
-  for (auto* child = layer.FirstChild(); child; child = child->NextSibling()) {
-    if (!child->IsReplacedNormalFlowStacking() &&
-        child->GetLayoutObject().IsStacked()) {
-      // In the above example, during UpdateForDescendants(child), this
-      // forces cull rect update of |stacked-child| which will be updated in
-      // the next loop during UpdateForDescendants(layer).
-      child->SetNeedsCullRectUpdate();
-      continue;
-    }
-    UpdateRecursively(*child, layer, force_update_children);
-  }
-
-  // Then stacked children (which may not be direct children in PaintLayer
-  // hierarchy) in paint order.
-  PaintLayerPaintOrderIterator iterator(&layer, kStackedChildren);
-  while (PaintLayer* child = iterator.Next()) {
-    if (!child->IsReplacedNormalFlowStacking())
-      UpdateRecursively(*child, layer, force_update_children);
-  }
-}
-
-bool OldCullRectUpdater::UpdateForSelf(
-    PaintLayer& layer,
-    const PaintLayer& parent_painting_layer) {
-  const auto& first_parent_fragment =
-      parent_painting_layer.GetLayoutObject().FirstFragment();
-  auto& first_fragment =
-      layer.GetLayoutObject().GetMutableForPainting().FirstFragment();
-  // If the containing layer is fragmented, try to match fragments from the
-  // container to |layer|, so that any fragment clip for
-  // |context.current.container|'s fragment matches |layer|'s.
-  //
-  // TODO(paint-dev): If nested fragmentation is involved, we're not matching
-  // correctly here. In order to fix that, we most likely need to move over to
-  // some sort of fragment tree traversal (rather than pure PaintLayer tree
-  // traversal).
-  bool should_match_fragments = first_parent_fragment.NextFragment();
-  bool force_update_children = false;
-  bool should_use_infinite_cull_rect =
-      !subtree_is_out_of_cull_rect_ &&
-      ShouldUseInfiniteCullRect(layer, subtree_should_use_infinite_cull_rect_);
-
-  for (auto* fragment = &first_fragment; fragment;
-       fragment = fragment->NextFragment()) {
-    CullRect cull_rect;
-    CullRect contents_cull_rect;
-    if (subtree_is_out_of_cull_rect_) {
-      // PaintLayerPainter may skip the subtree including this layer, so we
-      // need to SetPreviousPaintResult() here.
-      layer.SetPreviousPaintResult(kMayBeClippedByCullRect);
-    } else {
-      const FragmentData* parent_fragment = nullptr;
-      if (!should_use_infinite_cull_rect) {
-        if (should_match_fragments) {
-          for (parent_fragment = &first_parent_fragment; parent_fragment;
-               parent_fragment = parent_fragment->NextFragment()) {
-            if (parent_fragment->FragmentID() == fragment->FragmentID())
-              break;
-          }
-        } else {
-          parent_fragment = &first_parent_fragment;
-        }
-      }
-
-      if (should_use_infinite_cull_rect || !parent_fragment) {
-        cull_rect = CullRect::Infinite();
-        contents_cull_rect = CullRect::Infinite();
-      } else {
-        cull_rect = ComputeFragmentCullRect(layer, *fragment, *parent_fragment);
-        contents_cull_rect =
-            ComputeFragmentContentsCullRect(layer, *fragment, cull_rect);
-      }
-    }
-
-    SetFragmentCullRect(layer, *fragment, cull_rect);
-    force_update_children |=
-        SetFragmentContentsCullRect(layer, *fragment, contents_cull_rect);
-  }
-
-  return force_update_children;
-}
-
-CullRect OldCullRectUpdater::ComputeFragmentCullRect(
-    PaintLayer& layer,
-    const FragmentData& fragment,
-    const FragmentData& parent_fragment) {
-  auto local_state = fragment.LocalBorderBoxProperties().Unalias();
-  CullRect cull_rect = parent_fragment.GetContentsCullRect();
-  auto parent_state = parent_fragment.ContentsProperties().Unalias();
-
-  if (layer.GetLayoutObject().IsFixedPositioned()) {
-    const auto& view_fragment = layer.GetLayoutObject().View()->FirstFragment();
-    auto view_state = view_fragment.LocalBorderBoxProperties().Unalias();
-    if (const auto* properties = fragment.PaintProperties()) {
-      if (const auto* translation = properties->PaintOffsetTranslation()) {
-        if (translation->Parent() == &view_state.Transform()) {
-          // Use the viewport clip and ignore additional clips (e.g. clip-paths)
-          // because they are applied on this fixed-position layer by
-          // non-containers which may change location relative to this layer on
-          // viewport scroll for which we don't want to change fixed-position
-          // cull rects for performance.
-          local_state.SetClip(
-              view_fragment.ContentsProperties().Clip().Unalias());
-          parent_state = view_state;
-          cull_rect = view_fragment.GetCullRect();
-        }
-      }
-    }
-  }
-
-  if (parent_state != local_state) {
-    absl::optional<CullRect> old_cull_rect;
-    // Not using |old_cull_rect| will force the cull rect to be updated
-    // (skipping |ChangedEnough|) in |ApplyPaintProperties|.
-    if (!ShouldProactivelyUpdate(layer))
-      old_cull_rect = fragment.GetCullRect();
-    bool expanded = cull_rect.ApplyPaintProperties(root_state_, parent_state,
-                                                   local_state, old_cull_rect);
-    if (expanded && fragment.GetCullRect() != cull_rect)
-      force_proactive_update_ = true;
-  }
-  return cull_rect;
-}
-
-CullRect OldCullRectUpdater::ComputeFragmentContentsCullRect(
-    PaintLayer& layer,
-    const FragmentData& fragment,
-    const CullRect& cull_rect) {
-  auto local_state = fragment.LocalBorderBoxProperties().Unalias();
-  CullRect contents_cull_rect = cull_rect;
-  auto contents_state = fragment.ContentsProperties().Unalias();
-  if (contents_state != local_state) {
-    absl::optional<CullRect> old_contents_cull_rect;
-    // Not using |old_cull_rect| will force the cull rect to be updated
-    // (skipping |CullRect::ChangedEnough|) in |ApplyPaintProperties|.
-    if (!ShouldProactivelyUpdate(layer))
-      old_contents_cull_rect = fragment.GetContentsCullRect();
-    bool expanded = contents_cull_rect.ApplyPaintProperties(
-        root_state_, local_state, contents_state, old_contents_cull_rect);
-    if (expanded && fragment.GetContentsCullRect() != contents_cull_rect)
-      force_proactive_update_ = true;
-  }
-  return contents_cull_rect;
-}
-
-bool OldCullRectUpdater::ShouldProactivelyUpdate(
-    const PaintLayer& layer) const {
-  if (force_proactive_update_)
-    return true;
-
-  // If we will repaint anyway, proactively refresh cull rect. A sliding
-  // window (aka hysteresis, see: CullRect::ChangedEnough()) is used to
-  // avoid frequent cull rect updates because they force a repaint (see:
-  // |OldCullRectUpdater::SetFragmentCullRects|). Proactively updating the cull
-  // rect resets the sliding window which will minimize the need to update
-  // the cull rect again.
-  return layer.SelfOrDescendantNeedsRepaint();
-}
-
-void OldCullRectUpdater::PaintPropertiesChanged(
-    const LayoutObject& object,
-    PaintLayer& painting_layer,
-    const PaintPropertiesChangeInfo& properties_changed,
-    const gfx::Vector2dF& old_scroll_offset) {
-  // We don't need to update cull rect for kChangedOnlyCompositedValues (except
-  // for some paint translation changes, see below) because we expect no repaint
-  // or PAC update for performance.
-  // Clip nodes and scroll nodes don't have kChangedOnlyCompositedValues, so we
-  // don't need to check ShouldUseInfiniteCullRect before the early return
-  // below.
-  DCHECK_NE(properties_changed.clip_changed,
-            PaintPropertyChangeType::kChangedOnlyCompositedValues);
-  DCHECK_NE(properties_changed.scroll_changed,
-            PaintPropertyChangeType::kChangedOnlyCompositedValues);
-  // Cull rects depend on transforms, clip rects and scroll contents sizes.
-  bool needs_cull_rect_update =
-      properties_changed.transform_changed >=
-          PaintPropertyChangeType::kChangedOnlySimpleValues ||
-      properties_changed.clip_changed >=
-          PaintPropertyChangeType::kChangedOnlySimpleValues ||
-      properties_changed.scroll_changed >=
-          PaintPropertyChangeType::kChangedOnlySimpleValues;
-
-  if (!needs_cull_rect_update) {
-    if (const auto* properties = object.FirstFragment().PaintProperties()) {
-      if (const auto* scroll_translation = properties->ScrollTranslation()) {
-        // TODO(wangxianzhu): We can avoid cull rect update on scroll
-        // - if the scroll delta is not big enough to cause cull rect update, or
-        // - if the current contents cull rect is infinite and no descendants
-        //   need cull rect update.
-        needs_cull_rect_update =
-            scroll_translation->Get2dTranslation() != old_scroll_offset;
-      }
-    }
-  }
-
-  if (!needs_cull_rect_update) {
-    // For cases that the transform change can be directly updated, we should
-    // use infinite cull rect to avoid cull rect change and repaint.
-    bool subtree_should_use_infinite_cull_rect = false;
-    DCHECK(properties_changed.transform_changed !=
-               PaintPropertyChangeType::kChangedOnlyCompositedValues ||
-           object.IsSVGChild() ||
-           ShouldUseInfiniteCullRect(painting_layer,
-                                     subtree_should_use_infinite_cull_rect));
-    return;
-  }
-
-  if (object.HasLayer()) {
-    To<LayoutBoxModelObject>(object).Layer()->SetNeedsCullRectUpdate();
-    if (object.IsLayoutView() &&
-        object.GetFrameView()->HasFixedPositionObjects()) {
-      // Fixed-position cull rects depend on view clip. See
-      // ComputeFragmentCullRect().
-      if (const auto* clip_node =
-              object.FirstFragment().PaintProperties()->OverflowClip()) {
-        if (clip_node->NodeChanged() != PaintPropertyChangeType::kUnchanged) {
-          for (auto fixed : *object.GetFrameView()->FixedPositionObjects())
-            To<LayoutBox>(fixed.Get())->Layer()->SetNeedsCullRectUpdate();
-        }
-      }
-    }
-    return;
-  }
-
-  if (object.SlowFirstChild()) {
-    // This ensures cull rect update of the child PaintLayers affected by the
-    // paint property change on a non-PaintLayer. Though this may unnecessarily
-    // force update of unrelated children, the situation is rare and this is
-    // much easier.
-    painting_layer.SetForcesChildrenCullRectUpdate();
-  }
-}
-
-OverriddenOldCullRectScope::OverriddenOldCullRectScope(
-    PaintLayer& starting_layer,
-    const CullRect& cull_rect)
-    : starting_layer_(starting_layer) {
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-  if (starting_layer.GetLayoutObject().GetFrame()->IsLocalRoot() &&
-      !starting_layer.NeedsCullRectUpdate() &&
-      !starting_layer.DescendantNeedsCullRectUpdate() &&
-      cull_rect ==
-          starting_layer.GetLayoutObject().FirstFragment().GetCullRect()) {
-    // The current cull rects are good.
-    return;
-  }
-
-  updated_ = true;
-  starting_layer.SetNeedsCullRectUpdate();
-  OldCullRectUpdater(starting_layer).UpdateInternal(cull_rect);
-}
-
-OverriddenOldCullRectScope::~OverriddenOldCullRectScope() {
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-  if (updated_)
-    starting_layer_.SetNeedsCullRectUpdate();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/old_cull_rect_updater.h b/third_party/blink/renderer/core/paint/old_cull_rect_updater.h
deleted file mode 100644
index 54f51c3..0000000
--- a/third_party/blink/renderer/core/paint/old_cull_rect_updater.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OLD_CULL_RECT_UPDATER_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OLD_CULL_RECT_UPDATER_H_
-
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
-#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-
-namespace blink {
-
-class FragmentData;
-class LayoutObject;
-class PaintLayer;
-struct PaintPropertiesChangeInfo;
-
-// This class is equivalent to the cull rect update code prior to r1033837, but
-// with improvements made after r1033837. It only exists temporarily to compare
-// the performance against the new cull rect update in |cull_rect_updater.h|.
-// This code should only be used when ScrollUpdateOptimizations is enabled.
-//
-// This class is used for updating the cull rects of PaintLayer fragments (see:
-// |FragmentData::cull_rect_| and |FragmentData::contents_cull_rect_|.
-// Cull rects are used as an optimization to limit painting to areas "near" the
-// viewport. This update should happen during the PrePaint lifecycle stage.
-//
-// Dirty bits (see: |PaintLayer::NeedsCullRectUpdate()| and
-// PaintLayer::DescendantNeedsCullRectUpdate()|) are used to optimize this
-// update, and are cleared at the end.
-class CORE_EXPORT OldCullRectUpdater {
-  STACK_ALLOCATED();
-
- public:
-  explicit OldCullRectUpdater(PaintLayer& starting_layer);
-
-  void Update();
-
-  static void PaintPropertiesChanged(const LayoutObject&,
-                                     PaintLayer& painting_layer,
-                                     const PaintPropertiesChangeInfo&,
-                                     const gfx::Vector2dF& old_scroll_offset);
-
- private:
-  friend class OverriddenOldCullRectScope;
-
-  void UpdateInternal(const CullRect& input_cull_rect);
-  void UpdateRecursively(PaintLayer&,
-                         const PaintLayer& parent_painting_layer,
-                         bool force_update_self);
-  // Returns true if the contents cull rect changed which requires forced update
-  // for children.
-  bool UpdateForSelf(PaintLayer&, const PaintLayer& parent_painting_layer);
-  void UpdateForDescendants(PaintLayer&, bool force_update_children);
-  CullRect ComputeFragmentCullRect(PaintLayer&,
-                                   const FragmentData& fragment,
-                                   const FragmentData& parent_fragment);
-  CullRect ComputeFragmentContentsCullRect(PaintLayer&,
-                                           const FragmentData& fragment,
-                                           const CullRect& cull_rect);
-  bool ShouldProactivelyUpdate(const PaintLayer&) const;
-
-  PaintLayer& starting_layer_;
-  PropertyTreeState root_state_ = PropertyTreeState::Uninitialized();
-  bool force_proactive_update_ = false;
-  bool subtree_is_out_of_cull_rect_ = false;
-  bool subtree_should_use_infinite_cull_rect_ = false;
-};
-
-// Used when painting with a custom top-level cull rect, e.g. when printing a
-// page. It temporarily overrides the cull rect on the PaintLayer (which must be
-// a stacking context) and marks the PaintLayer as needing to recalculate the
-// cull rect when leaving this scope.
-// TODO(crbug.com/1215251): Avoid repaint after the scope if the scope is used
-// to paint into a separate PaintController.
-class CORE_EXPORT OverriddenOldCullRectScope {
-  STACK_ALLOCATED();
-
- public:
-  OverriddenOldCullRectScope(PaintLayer&, const CullRect&);
-  ~OverriddenOldCullRectScope();
-
- private:
-  PaintLayer& starting_layer_;
-  bool updated_ = false;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OLD_CULL_RECT_UPDATER_H_
diff --git a/third_party/blink/renderer/core/paint/old_cull_rect_updater_test.cc b/third_party/blink/renderer/core/paint/old_cull_rect_updater_test.cc
deleted file mode 100644
index 368634b..0000000
--- a/third_party/blink/renderer/core/paint/old_cull_rect_updater_test.cc
+++ /dev/null
@@ -1,413 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h"
-#include "third_party/blink/renderer/core/paint/paint_layer.h"
-
-namespace blink {
-
-class OldCullRectUpdaterTest : public PaintControllerPaintTest {
- protected:
-  CullRect GetCullRect(const char* id) {
-    return GetLayoutObjectByElementId(id)->FirstFragment().GetCullRect();
-  }
-
-  CullRect GetContentsCullRect(const char* id) {
-    return GetLayoutObjectByElementId(id)
-        ->FirstFragment()
-        .GetContentsCullRect();
-  }
-};
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         OldCullRectUpdaterTest,
-                         ::testing::Values(0, kScrollUnification));
-
-// TODO(wangxianzhu): Move other cull rect tests from PaintLayerPainterTest
-// into this file.
-
-CullRect GetCullRect(const PaintLayer& layer) {
-  return layer.GetLayoutObject().FirstFragment().GetCullRect();
-}
-
-TEST_P(OldCullRectUpdaterTest, FixedPositionUnderClipPath) {
-  GetDocument().View()->Resize(800, 600);
-  SetBodyInnerHTML(R"HTML(
-    <div style="height: 100vh"></div>
-    <div style="width: 100px; height: 100px; clip-path: inset(0 0 0 0)">
-      <div id="fixed" style="position: fixed; top: 0; left: 0; width: 1000px;
-                             height: 1000px"></div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("fixed").Rect());
-
-  GetDocument().GetFrame()->DomWindow()->scrollTo(0, 1000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("fixed").Rect());
-
-  GetDocument().View()->Resize(800, 1000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 1000), GetCullRect("fixed").Rect());
-}
-
-TEST_P(OldCullRectUpdaterTest, FixedPositionUnderClipPathWillChangeTransform) {
-  GetDocument().View()->Resize(800, 600);
-  SetBodyInnerHTML(R"HTML(
-    <div style="height: 100vh"></div>
-    <div style="width: 100px; height: 100px; clip-path: inset(0 0 0 0)">
-      <div id="fixed" style="position: fixed; top: 0; left: 0; width: 1000px;
-                             height: 1000px; will-change: transform"></div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 8600), GetCullRect("fixed").Rect());
-
-  GetDocument().GetFrame()->DomWindow()->scrollTo(0, 1000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 8600), GetCullRect("fixed").Rect());
-
-  GetDocument().View()->Resize(800, 2000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 10000), GetCullRect("fixed").Rect());
-}
-
-TEST_P(OldCullRectUpdaterTest,
-       AbsolutePositionUnderNonContainingStackingContext) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
-  SetBodyInnerHTML(R"HTML(
-    <div id="scroller" style="width: 200px; height: 200px; overflow: auto;
-                              position: relative">
-      <div style="height: 0; overflow: hidden; opacity: 0.5; margin: 250px">
-        <div id="absolute"
-             style="width: 100px; height: 100px; position: absolute;
-                    background: green"></div>
-      </div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 200), GetCullRect("absolute").Rect());
-
-  GetDocument().getElementById("scroller")->scrollTo(200, 200);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(RuntimeEnabledFeatures::LayoutNGEnabled()
-                ? gfx::Rect(200, 200, 200, 200)
-                : gfx::Rect(150, 200, 200, 200),
-            GetCullRect("absolute").Rect());
-}
-
-TEST_P(OldCullRectUpdaterTest, StackedChildOfNonStackingContextScroller) {
-  SetBodyInnerHTML(R"HTML(
-    <div id="scroller" style="width: 200px; height: 200px; overflow: auto;
-                              background: white">
-      <div id="child" style="height: 7000px; position: relative"></div>
-    </div>
-  )HTML");
-
-  auto* scroller = GetDocument().getElementById("scroller");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("child").Rect());
-
-  for (int i = 1000; i < 7000; i += 1000) {
-    scroller->scrollTo(0, i);
-    UpdateAllLifecyclePhasesForTest();
-  }
-  // When scrolled to 3800, the cull rect covers the whole scrolling contents.
-  // Then we use this full cull rect on further scroll to avoid repaint.
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetCullRect("child").Rect());
-
-  // The full cull rect still applies when the scroller scrolls to the top.
-  scroller->scrollTo(0, 0);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetCullRect("child").Rect());
-
-  // When child needs repaint, it will recalculate its cull rect.
-  GetPaintLayerByElementId("child")->SetNeedsRepaint();
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("child").Rect());
-
-  // Then scroll to the bottom, child should recalculate it cull rect again.
-  scroller->scrollTo(0, 7000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(0, 2800, 200, 4200), GetCullRect("child").Rect());
-}
-
-TEST_P(OldCullRectUpdaterTest, ContentsCullRectCoveringWholeContentsRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div id="scroller" style="width: 400px; height: 400px; overflow: scroll">
-      <div style="width: 600px; height: 8100px"></div>
-      <div id="child" style="will-change: transform; height: 20px"></div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 600, 4400), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(-4000, -8100, 8600, 4400), GetCullRect("child").Rect());
-
-  auto* scroller = GetDocument().getElementById("scroller");
-  scroller->scrollTo(0, 3600);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 600, 8000), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(-4000, -8100, 8600, 8000), GetCullRect("child").Rect());
-
-  scroller->scrollTo(0, 3800);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 600, 8120), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(-4000, -8100, 8600, 8120), GetCullRect("child").Rect());
-
-  scroller->scrollTo(0, 4000);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 600, 8120), GetContentsCullRect("scroller").Rect());
-  EXPECT_EQ(gfx::Rect(-4000, -8100, 8600, 8120), GetCullRect("child").Rect());
-}
-
-TEST_P(OldCullRectUpdaterTest, SVGForeignObject) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
-  SetBodyInnerHTML(R"HTML(
-    <div id="scroller" style="width: 100px; height: 100px; overflow: scroll">
-      <svg id="svg" style="width: 100px; height: 4000px">
-        <foreignObject id="foreign" style="width: 500px; height: 1000px">
-          <div id="child" style="position: relative">Child</div>
-        </foreignObject>
-      </svg>
-    </div>
-  )HTML");
-
-  auto* child = GetPaintLayerByElementId("child");
-  auto* foreign = GetPaintLayerByElementId("foreign");
-  auto* svg = GetPaintLayerByElementId("svg");
-  EXPECT_FALSE(child->NeedsCullRectUpdate());
-  EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate());
-  EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate());
-
-  GetDocument().getElementById("scroller")->scrollTo(0, 500);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_FALSE(child->NeedsCullRectUpdate());
-  EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate());
-  EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate());
-
-  child->SetNeedsCullRectUpdate();
-  EXPECT_TRUE(child->NeedsCullRectUpdate());
-  EXPECT_TRUE(foreign->DescendantNeedsCullRectUpdate());
-  EXPECT_TRUE(svg->DescendantNeedsCullRectUpdate());
-
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_FALSE(child->NeedsCullRectUpdate());
-  EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate());
-  EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate());
-}
-
-TEST_P(OldCullRectUpdaterTest, LayerUnderSVGHiddenContainer) {
-  SetBodyInnerHTML(R"HTML(
-    <div id="div" style="display: contents">
-      <svg id="svg1"></svg>
-    </div>
-    <svg id="svg2">
-      <defs id="defs"/>
-    </svg>
-  )HTML");
-
-  EXPECT_FALSE(GetCullRect("svg1").Rect().IsEmpty());
-
-  GetDocument().getElementById("defs")->appendChild(
-      GetDocument().getElementById("div"));
-  // This should not crash.
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_FALSE(GetLayoutObjectByElementId("svg1"));
-}
-
-TEST_P(OldCullRectUpdaterTest, PerspectiveDescendants) {
-  SetBodyInnerHTML(R"HTML(
-    <div style="perspective: 1000px">
-      <div style="height: 300px; transform-style: preserve-3d; contain: strict">
-        <div id="target" style="transform: rotateX(20deg)">TARGET</div>
-      </div>
-    </div>
-  )HTML");
-  EXPECT_TRUE(GetCullRect("target").IsInfinite());
-}
-
-class OldCullRectUpdateOnPaintPropertyChangeTest
-    : public OldCullRectUpdaterTest {
- protected:
-  void Check(const String& old_style,
-             const String& new_style,
-             bool expected_needs_repaint,
-             bool expected_needs_cull_rect_update,
-             bool expected_needs_repaint_after_cull_rect_update) {
-    UpdateAllLifecyclePhasesExceptPaint(/*update_cull_rects*/ false);
-    const auto* target_layer = GetPaintLayerByElementId("target");
-    EXPECT_EQ(expected_needs_repaint, target_layer->SelfNeedsRepaint())
-        << old_style << " -> " << new_style;
-    EXPECT_EQ(expected_needs_cull_rect_update,
-              target_layer->NeedsCullRectUpdate())
-        << old_style << " -> " << new_style;
-    UpdateCullRects();
-    EXPECT_EQ(expected_needs_repaint_after_cull_rect_update,
-              target_layer->SelfNeedsRepaint())
-        << old_style << " -> " << new_style;
-  }
-
-  void TestTargetChange(const AtomicString& old_style,
-                        const AtomicString& new_style,
-                        bool expected_needs_repaint,
-                        bool expected_needs_cull_rect_update,
-                        bool expected_needs_repaint_after_cull_rect_update) {
-    SetBodyInnerHTML(html_);
-    auto* target = GetDocument().getElementById("target");
-    target->setAttribute(html_names::kStyleAttr, old_style);
-    UpdateAllLifecyclePhasesForTest();
-    target->setAttribute(html_names::kStyleAttr, new_style);
-    Check(old_style, new_style, expected_needs_repaint,
-          expected_needs_cull_rect_update,
-          expected_needs_repaint_after_cull_rect_update);
-  }
-
-  void TestChildChange(const AtomicString& old_style,
-                       const AtomicString& new_style,
-                       bool expected_needs_repaint,
-                       bool expected_needs_cull_rect_update,
-                       bool expected_needs_repaint_after_cull_rect_update) {
-    SetBodyInnerHTML(html_);
-    auto* child = GetDocument().getElementById("child");
-    child->setAttribute(html_names::kStyleAttr, old_style);
-    UpdateAllLifecyclePhasesForTest();
-    child->setAttribute(html_names::kStyleAttr, new_style);
-    Check(old_style, new_style, expected_needs_repaint,
-          expected_needs_cull_rect_update,
-          expected_needs_repaint_after_cull_rect_update);
-  }
-
-  void TestTargetScroll(const ScrollOffset& old_scroll_offset,
-                        const ScrollOffset& new_scroll_offset,
-                        bool expected_needs_repaint,
-                        bool expected_needs_cull_rect_update,
-                        bool expected_needs_repaint_after_cull_rect_update) {
-    SetBodyInnerHTML(html_);
-    auto* target = GetDocument().getElementById("target");
-    target->scrollTo(old_scroll_offset.x(), old_scroll_offset.y()),
-        UpdateAllLifecyclePhasesForTest();
-    target->scrollTo(new_scroll_offset.x(), new_scroll_offset.y()),
-        Check(String(old_scroll_offset.ToString()),
-              String(new_scroll_offset.ToString()), expected_needs_repaint,
-              expected_needs_cull_rect_update,
-              expected_needs_repaint_after_cull_rect_update);
-  }
-
-  String html_ = R"HTML(
-    <style>
-      #target {
-        width: 100px;
-        height: 100px;
-        position: relative;
-        overflow: scroll;
-        background: white;
-      }
-      #child { width: 1000px; height: 1000px; }
-    </style>
-    <div id="target">
-      <div id="child">child</div>
-    </div>"
-  )HTML";
-};
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         OldCullRectUpdateOnPaintPropertyChangeTest,
-                         ::testing::Values(0, kScrollUnification));
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, Opacity) {
-  TestTargetChange("opacity: 0.2", "opacity: 0.8", false, false, false);
-  TestTargetChange("opacity: 0.5", "", true, false, true);
-  TestTargetChange("", "opacity: 0.5", true, false, true);
-  TestTargetChange("will-change: opacity", "will-change: opacity; opacity: 0.5",
-                   false, false, false);
-  TestTargetChange("will-change: opacity; opacity: 0.5", "will-change: opacity",
-                   false, false, false);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, NonPixelMovingFilter) {
-  TestTargetChange("filter: invert(5%)", "filter: invert(8%)", false, false,
-                   false);
-  TestTargetChange("filter: invert(5%)", "", true, false, true);
-  TestTargetChange("", "filter: invert(5%)", true, false, true);
-  TestTargetChange("will-change: filter; filter: invert(5%)",
-                   "will-change: filter", false, false, false);
-  TestTargetChange("will-change: filter",
-                   "will-change: filter; filter: invert(5%)", false, false,
-                   false);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, PixelMovingFilter) {
-  TestTargetChange("filter: blur(5px)", "filter: blur(8px)", false, false,
-                   false);
-  TestTargetChange("filter: blur(5px)", "", true, true, true);
-  TestTargetChange("", "filter: blur(5px)", true, true, true);
-  TestTargetChange("will-change: filter; filter: blur(5px)",
-                   "will-change: filter", true, false, true);
-  TestTargetChange("will-change: filter",
-                   "will-change: filter; filter: blur(5px)", true, false, true);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, Transform) {
-  TestTargetChange("transform: translateX(10px)", "transform: translateX(20px)",
-                   false, true, false);
-  TestTargetChange("transform: translateX(10px)", "", true, true, true);
-  TestTargetChange("", "transform: translateX(10px)", true, true, true);
-  TestTargetChange("will-change: transform; transform: translateX(10px)",
-                   "will-change: transform", false, true, false);
-  TestTargetChange("will-change: transform",
-                   "will-change: transform; transform: translateX(10px)", false,
-                   true, false);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, AnimatingTransform) {
-  html_ = html_ + R"HTML(
-    <style>
-      @keyframes test {
-        0% { transform: translateX(0); }
-        100% { transform: translateX(200px); }
-      }
-      #target { animation: test 1s infinite; }
-    </style>
-  )HTML";
-  TestTargetChange("transform: translateX(10px)", "transform: translateX(20px)",
-                   false, false, false);
-  TestTargetChange("transform: translateX(10px)", "", false, false, false);
-  TestTargetChange("", "transform: translateX(10px)", false, false, false);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, ScrollContentsSizeChange) {
-  TestChildChange("", "width: 3000px", true, true, true);
-  TestChildChange("", "height: 3000px", true, true, true);
-  TestChildChange("", "width: 50px; height: 50px", true, true, true);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, SmallContentsScroll) {
-  // TODO(wangxianzhu): Optimize for scrollers with small contents.
-  TestTargetScroll(ScrollOffset(), ScrollOffset(100, 200), false, true, false);
-  TestTargetScroll(ScrollOffset(100, 200), ScrollOffset(1000, 1000), false,
-                   true, false);
-  TestTargetScroll(ScrollOffset(1000, 1000), ScrollOffset(), false, true,
-                   false);
-}
-
-TEST_P(OldCullRectUpdateOnPaintPropertyChangeTest, LargeContentsScroll) {
-  html_ = html_ + "<style>#child { width: 10000px; height: 10000px; }</style>";
-  // TODO(wangxianzhu): Optimize for small scroll delta.
-  TestTargetScroll(ScrollOffset(), ScrollOffset(100, 200), false, true, false);
-  TestTargetScroll(ScrollOffset(100, 200), ScrollOffset(8000, 8000), false,
-                   true, true);
-  TestTargetScroll(ScrollOffset(8000, 8000), ScrollOffset(), false, true, true);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
index 5ae0f19..a2b3570 100644
--- a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
+++ b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
@@ -12,7 +12,6 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/paint/cull_rect_updater.h"
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
@@ -60,10 +59,7 @@
   void UpdateCullRects() {
     DCHECK_EQ(GetDocument().Lifecycle().GetState(),
               DocumentLifecycle::kPrePaintClean);
-    if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-      CullRectUpdater(*GetLayoutView().Layer()).Update();
-    else
-      OldCullRectUpdater(*GetLayoutView().Layer()).Update();
+    CullRectUpdater(*GetLayoutView().Layer()).Update();
   }
 
   void PaintContents(const gfx::Rect& interest_rect) {
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index f863ba3..f48b48b 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -100,6 +100,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
@@ -292,6 +293,8 @@
 void PaintLayer::UpdateLayerPositionsAfterLayout() {
   DCHECK(IsRootLayer());
 
+  SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES(
+      "Blink.Layout.UpdateLayerPositionsAfterLayout");
   TRACE_EVENT0("blink,benchmark",
                "PaintLayer::updateLayerPositionsAfterLayout");
   RUNTIME_CALL_TIMER_SCOPE(
@@ -882,10 +885,7 @@
     child->SetNeedsRepaint();
 
   if (child->NeedsCullRectUpdate()) {
-    if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-      SetDescendantNeedsCullRectUpdate();
-    else
-      MarkCompositingContainerChainForNeedsCullRectUpdate();
+    SetDescendantNeedsCullRectUpdate();
   } else {
     child->SetNeedsCullRectUpdate();
   }
@@ -2312,10 +2312,6 @@
   // Self-painting change can change the compositing container chain;
   // invalidate the new chain in addition to the old one.
   MarkCompositingContainerChainForNeedsRepaint();
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    if (SelfOrDescendantNeedsCullRectUpdate())
-      MarkCompositingContainerChainForNeedsCullRectUpdate();
-  }
 
   if (is_self_painting_layer)
     SetNeedsVisualOverflowRecalc();
@@ -2405,10 +2401,6 @@
     // propagated up the new compositing chain.
     if (SelfOrDescendantNeedsRepaint())
       MarkCompositingContainerChainForNeedsRepaint();
-    if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-      if (SelfOrDescendantNeedsCullRectUpdate())
-        MarkCompositingContainerChainForNeedsCullRectUpdate();
-    }
 
     MarkAncestorChainForFlagsUpdate();
   }
@@ -2688,11 +2680,8 @@
   if (needs_cull_rect_update_)
     return;
   needs_cull_rect_update_ = true;
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    if (Parent())
-      Parent()->SetDescendantNeedsCullRectUpdate();
-  } else {
-    MarkCompositingContainerChainForNeedsCullRectUpdate();
+  if (Parent()) {
+    Parent()->SetDescendantNeedsCullRectUpdate();
   }
 }
 
@@ -2701,55 +2690,12 @@
     return;
   forces_children_cull_rect_update_ = true;
   descendant_needs_cull_rect_update_ = true;
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    if (Parent())
-      Parent()->SetDescendantNeedsCullRectUpdate();
-  } else {
-    MarkCompositingContainerChainForNeedsCullRectUpdate();
-  }
-}
-
-void PaintLayer::MarkCompositingContainerChainForNeedsCullRectUpdate() {
-  // This is only used by the old cull rect updater.
-  DCHECK(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-
-  // Mark compositing container chain for needing cull rect update. This is
-  // similar to MarkCompositingContainerChainForNeedsRepaint().
-  PaintLayer* layer = this;
-  while (true) {
-    // For a non-self-painting layer having self-painting descendant, the
-    // descendant will be painted through this layer's Parent() instead of
-    // this layer's Container(), so in addition to the CompositingContainer()
-    // chain, we also need to mark NeedsRepaint for Parent().
-    // TODO(crbug.com/828103): clean up this.
-    if (layer->Parent() && !layer->IsSelfPaintingLayer())
-      layer->Parent()->SetNeedsCullRectUpdate();
-
-    PaintLayer* container = layer->CompositingContainer();
-    if (!container) {
-      auto* owner = layer->GetLayoutObject().GetFrame()->OwnerLayoutObject();
-      if (!owner)
-        break;
-      container = owner->EnclosingLayer();
-    }
-
-    if (container->descendant_needs_cull_rect_update_)
-      break;
-
-    container->descendant_needs_cull_rect_update_ = true;
-
-    // Only propagate the dirty bit up to the display locked ancestor.
-    if (container->GetLayoutObject().ChildPrePaintBlockedByDisplayLock())
-      break;
-
-    layer = container;
+  if (Parent()) {
+    Parent()->SetDescendantNeedsCullRectUpdate();
   }
 }
 
 void PaintLayer::SetDescendantNeedsCullRectUpdate() {
-  // This is only used by the new cull rect updater.
-  DCHECK(RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-
   for (auto* layer = this; layer; layer = layer->Parent()) {
     if (layer->descendant_needs_cull_rect_update_)
       break;
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index e795c352..079f4ce 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -561,7 +561,6 @@
   }
   void SetNeedsCullRectUpdate();
   void SetForcesChildrenCullRectUpdate();
-  void MarkCompositingContainerChainForNeedsCullRectUpdate();
   void SetDescendantNeedsCullRectUpdate();
   void ClearNeedsCullRectUpdate() {
     needs_cull_rect_update_ = false;
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
index 77e296f..b41cadaf 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
@@ -7,7 +7,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
 #include "third_party/blink/renderer/core/paint/cull_rect_updater.h"
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/testing/find_cc_layer.h"
@@ -767,16 +766,12 @@
   {
     OverriddenCullRectScope scope(stacking,
                                   CullRect(gfx::Rect(0, 0, 100, 100)));
-    OverriddenOldCullRectScope old_scope(stacking,
-                                         CullRect(gfx::Rect(0, 0, 100, 100)));
     EXPECT_EQ(gfx::Rect(0, 0, 100, 100), GetCullRect(stacking).Rect());
     EXPECT_EQ(gfx::Rect(0, 0, 100, 100), GetCullRect(absolute).Rect());
     PaintController controller(PaintController::kTransient);
     GraphicsContext context(controller);
     PaintLayerPainter(stacking).Paint(context);
   }
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    UpdateAllLifecyclePhasesForTest();
   // Should restore the original status after OverridingCullRectScope.
   EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect(stacking).Rect());
   EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect(absolute).Rect());
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
index 2a30f17..9f7ee0c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
@@ -745,8 +745,8 @@
   scrollable_area->SetScrollOffset(ScrollOffset(0, 1),
                                    mojom::blink::ScrollType::kProgrammatic);
   UpdateAllLifecyclePhasesExceptPaint();
-  EXPECT_EQ(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled(),
-            GetDocument().View()->GetPaintArtifactCompositor()->NeedsUpdate());
+  EXPECT_FALSE(
+      GetDocument().View()->GetPaintArtifactCompositor()->NeedsUpdate());
   UpdateAllLifecyclePhasesForTest();
   EXPECT_EQ(ScrollOffset(0, 1), scrollable_area->GetScrollOffset());
 }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 3ebc3a7..9336ba1 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -52,7 +52,6 @@
 #include "third_party/blink/renderer/core/paint/find_paint_offset_needing_update.h"
 #include "third_party/blink/renderer/core/paint/find_properties_needing_update.h"
 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
-#include "third_party/blink/renderer/core/paint/old_cull_rect_updater.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
@@ -2500,8 +2499,7 @@
           // treated as scroll translations with overlap testing treatment.
           // A scroll translation for overflow:hidden doesn't have a scroll node
           // and needs full PaintArtifactCompositor update on scroll.
-          (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled() ||
-           properties_->Scroll())) {
+          properties_->Scroll()) {
         if (auto* paint_artifact_compositor =
                 object_.GetFrameView()->GetPaintArtifactCompositor()) {
           bool updated =
@@ -4276,18 +4274,7 @@
   PaintPropertiesChangeInfo properties_changed;
   properties_changed.transform_changed = effective_change_type;
 
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    CullRectUpdater::PaintPropertiesChanged(object, properties_changed);
-  } else {
-    DCHECK(box.Layer());
-    DCHECK(box.HasSelfPaintingLayer());
-    gfx::Vector2dF old_scroll_offset;
-    if (const auto* scroll_translation = properties->ScrollTranslation()) {
-      old_scroll_offset = scroll_translation->Get2dTranslation();
-    }
-    OldCullRectUpdater::PaintPropertiesChanged(
-        object, *box.Layer(), properties_changed, old_scroll_offset);
-  }
+  CullRectUpdater::PaintPropertiesChanged(object, properties_changed);
 }
 
 void PaintPropertyTreeBuilder::DirectlyUpdateOpacityValue(
@@ -4353,46 +4340,7 @@
     }
   }
 
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    if (const auto* properties = object_.FirstFragment().PaintProperties()) {
-      if (const auto* scroll_translation = properties->ScrollTranslation()) {
-        if (scroll_translation->Get2dTranslation() !=
-            context_.old_scroll_offset) {
-          // Scrolling can change overlap relationship for elements fixed to an
-          // overflow: hidden view that programmatically scrolls via script.
-          // In this case the fixed transform doesn't have enough information to
-          // perform the expansion - there is no scroll node to describe the
-          // bounds of the scrollable content.
-          auto* frame_view = object_.GetFrameView();
-          if (frame_view->HasFixedPositionObjects() &&
-              !object_.View()->FirstFragment().PaintProperties()->Scroll()) {
-            frame_view->SetPaintArtifactCompositorNeedsUpdate(
-                PaintArtifactCompositorUpdateReason::
-                    kPaintPropertyTreeBuilderHasFixedPositionObjects);
-          } else if (!object_.IsStackingContext() &&
-                     To<LayoutBoxModelObject>(object_)
-                         .Layer()
-                         ->HasSelfPaintingLayerDescendant()) {
-            // If the scroller is not a stacking context but contains stacked
-            // descendants, we need to update compositing because the stacked
-            // descendants may change overlap relationship with other stacked
-            // elements that are not contained by this scroller.
-            frame_view->SetPaintArtifactCompositorNeedsUpdate(
-                PaintArtifactCompositorUpdateReason::
-                    kPaintPropertyTreeBulderNonStackingContextScroll);
-          }
-        }
-      }
-    }
-  }
-
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    CullRectUpdater::PaintPropertiesChanged(object_, properties_changed_);
-  } else {
-    OldCullRectUpdater::PaintPropertiesChanged(
-        object_, *context_.painting_layer, properties_changed_,
-        context_.old_scroll_offset);
-  }
+  CullRectUpdater::PaintPropertiesChanged(object_, properties_changed_);
 }
 
 bool PaintPropertyTreeBuilder::CanDoDeferredTransformNodeUpdate(
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 76589c104..fb1a0e1f 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -135,9 +135,7 @@
 
 INSTANTIATE_TEST_SUITE_P(All,
                          PaintPropertyTreeBuilderTest,
-                         ::testing::Values(0,
-                                           kUnderInvalidationChecking,
-                                           kScrollUpdateOptimizations));
+                         ::testing::Values(0, kUnderInvalidationChecking));
 
 TEST_P(PaintPropertyTreeBuilderTest, FixedPosition) {
   LoadTestData("fixed-position.html");
@@ -6906,8 +6904,7 @@
   UpdateAllLifecyclePhasesExceptPaint();
 
   EXPECT_EQ(gfx::Vector2dF(), sticky_translation->Get2dTranslation());
-  EXPECT_EQ(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled(),
-            pac->NeedsUpdate());
+  EXPECT_FALSE(pac->NeedsUpdate());
   EXPECT_EQ(gfx::Vector2dF(), cc_transform_node->local.To2dTranslation());
   EXPECT_TRUE(property_trees->transform_tree().needs_update());
   EXPECT_TRUE(cc_transform_node->transform_changed);
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index 225252e..c8d15d18 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -1819,7 +1819,7 @@
   // not a stacking context but contains stacked descendants.
   scroller->setScrollTop(100);
   UpdateAllLifecyclePhasesExceptPaint();
-  EXPECT_TRUE(paint_artifact_compositor->NeedsUpdate());
+  EXPECT_FALSE(paint_artifact_compositor->NeedsUpdate());
   UpdateAllLifecyclePhasesForTest();
   EXPECT_FALSE(paint_artifact_compositor->NeedsUpdate());
 
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.cc b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
index 779aefc..0ff35dff 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
@@ -434,7 +434,7 @@
   if (!AllowTimingDetails())
     return 0.0;
   ResourceLoadTiming* timing = GetResourceLoadTiming();
-  if (!timing)
+  if (!timing || timing->SendStart().is_null())
     return connectEnd();
 
   return Performance::MonotonicTimeToDOMHighResTimeStamp(
@@ -517,8 +517,8 @@
   builder.AddNumber("domainLookupStart", domainLookupStart());
   builder.AddNumber("domainLookupEnd", domainLookupEnd());
   builder.AddNumber("connectStart", connectStart());
-  builder.AddNumber("connectEnd", connectEnd());
   builder.AddNumber("secureConnectionStart", secureConnectionStart());
+  builder.AddNumber("connectEnd", connectEnd());
   builder.AddNumber("requestStart", requestStart());
   builder.AddNumber("responseStart", responseStart());
   builder.AddNumber("responseEnd", responseEnd());
diff --git a/third_party/blink/renderer/modules/credentialmanagement/BUILD.gn b/third_party/blink/renderer/modules/credentialmanagement/BUILD.gn
index dcf904b8..f68350f 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/BUILD.gn
+++ b/third_party/blink/renderer/modules/credentialmanagement/BUILD.gn
@@ -24,6 +24,8 @@
     "federated_credential.h",
     "identity_credential.cc",
     "identity_credential.h",
+    "identity_provider.cc",
+    "identity_provider.h",
     "otp_credential.cc",
     "otp_credential.h",
     "password_credential.cc",
diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
new file mode 100644
index 0000000..de8a08b
--- /dev/null
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
@@ -0,0 +1,49 @@
+// 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 "third_party/blink/renderer/modules/credentialmanagement/identity_provider.h"
+
+#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-blink.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/modules/credentialmanagement/credential_manager_proxy.h"
+#include "third_party/blink/renderer/modules/credentialmanagement/scoped_promise_resolver.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+
+namespace blink {
+
+ScriptPromise IdentityProvider::getUserInfo(
+    ScriptState* script_state,
+    const blink::IdentityProviderConfig* config,
+    ExceptionState& exception_state) {
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  ScriptPromise promise = resolver->Promise();
+  // TODO(crbug.com/1304402): implement the actual logic.
+  return promise;
+}
+
+void IdentityProvider::login(ScriptState* script_state) {
+  // TODO(https://crbug.com/1382193): Determine if we should add an origin
+  // parameter.
+  auto* context = ExecutionContext::From(script_state);
+  auto* request =
+      CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
+  request->SetIdpSigninStatus(context->GetSecurityOrigin(),
+                              mojom::blink::IdpSigninStatus::kSignedIn);
+}
+
+void IdentityProvider::logout(ScriptState* script_state) {
+  // TODO(https://crbug.com/1382193): Determine if we should add an origin
+  // parameter.
+  auto* context = ExecutionContext::From(script_state);
+  auto* request =
+      CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
+  request->SetIdpSigninStatus(context->GetSecurityOrigin(),
+                              mojom::blink::IdpSigninStatus::kSignedOut);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_provider.h b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.h
new file mode 100644
index 0000000..e746c92
--- /dev/null
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.h
@@ -0,0 +1,31 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGEMENT_IDENTITY_PROVIDER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGEMENT_IDENTITY_PROVIDER_H_
+
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+
+namespace blink {
+
+class MODULES_EXPORT IdentityProvider : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static ScriptPromise getUserInfo(ScriptState*,
+                                   const blink::IdentityProviderConfig*,
+                                   ExceptionState&);
+
+  static void login(ScriptState*);
+  static void logout(ScriptState*);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGEMENT_IDENTITY_PROVIDER_H_
diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_provider.idl b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.idl
new file mode 100644
index 0000000..02d280b
--- /dev/null
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.idl
@@ -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.
+
+[
+    Exposed=Window,
+    SecureContext,
+    RuntimeEnabled=FedCmUserInfo
+] dictionary IdentityUserInfo {
+  USVString email;
+  USVString name;
+  USVString given_name;
+  USVString picture;
+};
+
+// https://fedidcg.github.io/FedCM/#identityprovider
+[
+    Exposed=Window,
+    SecureContext,
+    RuntimeEnabled=FedCmIdentityProviderInterface
+] interface IdentityProvider {
+    // Allows an IDP to request user info from its own iframe embeded on an RP.
+    [RuntimeEnabled=FedCmUserInfo, CallWith=ScriptState, RaisesException, MeasureAs=FedCmUserInfo]
+    static Promise<sequence<IdentityUserInfo>?> getUserInfo(IdentityProviderConfig config);
+
+    [RuntimeEnabled=FedCmIdpSigninStatus, CallWith=ScriptState]
+    static void login();
+    [RuntimeEnabled=FedCmIdpSigninStatus, CallWith=ScriptState]
+    static void logout();
+};
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 b547e87..deeecd6 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
@@ -1201,6 +1201,38 @@
   return output;
 }
 
+MLOperand* MLGraphBuilder::sigmoid(const MLOperand* input,
+                                   ExceptionState& exception_state) {
+  auto* sigmoid = MakeGarbageCollected<MLOperator>(
+      this, MLOperator::OperatorKind::kSigmoid);
+  // According to WebNN spec
+  // https://webmachinelearning.github.io/webnn/#api-mlgraphbuilder-sigmoid, the
+  // output tensor of sigmoid has the same type and dimensions as its input.
+  // And the input type must be one of the floating point types.
+  if (!IsFloatingPointType(input->Type())) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kDataError,
+        "The input type must be one of the floating point types.");
+    return nullptr;
+  }
+  String error_message;
+  auto* output = MLOperand::ValidateAndCreateOutput(
+      this, input->Type(), input->Dimensions(), sigmoid, error_message);
+  if (!output) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
+                                      error_message);
+    return nullptr;
+  }
+  sigmoid->Connect({input}, {output});
+  return output;
+}
+
+MLOperator* MLGraphBuilder::sigmoid(ExceptionState& exception_state) {
+  // Create the sigmoid operator that would be used as an activation function.
+  return MakeGarbageCollected<MLOperator>(this,
+                                          MLOperator::OperatorKind::kSigmoid);
+}
+
 ScriptPromise MLGraphBuilder::buildAsync(ScriptState* script_state,
                                          const MLNamedOperands& named_outputs,
                                          ExceptionState& exception_state) {
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h
index b3919769..b283db8 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.h
@@ -117,6 +117,9 @@
 
   MLOperand* softmax(const MLOperand* input, ExceptionState& exception_state);
 
+  MLOperand* sigmoid(const MLOperand* input, ExceptionState& exception_state);
+  MLOperator* sigmoid(ExceptionState& exception_state);
+
   ScriptPromise buildAsync(ScriptState* script_state,
                            const MLNamedOperands& named_outputs,
                            ExceptionState& exception_state);
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
index 70e0ac7..d7c5f42 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
@@ -103,5 +103,8 @@
 
   [RaisesException] MLOperand softmax(MLOperand input);
 
+  [RaisesException] MLOperand sigmoid(MLOperand input);
+  [RaisesException] MLOperator sigmoid();
+
   [CallWith=ScriptState, RaisesException] Promise<MLGraph> buildAsync(MLNamedOperands outputs);
 };
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 323a146..7b85713 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
@@ -69,17 +69,37 @@
   NotShared<DOMArrayBufferView> buffer_view;
   switch (type) {
     case V8MLOperandType::Enum::kFloat32: {
-      auto* float32_array = blink::DOMFloat32Array::Create(size);
-      buffer_view = NotShared<DOMArrayBufferView>(float32_array);
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMFloat32Array::Create(size));
+      break;
+    }
+    case V8MLOperandType::Enum::kFloat16: {
+      // Using Uint16Array for float16 is a workaround of WebNN spec issue:
+      // https://github.com/webmachinelearning/webnn/issues/127
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMUint16Array::Create(size));
       break;
     }
     case V8MLOperandType::Enum::kInt32: {
-      auto* int32_array = blink::DOMInt32Array::Create(size);
-      buffer_view = NotShared<DOMArrayBufferView>(int32_array);
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMInt32Array::Create(size));
       break;
     }
-    default:
-      NOTREACHED();
+    case V8MLOperandType::Enum::kUint32: {
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMUint32Array::Create(size));
+      break;
+    }
+    case V8MLOperandType::Enum::kInt8: {
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMInt8Array::Create(size));
+      break;
+    }
+    case V8MLOperandType::Enum::kUint8: {
+      buffer_view =
+          NotShared<DOMArrayBufferView>(blink::DOMUint8Array::Create(size));
+      break;
+    }
   }
   CHECK(buffer_view.Get());
   return buffer_view;
@@ -2230,6 +2250,47 @@
   }
 }
 
+TEST_F(MLGraphBuilderTest, SigmoidTest) {
+  V8TestingScope scope;
+  auto* builder = CreateMLGraphBuilder(scope);
+  {
+    // Test building sigmoid with float32 input.
+    Vector<uint32_t> input_shape({3, 4, 5});
+    auto* input = BuildInput(scope, builder, "input", input_shape,
+                             V8MLOperandType::Enum::kFloat32);
+    auto* output = builder->sigmoid(input, scope.GetExceptionState());
+    EXPECT_NE(output, nullptr);
+    EXPECT_EQ(output->Kind(), MLOperand::OperandKind::kOutput);
+    EXPECT_EQ(output->Type(), V8MLOperandType::Enum::kFloat32);
+    EXPECT_EQ(output->Dimensions(), input_shape);
+    const MLOperator* sigmoid = output->Operator();
+    EXPECT_NE(sigmoid, nullptr);
+    EXPECT_EQ(sigmoid->Kind(), MLOperator::OperatorKind::kSigmoid);
+    EXPECT_EQ(sigmoid->IsConnected(), true);
+    EXPECT_EQ(sigmoid->Options(), nullptr);
+  }
+  {
+    // Test throwing exception when building sigmoid with int32 input.
+    Vector<uint32_t> input_shape({3, 4, 5});
+    auto* input = BuildInput(scope, builder, "input", input_shape,
+                             V8MLOperandType::Enum::kInt32);
+    auto* output = builder->sigmoid(input, scope.GetExceptionState());
+    EXPECT_EQ(output, nullptr);
+    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
+              DOMExceptionCode::kDataError);
+    EXPECT_EQ(scope.GetExceptionState().Message(),
+              "The input type must be one of the floating point types.");
+  }
+  {
+    // Test building sigmoid operator.
+    auto* sigmoid = builder->sigmoid(scope.GetExceptionState());
+    EXPECT_NE(sigmoid, nullptr);
+    EXPECT_EQ(sigmoid->Kind(), MLOperator::OperatorKind::kSigmoid);
+    EXPECT_EQ(sigmoid->IsConnected(), false);
+    EXPECT_EQ(sigmoid->Options(), nullptr);
+  }
+}
+
 class FakeMLGraphBackend final : public MLGraph {
  public:
   // Create and build a FakeMLGraphBackend object. Resolve the promise with
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
index 124dc06d..733cfd5 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc
@@ -29,6 +29,18 @@
 
 namespace blink {
 
+// Maps MLOperand pointer address to its XNNPACK Value ID.
+//
+// Use `const void*` here because this HashMap might be used in a worker thread
+// that doesn't support GC.
+//
+// This map is only used in CreateXnnSubgraphAndRuntime(), who owns references
+// to MLOperands, so it's safe to use raw pointers here.
+//
+// TODO(crbug.com/1273291): Consider getting GC support in worker threads, so
+// the safer `HeapHashMap<Member<MLOperand>, uint32_t>` could be used instead.
+using OperandValueIdMap = HashMap<const void*, uint32_t>;
+
 #define XNN_CHECK_STATUS_AND_SET_ERROR_MESSAGE(xnn_func)            \
   {                                                                 \
     xnn_status status = xnn_func;                                   \
@@ -40,6 +52,14 @@
     }                                                               \
   }
 
+#define XNN_CHECK_STATUS(xnn_func)      \
+  {                                     \
+    xnn_status status = xnn_func;       \
+    if (status != xnn_status_success) { \
+      return status;                    \
+    }                                   \
+  }
+
 namespace {
 
 String XnnStatusToString(xnn_status status) {
@@ -61,6 +81,27 @@
   }
 }
 
+String XnnDataTypeToString(xnn_datatype datatype) {
+  switch (datatype) {
+    case xnn_datatype_invalid:
+      return "xnn_datatype_invalid";
+    case xnn_datatype_fp32:
+      return "xnn_datatype_fp32";
+    case xnn_datatype_fp16:
+      return "xnn_datatype_fp16";
+    case xnn_datatype_qint8:
+      return "xnn_datatype_qint8";
+    case xnn_datatype_quint8:
+      return "xnn_datatype_quint8";
+    case xnn_datatype_qint32:
+      return "xnn_datatype_qint32";
+    case xnn_datatype_qcint8:
+      return "xnn_datatype_qcint8";
+    case xnn_datatype_qcint32:
+      return "xnn_datatype_qcint32";
+  }
+}
+
 DOMExceptionCode XnnStatusToDOMExceptionCode(xnn_status status) {
   switch (status) {
     case xnn_status_success:
@@ -151,6 +192,134 @@
 
 SharedXnnpackContext* SharedXnnpackContext::instance_ = nullptr;
 
+xnn_datatype GetXnnDataType(V8MLOperandType::Enum operand_type) {
+  switch (operand_type) {
+    case V8MLOperandType::Enum::kFloat32:
+      return xnn_datatype_fp32;
+    case V8MLOperandType::Enum::kFloat16:
+      return xnn_datatype_fp16;
+    case V8MLOperandType::Enum::kInt32:
+    case V8MLOperandType::Enum::kUint32:
+    case V8MLOperandType::Enum::kInt8:
+    case V8MLOperandType::Enum::kUint8:
+      // TODO(crbug.com/1273291): Support the quantized integer types that is a
+      // WebNN v2 feature tracked by:
+      // https://github.com/webmachinelearning/webnn/issues/128.
+      return xnn_datatype_invalid;
+  }
+}
+
+Vector<size_t> GetXnnDimensions(const Vector<uint32_t>& operand_dimensions) {
+  Vector<size_t> xnn_dimensions;
+  for (const auto d : operand_dimensions) {
+    xnn_dimensions.push_back(base::checked_cast<size_t>(d));
+  }
+  return xnn_dimensions;
+}
+
+// DefineXnnValue() defines an XNNPACK Value for a WebNN operand. If there are
+// no errors, it returns xnn_status_success and the value_id is set to the
+// XNNPACK Value's ID.
+//
+// This method should not be used directly. Please use the specialized
+// DefineExternalXnnValue(), DefineInternalXnnValue() and DefineStaticXnnValue()
+// methods instead.
+//
+// If the data pointer is not nullptr, it is safe to be used to initialize the
+// XNNPACK Value. Because its buffer is holded by this MLGraph object's
+// static_data_buffers_ member, it would outlive the XNNPACK Value who uses it.
+xnn_status DefineXnnValue(xnn_subgraph_t subgraph,
+                          const MLOperand* operand,
+                          const DataBufferPtr& data,
+                          uint32_t external_value_id,
+                          uint32_t& value_id,
+                          String& error_message) {
+  DCHECK(operand);
+  xnn_datatype datatype = GetXnnDataType(operand->Type());
+  if (datatype == xnn_datatype_invalid) {
+    error_message = "The operand type (" +
+                    V8MLOperandType(operand->Type()).AsString() +
+                    ") is not supported.";
+    return xnn_status_unsupported_parameter;
+  }
+  Vector<size_t> dims = GetXnnDimensions(operand->Dimensions());
+
+  uint32_t flags = 0;
+  if (external_value_id != XNN_INVALID_VALUE_ID) {
+    // External Values should not be initialized with static data.
+    DCHECK(!data);
+    switch (operand->Kind()) {
+      case MLOperand::OperandKind::kInput:
+        flags = XNN_VALUE_FLAG_EXTERNAL_INPUT;
+        break;
+      case MLOperand::OperandKind::kOutput:
+        flags = XNN_VALUE_FLAG_EXTERNAL_OUTPUT;
+        break;
+      case MLOperand::OperandKind::kConstant:
+        // Should not define an external Value for constant operand.
+        NOTREACHED();
+        break;
+    }
+  }
+
+  switch (datatype) {
+    case xnn_datatype_fp32:
+    case xnn_datatype_fp16:
+      XNN_CHECK_STATUS_AND_SET_ERROR_MESSAGE(xnn_define_tensor_value(
+          subgraph, datatype, dims.size(), dims.data(), data.get(),
+          external_value_id, flags, &value_id));
+      break;
+    default:
+      // TODO(crbug.com/1273291): Call xnn_define_quantized_tensor_value() once
+      // WebNN supports quantized integer types that is tracked by
+      // https://github.com/webmachinelearning/webnn/issues/128
+      error_message = "The data type (" + XnnDataTypeToString(datatype) +
+                      ") is not supported.";
+      return xnn_status_unsupported_parameter;
+  }
+
+  return xnn_status_success;
+}
+
+// Define an external XNNPACK Value given a WebNN graph's input or output
+// operand.
+xnn_status DefineExternalXnnValue(xnn_subgraph_t subgraph,
+                                  const MLOperand* operand,
+                                  uint32_t external_value_id,
+                                  uint32_t& value_id,
+                                  String& error_message) {
+  DCHECK_NE(external_value_id, XNN_INVALID_VALUE_ID);
+  return DefineXnnValue(subgraph, operand, DataBufferPtr(nullptr),
+                        external_value_id, value_id, error_message);
+}
+
+// Define an internal XNNPACK Value given a WebNN graph's intermediate
+// operand that connects with two operators.
+xnn_status DefineInternalXnnValue(xnn_subgraph_t subgraph,
+                                  const MLOperand* operand,
+                                  uint32_t& value_id,
+                                  String& error_message) {
+  // Set external_value_id to XNN_INVALID_VALUE_ID, so an internal ID will be
+  // created for the Value and value_id will be set to that internal ID.
+  return DefineXnnValue(subgraph, operand, DataBufferPtr(nullptr),
+                        XNN_INVALID_VALUE_ID, value_id, error_message);
+}
+
+// Define a static XNNPACK Value given a WebNN graph's constant operand and its
+// data. XNNPACK requires the life-time of the data must exceed the life-time of
+// the Subgraph object, and of any Runtime objects created from the Subgraph.
+xnn_status DefineStaticXnnValue(xnn_subgraph_t subgraph,
+                                const MLOperand* operand,
+                                const DataBufferPtr& data,
+                                uint32_t& value_id,
+                                String& error_message) {
+  DCHECK(data);
+  // Set external_value_id to XNN_INVALID_VALUE_ID, so an internal ID will be
+  // created for the Value and value_id will be set to that internal ID.
+  return DefineXnnValue(subgraph, operand, data, XNN_INVALID_VALUE_ID, value_id,
+                        error_message);
+}
+
 }  // namespace
 
 // static
@@ -172,7 +341,13 @@
 
 MLGraphXnnpack::MLGraphXnnpack(MLContext* context) : MLGraph(context) {}
 
-MLGraphXnnpack::~MLGraphXnnpack() = default;
+MLGraphXnnpack::~MLGraphXnnpack() {
+  // Explicitly destroy XNNPACK Runtime before releasing static data buffers. It
+  // ensures the lifetime of static data buffers exceeds the lifetime of this
+  // Runtime object.
+  xnn_runtime_.reset();
+  static_data_buffers_.clear();
+}
 
 // static
 HeapVector<Member<const MLOperator>>*
@@ -238,6 +413,14 @@
   return toposorted_operators;
 }
 
+const ExternalValueIdMap& MLGraphXnnpack::GetInputExternalValueIdMap() const {
+  return input_external_value_id_map_;
+}
+
+const ExternalValueIdMap& MLGraphXnnpack::GetOutputExternalValueIdMap() const {
+  return output_external_value_id_map_;
+}
+
 void MLGraphXnnpack::BuildAsyncImpl(const MLNamedOperands& named_outputs,
                                     ScriptPromiseResolver* resolver) {
   // TODO(crbug.com/1273291): Revisit whether the topological sorting should run
@@ -372,10 +555,105 @@
   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> subgraph(
       subgraph_ptr, &xnn_delete_subgraph);
 
-  // TODO(ningxin.hu@intel.com): Define XNNPACK Subgraph external Values for
-  // the named output operands. Visit the topologically sorted operators. For
-  // each operator, define the XNNPACK Subgraph Node for the operator and Values
-  // for its input and output operands.
+  // Map the operand to its XNNPACK Value ID.
+  OperandValueIdMap operand_value_id_map;
+  // The ID is used to define an external XNNPACK Value. It should be increased
+  // by 1 after each definition.
+  uint32_t external_value_id = 0;
+
+  for (const auto& output : named_outputs) {
+    // Define an external XNNPACK Value for the graph's output operand.
+    const auto& [name, operand] = output;
+    // The external Value ID should be in the [0, external_value_ids_num - 1]
+    // range.
+    DCHECK_LT(external_value_id, external_value_ids_num);
+    uint32_t value_id;
+    XNN_CHECK_STATUS(DefineExternalXnnValue(
+        subgraph.get(), operand, external_value_id, value_id, error_message));
+    // If the external Value ID is provided, the value_id should be set to that
+    // ID.
+    DCHECK_EQ(external_value_id, value_id);
+    // Increase the ID by 1 for defining the next external Value.
+    external_value_id++;
+    operand_value_id_map.insert(operand.Get(), value_id);
+    output_external_value_id_map_.insert(name, value_id);
+  }
+
+  // Visit the operators in topological order. For each operator, define XNNPACK
+  // Values for its input and output operands.
+  for (const auto current_operator : toposorted_operators) {
+    for (const auto& operand : current_operator->Inputs()) {
+      if (operand_value_id_map.Contains(operand.Get())) {
+        // The XNNPACK Value is already defined for this operand, skip it.
+        continue;
+      }
+      switch (operand->Kind()) {
+        case MLOperand::OperandKind::kInput: {
+          // Define an external XNNPACK Value for the graph's input operand.
+          // The external ID should be in the [0, external_value_ids_num - 1]
+          // range.
+          DCHECK_LT(external_value_id, external_value_ids_num);
+          uint32_t value_id;
+          XNN_CHECK_STATUS(DefineExternalXnnValue(subgraph.get(), operand,
+                                                  external_value_id, value_id,
+                                                  error_message));
+          // If the external Value ID is provided, the value_id should be set to
+          // that ID.
+          DCHECK_EQ(external_value_id, value_id);
+          // Increase the ID by 1 for defining the next external Value.
+          external_value_id++;
+          operand_value_id_map.insert(operand.Get(), value_id);
+          input_external_value_id_map_.insert(operand->Name(), value_id);
+          break;
+        }
+        case MLOperand::OperandKind::kConstant: {
+          // Define a static XNNPACK Value for this constant operand. Because
+          // XNNPACK requires the static data of a static XNNPACK Value must
+          // exceed the life-time of its Subgraph and Runtime objects, a new
+          // buffer is allocated and kept alive by this MLGraphXnnpack object.
+          // The contents of this constant operand are copied from the array
+          // buffer into the newly-allocated buffer and it is used to initialize
+          // the XNNPACK Value.
+          const auto* array_buffer_view = operand->ArrayBufferView();
+          auto data =
+              std::make_unique<uint8_t[]>(array_buffer_view->byteLength());
+          DCHECK(data);
+          memcpy(data.get(), array_buffer_view->BaseAddress(),
+                 array_buffer_view->byteLength());
+          uint32_t value_id;
+          XNN_CHECK_STATUS(DefineStaticXnnValue(subgraph.get(), operand, data,
+                                                value_id, error_message));
+          operand_value_id_map.insert(operand.Get(), value_id);
+          static_data_buffers_.push_back(std::move(data));
+          break;
+        }
+        case MLOperand::OperandKind::kOutput:
+          // Because the operators are visited in topological order, if this
+          // operand is an intermediate operand, it should already be defined as
+          // an output operand of the dependent operator.
+          NOTREACHED();
+          break;
+      }
+    }
+
+    for (const auto& operand : current_operator->Outputs()) {
+      if (operand_value_id_map.Contains(operand.Get())) {
+        // If the XNNPACK Value is already defined for this operand, skip it.
+        continue;
+      }
+      // Because the graph's output operands are already defined before, this
+      // operand should be an intermediate operand that connects with two
+      // operators. Define an internal XNNPACK Value for this operand.
+      uint32_t value_id;
+      XNN_CHECK_STATUS(DefineInternalXnnValue(subgraph.get(), operand, value_id,
+                                              error_message));
+      operand_value_id_map.insert(operand.Get(), value_id);
+    }
+
+    // TODO(ningxin.hu@intel.com): Define the XNNPACK Node for the current
+    // operator. The operand_value_id_map will be used to find its corresponding
+    // input and output XNNPACK Values.
+  }
 
   xnn_runtime_t runtime_ptr = nullptr;
   XNN_CHECK_STATUS_AND_SET_ERROR_MESSAGE(
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
index f6bd8615..c756a90 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.h
@@ -19,6 +19,9 @@
 class SharedXnnpackContext;
 }
 
+using ExternalValueIdMap = HashMap<String, uint32_t>;
+using DataBufferPtr = std::unique_ptr<uint8_t[]>;
+
 class MODULES_EXPORT MLGraphXnnpack final : public MLGraph {
  public:
   // Create and build an MLGraphXnnpack object. Resolve the promise with
@@ -50,6 +53,9 @@
   static HeapVector<Member<const MLOperator>>* GetOperatorsInTopologicalOrder(
       const MLNamedOperands& named_outputs);
 
+  const ExternalValueIdMap& GetInputExternalValueIdMap() const;
+  const ExternalValueIdMap& GetOutputExternalValueIdMap() const;
+
  private:
   // Post the XNNPACK Subgraph and Runtime building to a background thread.
   void BuildAsyncImpl(const MLNamedOperands& named_outputs,
@@ -105,6 +111,17 @@
   // library for graph building and execution.
   scoped_refptr<SharedXnnpackContext> xnn_context_;
 
+  // Holds the static data of XNNPACK Values for MLGraph's constant operands.
+  // The data must outlive XNNPACK Subgraph and Runtime objects using them.
+  Vector<DataBufferPtr> static_data_buffers_;
+
+  // Map the names of the MLGraph's inputs/outputs to the XNNPACK external Value
+  // IDs. They will be used to set up the xnn_external_value structures from the
+  // input/output named array buffer views when invoking the XNNPACK Runtime
+  // object for the MLGraph compute.
+  ExternalValueIdMap input_external_value_id_map_;
+  ExternalValueIdMap output_external_value_id_map_;
+
   // The XNNPACK Runtime object for the accelerated executions.
   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> xnn_runtime_{
       nullptr, &xnn_delete_runtime};
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack_test.cc
index efcccfd..3e7b051 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack_test.cc
@@ -58,12 +58,6 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         MLGraphXnnpackTest,
-                         ::testing::Values(ExecutionMode::kAsync,
-                                           ExecutionMode::kSync),
-                         ExecutionModeParamToString);
-
 TEST_F(MLGraphXnnpackTest, TopoSortOperatorsTest) {
   V8TestingScope scope;
   auto* builder = CreateMLGraphBuilder(scope);
@@ -122,4 +116,158 @@
   }
 }
 
+TEST_P(MLGraphXnnpackTest, DefineXnnpackValuesTest) {
+  V8TestingScope scope;
+  auto* builder = CreateMLGraphBuilder(scope);
+  Vector<uint32_t> shape({1, 4, 4, 3});
+  const auto OperandTypes = {V8MLOperandType::Enum::kFloat32,
+                             V8MLOperandType::Enum::kFloat16};
+  {
+    // Test defining XNNPACK Values for operands in the following topology:
+    //       [input0] [input1]
+    //           \   /
+    //            add
+    //             |
+    //          [output]
+    for (const auto operand_type : OperandTypes) {
+      auto* input0 = BuildInput(scope, builder, "input0", shape, operand_type);
+      auto* input1 = BuildInput(scope, builder, "input1", shape, operand_type);
+      auto* output = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, input0, input1);
+      auto [graph, exception] =
+          BuildGraph(scope, builder, {{"output", output}});
+      EXPECT_NE(graph, nullptr);
+      MLGraphXnnpack* xnnpack_graph = static_cast<MLGraphXnnpack*>(graph.Get());
+      const auto& output_externals =
+          xnnpack_graph->GetOutputExternalValueIdMap();
+      EXPECT_EQ(output_externals.size(), 1u);
+      EXPECT_EQ(output_externals.Contains("output"), true);
+      // MLGraphXnnpack defines output external Values first.
+      EXPECT_EQ(output_externals.at("output"), 0u);
+      const auto& input_externals = xnnpack_graph->GetInputExternalValueIdMap();
+      EXPECT_EQ(input_externals.size(), 2u);
+      EXPECT_EQ(input_externals.Contains("input0"), true);
+      EXPECT_EQ(input_externals.Contains("input1"), true);
+      EXPECT_EQ(input_externals.at("input0"), 1u);
+      EXPECT_EQ(input_externals.at("input1"), 2u);
+    }
+  }
+  {
+    // Test defining XNNPACK Values for the operands in the following topology:
+    //       [input] [constant]
+    //           \   /
+    //            add
+    //             |
+    //          [output]
+    for (const auto operand_type : OperandTypes) {
+      auto* input = BuildInput(scope, builder, "input", shape, operand_type);
+      auto* constant = BuildConstant(scope, builder, shape, operand_type);
+      auto* output = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, input, constant);
+      auto [graph, exception] =
+          BuildGraph(scope, builder, {{"output", output}});
+      EXPECT_NE(graph, nullptr);
+      MLGraphXnnpack* xnnpack_graph = static_cast<MLGraphXnnpack*>(graph.Get());
+      const auto& output_externals =
+          xnnpack_graph->GetOutputExternalValueIdMap();
+      EXPECT_EQ(output_externals.size(), 1u);
+      EXPECT_EQ(output_externals.Contains("output"), true);
+      // MLGraphXnnpack defines output external Values first.
+      EXPECT_EQ(output_externals.at("output"), 0u);
+      const auto& input_externals = xnnpack_graph->GetInputExternalValueIdMap();
+      EXPECT_EQ(input_externals.size(), 1u);
+      EXPECT_EQ(input_externals.Contains("input"), true);
+      EXPECT_EQ(input_externals.at("input"), 1u);
+    }
+  }
+  {
+    // Test defining XNNPACK Values for the operands in the following topology:
+    //       [input] [constant0]
+    //           \   /
+    //            add
+    //             |
+    //      [intermediate]  [constant1]
+    //                  \   /
+    //                   add
+    //                    |
+    //                 [output]
+    for (const auto operand_type : OperandTypes) {
+      auto* input = BuildInput(scope, builder, "input", shape, operand_type);
+      auto* constant0 = BuildConstant(scope, builder, shape, operand_type);
+      auto* intermediate = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, input, constant0);
+      auto* constant1 = BuildConstant(scope, builder, shape, operand_type);
+      auto* output = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, intermediate, constant1);
+      auto [graph, exception] =
+          BuildGraph(scope, builder, {{"output", output}});
+      EXPECT_NE(graph, nullptr);
+      MLGraphXnnpack* xnnpack_graph = static_cast<MLGraphXnnpack*>(graph.Get());
+      const auto& output_externals =
+          xnnpack_graph->GetOutputExternalValueIdMap();
+      EXPECT_EQ(output_externals.size(), 1u);
+      EXPECT_EQ(output_externals.Contains("output"), true);
+      // MLGraphXnnpack defines output external Values first, so the external
+      // Value's ID of the output operand should start from 0.
+      EXPECT_EQ(output_externals.at("output"), 0u);
+      const auto& input_externals = xnnpack_graph->GetInputExternalValueIdMap();
+      EXPECT_EQ(input_externals.size(), 1u);
+      EXPECT_EQ(input_externals.Contains("input"), true);
+      EXPECT_EQ(input_externals.at("input"), 1u);
+    }
+  }
+  {
+    // Test defining XNNPACK Values for the operands in the following topology:
+    //     [input0] [input1]
+    //           \   /
+    //            add
+    //             |
+    //      [intermediate]  [input2]
+    //             |     \   /
+    //            relu    add
+    //             |       |
+    //       [output0]   [output1]
+    for (const auto operand_type : OperandTypes) {
+      auto* input0 = BuildInput(scope, builder, "input0", shape, operand_type);
+      auto* input1 = BuildInput(scope, builder, "input1", shape, operand_type);
+      auto* intermediate = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, input0, input1);
+      auto* output0 = builder->relu(intermediate, scope.GetExceptionState());
+      auto* input2 = BuildInput(scope, builder, "input2", shape, operand_type);
+      auto* output1 = BuildElementWiseBinary(
+          scope, builder, ElementWiseBinaryKind::kAdd, intermediate, input2);
+      auto [graph, exception] = BuildGraph(
+          scope, builder, {{"output0", output0}, {"output1", output1}});
+      EXPECT_NE(graph, nullptr);
+      MLGraphXnnpack* xnnpack_graph = static_cast<MLGraphXnnpack*>(graph.Get());
+      const auto& output_externals =
+          xnnpack_graph->GetOutputExternalValueIdMap();
+      EXPECT_EQ(output_externals.size(), 2u);
+      EXPECT_EQ(output_externals.Contains("output0"), true);
+      EXPECT_EQ(output_externals.Contains("output1"), true);
+      // MLGraphXnnpack defines output external Values first, so the external
+      // Value's ID of the output operand should start from 0.
+      EXPECT_EQ(output_externals.at("output0"), 0u);
+      EXPECT_EQ(output_externals.at("output1"), 1u);
+      const auto& input_externals = xnnpack_graph->GetInputExternalValueIdMap();
+      EXPECT_EQ(input_externals.size(), 3u);
+      EXPECT_EQ(input_externals.Contains("input0"), true);
+      EXPECT_EQ(input_externals.Contains("input1"), true);
+      EXPECT_EQ(input_externals.Contains("input2"), true);
+      // MLGraphXnnpack defines input external Values in the topological order
+      // of operators, so the Value ID of input2 should be greater than input0
+      // and input1.
+      EXPECT_EQ(input_externals.at("input0"), 2u);
+      EXPECT_EQ(input_externals.at("input1"), 3u);
+      EXPECT_EQ(input_externals.at("input2"), 4u);
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         MLGraphXnnpackTest,
+                         ::testing::Values(ExecutionMode::kAsync,
+                                           ExecutionMode::kSync),
+                         ExecutionModeParamToString);
+
 }  // namespace blink
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 3d0ca5d6b..5241497d 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_operator.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_operator.h
@@ -38,7 +38,8 @@
     kRelu,
     kReshape,
     kResample,
-    kSoftmax
+    kSoftmax,
+    kSigmoid
   };
 
   // It is safe for a caller, usually a MLGraphBuidler operation build method,
diff --git a/third_party/blink/renderer/platform/exported/web_url_response.cc b/third_party/blink/renderer/platform/exported/web_url_response.cc
index 8a6afa6..767178d 100644
--- a/third_party/blink/renderer/platform/exported/web_url_response.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_response.cc
@@ -205,7 +205,24 @@
   // If there's no received headers end time, don't set load timing.  This is
   // the case for non-HTTP requests, requests that don't go over the wire, and
   // certain error cases.
-  if (!head.load_timing.receive_headers_end.is_null()) {
+  //
+  // https://crbug.com/1382255: Because the resource-fetching request of
+  // prefetch occurs before the navigation, both `requestStart` and
+  // `responseStart` are negative, measured with respect to `startTime` of the
+  // navigation. Do not set the `ResourceLoadTiming` for the prefetch navigation
+  // response; then `PerformanceResourceTiming` won't be able to retrieve the
+  // timing info, resulting in setting `requestStart` and `responseStart` to
+  // their respective previous timeline event.
+  //
+  // Not setting the `ResourceLoadTiming` does not affect the DNS and TCP
+  // timings (domainLookupStart, domainLookupEnd, connectStart,
+  // secureConnectionStart and connectEnd) because these values are null for the
+  // prefetch navigation. See
+  // https://docs.google.com/document/d/1XbLImIqGoHgxJZnscWoZIX8IlIiHOlXvBW81B_3HScc
+  // (Chromium org access) for the navigation timing events timeline.
+  if (!head.load_timing.receive_headers_end.is_null() &&
+      head.navigation_delivery_type !=
+          network::mojom::NavigationDeliveryType::kNavigationalPrefetch) {
     response.SetLoadTiming(ToMojoLoadTiming(head.load_timing));
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 9c665df..05d0154 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -451,15 +451,13 @@
     // If the new layer is the first using the nearest directly composited
     // ancestor, it can't be merged into any previous layers, so skip the merge
     // and overlap loop below.
-    if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-      if (const auto* composited_transform =
-              new_layer.GetPropertyTreeState()
-                  .Transform()
-                  .NearestDirectlyCompositedAncestor()) {
-        if (directly_composited_transforms.insert(composited_transform)
-                .is_new_entry) {
-          continue;
-        }
+    if (const auto* composited_transform =
+            new_layer.GetPropertyTreeState()
+                .Transform()
+                .NearestDirectlyCompositedAncestor()) {
+      if (directly_composited_transforms.insert(composited_transform)
+              .is_new_entry) {
+        continue;
       }
     }
 
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
index 06066090..93d3460 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
@@ -15,6 +15,11 @@
 namespace blink {
 namespace {
 
+// todo(1399566): Add a IsWithinEpsilon method for SkColor4f.
+bool IsWithinEpsilon(float a, float b) {
+  return std::abs(a - b) < std::numeric_limits<float>::epsilon();
+}
+
 // SkColorFilterWrapper implementation.
 class SkColorFilterWrapper : public DarkModeColorFilter {
  public:
@@ -36,8 +41,8 @@
         new SkColorFilterWrapper(SkHighContrastFilter::Make(config)));
   }
 
-  SkColor InvertColor(SkColor color) const override {
-    return filter_->filterColor(color);
+  SkColor4f InvertColor(const SkColor4f& color) const override {
+    return filter_->filterColor4f(color, nullptr, nullptr);
   }
 
   sk_sp<SkColorFilter> ToSkColorFilter() const override { return filter_; }
@@ -60,34 +65,30 @@
     filter_ = SkHighContrastFilter::Make(config);
   }
 
-  SkColor InvertColor(SkColor color) const override {
-    SkV3 rgb = {SkColorGetR(color) / 255.0f, SkColorGetG(color) / 255.0f,
-                SkColorGetB(color) / 255.0f};
+  SkColor4f InvertColor(const SkColor4f& color) const override {
+    SkV3 rgb = {color.fR, color.fG, color.fB};
     SkV3 lab = transformer_.SRGBToLAB(rgb);
     lab.x = std::min(110.0f - lab.x, 100.0f);
     rgb = transformer_.LABToSRGB(lab);
 
-    SkColor inverted_color = SkColorSetARGB(
-        SkColorGetA(color), static_cast<unsigned int>(rgb.x * 255 + 0.5),
-        static_cast<unsigned int>(rgb.y * 255 + 0.5),
-        static_cast<unsigned int>(rgb.z * 255 + 0.5));
+    SkColor4f inverted_color{rgb.x, rgb.y, rgb.z, color.fA};
     return AdjustGray(inverted_color);
   }
 
-  SkColor AdjustColorForHigherConstrast(
-      SkColor adjusted_color,
-      SkColor background,
+  SkColor4f AdjustColorForHigherConstrast(
+      const SkColor4f& adjusted_color,
+      const SkColor4f& background,
       float reference_contrast_ratio) override {
     if (color_utils::GetContrastRatio(adjusted_color, background) >=
         reference_contrast_ratio)
       return adjusted_color;
 
-    SkColor best_color = adjusted_color;
+    SkColor4f best_color = adjusted_color;
     constexpr int MaxLightness = 100;
     int min_lightness = GetLabSkV3Data(adjusted_color).x;
     for (int low = min_lightness, high = MaxLightness + 1; low < high;) {
       const int lightness = (low + high) / 2;
-      const SkColor color = AdjustColorByLightness(adjusted_color, lightness);
+      const SkColor4f color = AdjustColorByLightness(adjusted_color, lightness);
       const float contrast = color_utils::GetContrastRatio(color, background);
       if (contrast > reference_contrast_ratio) {
         high = lightness;
@@ -108,50 +109,48 @@
   //
   // TODO(gilmanmh): Consider adding a more general way to adjust colors after
   // applying the main filter.
-  SkColor AdjustGray(SkColor color) const {
-    static const uint8_t kBrightnessThreshold = 32;
-    static const uint8_t kAdjustedBrightness = 18;
+  SkColor4f AdjustGray(const SkColor4f& color) const {
+    static const float kBrightnessThreshold = 32.0f / 255.0f;
+    static const float kAdjustedBrightness = 18.0f / 255.0f;
 
-    uint8_t r = SkColorGetR(color);
-    uint8_t g = SkColorGetG(color);
-    uint8_t b = SkColorGetB(color);
+    const float r = color.fR;
+    const float g = color.fG;
+    const float b = color.fB;
 
-    if (r == b && r == g && r < kBrightnessThreshold &&
-        r > kAdjustedBrightness) {
-      return SkColorSetARGB(SkColorGetA(color), kAdjustedBrightness,
-                            kAdjustedBrightness, kAdjustedBrightness);
+    if (IsWithinEpsilon(r, g) && IsWithinEpsilon(r, b) &&
+        r < kBrightnessThreshold && r > kAdjustedBrightness) {
+      return SkColor4f{kAdjustedBrightness, kAdjustedBrightness,
+                       kAdjustedBrightness, color.fA};
     }
 
     return color;
   }
 
-  SkColor AdjustColorByLightness(SkColor reference_color, int lightness) {
-    SkColor new_color = AdjustLightness(reference_color, lightness);
+  SkColor4f AdjustColorByLightness(const SkColor4f& reference_color,
+                                   int lightness) {
+    // Todo(1399566): SkColorToHSV and SkHSVToColor need SkColor4f versions.
+    SkColor4f new_color = AdjustLightness(reference_color, lightness);
     SkScalar hsv[3];
-    SkColorToHSV(reference_color, hsv);
+    SkColorToHSV(reference_color.toSkColor(), hsv);
     const float hue = hsv[0];
-    SkColorToHSV(new_color, hsv);
+    SkColorToHSV(new_color.toSkColor(), hsv);
     if (hsv[0] != hue)
       hsv[0] = hue;
 
-    return SkHSVToColor(SkColorGetA(reference_color), hsv);
+    return SkColor4f::FromColor(SkHSVToColor(reference_color.fA * 255, hsv));
   }
 
-  SkColor AdjustLightness(SkColor color, int lightness) {
+  SkColor4f AdjustLightness(const SkColor4f& color, int lightness) {
     SkV3 lab = GetLabSkV3Data(color);
     if (lab.x != lightness)
       lab.x = lightness;
     SkV3 rgb = transformer_.LABToSRGB(lab);
 
-    return SkColorSetARGB(SkColorGetA(color),
-                          static_cast<unsigned int>(rgb.x * 255 + 0.5),
-                          static_cast<unsigned int>(rgb.y * 255 + 0.5),
-                          static_cast<unsigned int>(rgb.z * 255 + 0.5));
+    return {rgb.x, rgb.y, rgb.z, color.fA};
   }
 
-  SkV3 GetLabSkV3Data(SkColor color) {
-    SkV3 rgb = {SkColorGetR(color) / 255.0f, SkColorGetG(color) / 255.0f,
-                SkColorGetB(color) / 255.0f};
+  SkV3 GetLabSkV3Data(const SkColor4f& color) {
+    SkV3 rgb = {color.fR, color.fG, color.fB};
     return transformer_.SRGBToLAB(rgb);
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.h b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.h
index 70d54dbc..5bc8d88 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.h
@@ -23,11 +23,11 @@
       const DarkModeSettings& settings);
 
   virtual ~DarkModeColorFilter();
-  virtual SkColor InvertColor(SkColor color) const = 0;
+  virtual SkColor4f InvertColor(const SkColor4f& color) const = 0;
   virtual sk_sp<SkColorFilter> ToSkColorFilter() const = 0;
-  virtual SkColor AdjustColorForHigherConstrast(
-      SkColor adjusted_color,
-      const SkColor background,
+  virtual SkColor4f AdjustColorForHigherConstrast(
+      const SkColor4f& adjusted_color,
+      const SkColor4f& background,
       float reference_contrast_ratio) {
     return adjusted_color;
   }
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
index c58b450c..806925a0 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
@@ -88,14 +88,14 @@
   DarkModeInvertedColorCache() : cache_(kMaxCacheSize) {}
   ~DarkModeInvertedColorCache() = default;
 
-  SkColor GetInvertedColor(DarkModeColorFilter* filter, SkColor color) {
-    SkColor key(color);
+  SkColor4f GetInvertedColor(DarkModeColorFilter* filter, SkColor4f color) {
+    SkColor key = color.toSkColor();
     auto it = cache_.Get(key);
     if (it != cache_.end())
       return it->second;
 
-    SkColor inverted_color = filter->InvertColor(color);
-    cache_.Put(key, static_cast<SkColor>(inverted_color));
+    SkColor4f inverted_color = filter->InvertColor(color);
+    cache_.Put(key, inverted_color);
     return inverted_color;
   }
 
@@ -104,7 +104,7 @@
   size_t size() { return cache_.size(); }
 
  private:
-  base::HashingLRUCache<SkColor, SkColor> cache_;
+  base::HashingLRUCache<SkColor, SkColor4f> cache_;
 };
 
 DarkModeFilter::DarkModeFilter(const DarkModeSettings& settings)
@@ -140,31 +140,35 @@
 
 // Heuristic to maintain contrast for borders and selections (see:
 // crbug.com/1263545,crbug.com/1298969)
-SkColor DarkModeFilter::AdjustDarkenColor(SkColor color,
-                                          DarkModeFilter::ElementRole role,
-                                          SkColor contrast_background) {
-  if (contrast_background == 0)
-    contrast_background = SK_ColorDark;
+SkColor4f DarkModeFilter::AdjustDarkenColor(
+    const SkColor4f& color,
+    DarkModeFilter::ElementRole role,
+    const SkColor4f& contrast_background) {
+  const SkColor4f& background = [&contrast_background]() {
+    if (contrast_background == SkColors::kTransparent)
+      return SkColor4f::FromColor(SK_ColorDark);
+    else
+      return contrast_background;
+  }();
 
   switch (role) {
     case ElementRole::kBorder: {
-      if (color == SkColorSetARGB(SkColorGetA(color), 0, 0, 0))
+      if (color == SkColor4f{0.0f, 0.0f, 0.0f, color.fA})
         return color;
 
-      if (color_utils::GetContrastRatio(color, contrast_background) <
+      if (color_utils::GetContrastRatio(color, background) <
           color_utils::kMinimumReadableContrastRatio)
         return color;
 
-      return AdjustDarkenColor(Color::FromSkColor(color).Dark().Rgb(), role,
-                               contrast_background);
+      return AdjustDarkenColor(Color::FromSkColor4f(color).Dark().toSkColor4f(),
+                               role, background);
     }
     case ElementRole::kSelection: {
       if (!immutable_.color_filter)
         return color;
 
       return immutable_.color_filter->AdjustColorForHigherConstrast(
-          color, contrast_background,
-          color_utils::kMinimumVisibleContrastRatio);
+          color, background, color_utils::kMinimumVisibleContrastRatio);
     }
     default:
       return color;
@@ -172,15 +176,17 @@
   NOTREACHED();
 }
 
-SkColor DarkModeFilter::InvertColorIfNeeded(SkColor color,
-                                            ElementRole role,
-                                            SkColor contrast_background) {
+SkColor4f DarkModeFilter::InvertColorIfNeeded(
+    const SkColor4f& color,
+    ElementRole role,
+    const SkColor4f& contrast_background) {
   return AdjustDarkenColor(
       InvertColorIfNeeded(color, role), role,
       InvertColorIfNeeded(contrast_background, ElementRole::kBackground));
 }
 
-SkColor DarkModeFilter::InvertColorIfNeeded(SkColor color, ElementRole role) {
+SkColor4f DarkModeFilter::InvertColorIfNeeded(const SkColor4f& color,
+                                              ElementRole role) {
   if (!immutable_.color_filter)
     return color;
 
@@ -257,12 +263,12 @@
 absl::optional<cc::PaintFlags> DarkModeFilter::ApplyToFlagsIfNeeded(
     const cc::PaintFlags& flags,
     ElementRole role,
-    SkColor contrast_background) {
+    SkColor4f contrast_background) {
   if (!immutable_.color_filter || flags.HasShader())
     return absl::nullopt;
 
   cc::PaintFlags dark_mode_flags = flags;
-  SkColor flags_color = flags.getColor();
+  SkColor4f flags_color = flags.getColor4f();
   if (ShouldApplyToColor(flags_color, role)) {
     flags_color = inverted_color_cache_->GetInvertedColor(
         immutable_.color_filter.get(), flags_color);
@@ -274,20 +280,21 @@
   return absl::make_optional<cc::PaintFlags>(std::move(dark_mode_flags));
 }
 
-bool DarkModeFilter::ShouldApplyToColor(SkColor color, ElementRole role) {
+bool DarkModeFilter::ShouldApplyToColor(const SkColor4f& color,
+                                        ElementRole role) {
   switch (role) {
     case ElementRole::kBorder:
     case ElementRole::kSVG:
     case ElementRole::kForeground:
     case ElementRole::kListSymbol:
       DCHECK(immutable_.foreground_classifier);
-      return immutable_.foreground_classifier->ShouldInvertColor(color) ==
-             DarkModeResult::kApplyFilter;
+      return immutable_.foreground_classifier->ShouldInvertColor(
+                 color.toSkColor()) == DarkModeResult::kApplyFilter;
     case ElementRole::kBackground:
     case ElementRole::kSelection:
       DCHECK(immutable_.background_classifier);
-      return immutable_.background_classifier->ShouldInvertColor(color) ==
-             DarkModeResult::kApplyFilter;
+      return immutable_.background_classifier->ShouldInvertColor(
+                 color.toSkColor()) == DarkModeResult::kApplyFilter;
     default:
       return false;
   }
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
index 2fef40f..5e633b0 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
@@ -41,15 +41,16 @@
   };
   enum class ImageType { kNone, kIcon, kSeparator, kPhoto };
 
-  SkColor InvertColorIfNeeded(SkColor color, ElementRole element_role);
-  SkColor InvertColorIfNeeded(SkColor color,
-                              ElementRole role,
-                              SkColor contrast_background);
+  SkColor4f InvertColorIfNeeded(const SkColor4f& color,
+                                ElementRole element_role);
+  SkColor4f InvertColorIfNeeded(const SkColor4f& color,
+                                ElementRole role,
+                                const SkColor4f& contrast_background);
 
   absl::optional<cc::PaintFlags> ApplyToFlagsIfNeeded(
       const cc::PaintFlags& flags,
       ElementRole role,
-      SkColor contrast_background);
+      SkColor4f contrast_background);
 
   size_t GetInvertedColorCacheSizeForTesting();
 
@@ -80,11 +81,11 @@
     sk_sp<SkColorFilter> image_filter;
   };
 
-  SkColor AdjustDarkenColor(SkColor color,
-                            DarkModeFilter::ElementRole role,
-                            SkColor contrast_background);
+  SkColor4f AdjustDarkenColor(const SkColor4f& color,
+                              DarkModeFilter::ElementRole role,
+                              const SkColor4f& contrast_background);
 
-  bool ShouldApplyToColor(SkColor color, ElementRole role);
+  bool ShouldApplyToColor(const SkColor4f& color, ElementRole role);
 
   // Returns dark mode color filter for images. This function should be called
   // only if image policy is set to DarkModeImagePolicy::kFilterAll or image is
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
index b6bdb222..934f76f 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter_test.cc
@@ -18,67 +18,94 @@
   settings.mode = DarkModeInversionAlgorithm::kSimpleInvertForTesting;
   DarkModeFilter filter(settings);
 
-  EXPECT_EQ(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kBlack,
             filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
-  EXPECT_EQ(SK_ColorWHITE,
+                SkColors::kWhite, DarkModeFilter::ElementRole::kBackground));
+  EXPECT_EQ(SkColors::kWhite,
             filter.InvertColorIfNeeded(
-                SK_ColorBLACK, DarkModeFilter::ElementRole::kBackground));
+                SkColors::kBlack, DarkModeFilter::ElementRole::kBackground));
 
-  EXPECT_EQ(SK_ColorWHITE,
-            filter.InvertColorIfNeeded(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kWhite,
+            filter.InvertColorIfNeeded(SkColors::kBlack,
                                        DarkModeFilter::ElementRole::kSVG));
-  EXPECT_EQ(SK_ColorBLACK,
-            filter.InvertColorIfNeeded(SK_ColorWHITE,
+  EXPECT_EQ(SkColors::kBlack,
+            filter.InvertColorIfNeeded(SkColors::kWhite,
                                        DarkModeFilter::ElementRole::kSVG));
 
   cc::PaintFlags flags;
-  flags.setColor(SK_ColorWHITE);
+  flags.setColor(SkColors::kWhite);
   auto flags_or_nullopt = filter.ApplyToFlagsIfNeeded(
-      flags, DarkModeFilter::ElementRole::kBackground, 0);
+      flags, DarkModeFilter::ElementRole::kBackground, SkColors::kTransparent);
   ASSERT_NE(flags_or_nullopt, absl::nullopt);
-  EXPECT_EQ(SK_ColorBLACK, flags_or_nullopt.value().getColor());
+  EXPECT_EQ(SkColors::kBlack, flags_or_nullopt.value().getColor4f());
 }
 
 TEST(DarkModeFilterTest, ApplyDarkModeToColorsAndFlagsWithInvertLightnessLAB) {
+  constexpr float kPrecision = 0.00001f;
   DarkModeSettings settings;
   settings.mode = DarkModeInversionAlgorithm::kInvertLightnessLAB;
   DarkModeFilter filter(settings);
-  constexpr SkColor SK_ColorWhiteWithAlpha =
-      SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF);
-  constexpr SkColor SK_ColorBlackWithAlpha =
-      SkColorSetARGB(0x80, 0x00, 0x00, 0x00);
-  constexpr SkColor SK_ColorDark = SkColorSetARGB(0xFF, 0x12, 0x12, 0x12);
-  constexpr SkColor SK_ColorDarkWithAlpha =
-      SkColorSetARGB(0x80, 0x12, 0x12, 0x12);
+  const SkColor4f ColorWhiteWithAlpha =
+      SkColor4f::FromColor(SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF));
+  const SkColor4f ColorBlackWithAlpha =
+      SkColor4f::FromColor(SkColorSetARGB(0x80, 0x00, 0x00, 0x00));
+  const SkColor4f ColorDark =
+      SkColor4f::FromColor(SkColorSetARGB(0xFF, 0x12, 0x12, 0x12));
+  const SkColor4f ColorDarkWithAlpha =
+      SkColor4f::FromColor(SkColorSetARGB(0x80, 0x12, 0x12, 0x12));
 
-  EXPECT_EQ(SK_ColorDark,
-            filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
-  EXPECT_EQ(SK_ColorWHITE,
-            filter.InvertColorIfNeeded(
-                SK_ColorBLACK, DarkModeFilter::ElementRole::kBackground));
-  EXPECT_EQ(
-      SK_ColorDarkWithAlpha,
-      filter.InvertColorIfNeeded(SK_ColorWhiteWithAlpha,
-                                 DarkModeFilter::ElementRole::kBackground));
+  SkColor4f result = filter.InvertColorIfNeeded(
+      SkColors::kWhite, DarkModeFilter::ElementRole::kBackground);
+  EXPECT_NEAR(ColorDark.fR, result.fR, kPrecision);
+  EXPECT_NEAR(ColorDark.fG, result.fG, kPrecision);
+  EXPECT_NEAR(ColorDark.fB, result.fB, kPrecision);
+  EXPECT_NEAR(ColorDark.fA, result.fA, kPrecision);
 
-  EXPECT_EQ(SK_ColorWHITE,
-            filter.InvertColorIfNeeded(SK_ColorBLACK,
-                                       DarkModeFilter::ElementRole::kSVG));
-  EXPECT_EQ(SK_ColorDark,
-            filter.InvertColorIfNeeded(SK_ColorWHITE,
-                                       DarkModeFilter::ElementRole::kSVG));
-  EXPECT_EQ(SK_ColorWhiteWithAlpha,
-            filter.InvertColorIfNeeded(SK_ColorBlackWithAlpha,
-                                       DarkModeFilter::ElementRole::kSVG));
+  result = filter.InvertColorIfNeeded(SkColors::kBlack,
+                                      DarkModeFilter::ElementRole::kBackground);
+  EXPECT_NEAR(SkColors::kWhite.fR, result.fR, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fG, result.fG, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fB, result.fB, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fA, result.fA, kPrecision);
+
+  result = filter.InvertColorIfNeeded(ColorWhiteWithAlpha,
+                                      DarkModeFilter::ElementRole::kBackground);
+  EXPECT_NEAR(ColorDarkWithAlpha.fR, result.fR, kPrecision);
+  EXPECT_NEAR(ColorDarkWithAlpha.fG, result.fG, kPrecision);
+  EXPECT_NEAR(ColorDarkWithAlpha.fB, result.fB, kPrecision);
+  EXPECT_NEAR(ColorDarkWithAlpha.fA, result.fA, kPrecision);
+
+  result = filter.InvertColorIfNeeded(SkColors::kBlack,
+                                      DarkModeFilter::ElementRole::kSVG);
+  EXPECT_NEAR(SkColors::kWhite.fR, result.fR, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fG, result.fG, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fB, result.fB, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fA, result.fA, kPrecision);
+
+  result = filter.InvertColorIfNeeded(SkColors::kWhite,
+                                      DarkModeFilter::ElementRole::kSVG);
+  EXPECT_NEAR(ColorDark.fR, result.fR, kPrecision);
+  EXPECT_NEAR(ColorDark.fG, result.fG, kPrecision);
+  EXPECT_NEAR(ColorDark.fB, result.fB, kPrecision);
+  EXPECT_NEAR(ColorDark.fA, result.fA, kPrecision);
+
+  result = filter.InvertColorIfNeeded(ColorBlackWithAlpha,
+                                      DarkModeFilter::ElementRole::kSVG);
+  EXPECT_NEAR(ColorWhiteWithAlpha.fR, result.fR, kPrecision);
+  EXPECT_NEAR(ColorWhiteWithAlpha.fG, result.fG, kPrecision);
+  EXPECT_NEAR(ColorWhiteWithAlpha.fB, result.fB, kPrecision);
+  EXPECT_NEAR(ColorWhiteWithAlpha.fA, result.fA, kPrecision);
 
   cc::PaintFlags flags;
-  flags.setColor(SK_ColorBLACK);
+  flags.setColor(SkColors::kBlack);
   auto flags_or_nullopt = filter.ApplyToFlagsIfNeeded(
-      flags, DarkModeFilter::ElementRole::kBackground, 0);
+      flags, DarkModeFilter::ElementRole::kBackground, SkColors::kTransparent);
   ASSERT_NE(flags_or_nullopt, absl::nullopt);
-  EXPECT_EQ(SK_ColorWHITE, flags_or_nullopt.value().getColor());
+  result = flags_or_nullopt.value().getColor4f();
+  EXPECT_NEAR(SkColors::kWhite.fR, result.fR, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fG, result.fG, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fB, result.fB, kPrecision);
+  EXPECT_NEAR(SkColors::kWhite.fA, result.fA, kPrecision);
 }
 
 TEST(DarkModeFilterTest, ApplyDarkModeToColorsAndFlagsWithContrast) {
@@ -87,24 +114,26 @@
   settings.background_brightness_threshold = 205;
   DarkModeFilter filter(settings);
 
-  constexpr SkColor SK_Target_For_White = SkColorSetRGB(0x12, 0x12, 0x12);
-  constexpr SkColor SK_Target_For_Black = SkColorSetRGB(0x57, 0x57, 0x57);
+  const SkColor4f Target_For_White =
+      SkColor4f::FromColor(SkColorSetRGB(0x12, 0x12, 0x12));
+  const SkColor4f Target_For_Black =
+      SkColor4f::FromColor(SkColorSetRGB(0x57, 0x57, 0x57));
 
-  EXPECT_EQ(
-      SK_Target_For_White,
-      filter.InvertColorIfNeeded(
-          SK_ColorWHITE, DarkModeFilter::ElementRole::kBorder, SK_ColorBLACK));
-  EXPECT_EQ(
-      SK_Target_For_Black,
-      filter.InvertColorIfNeeded(
-          SK_ColorBLACK, DarkModeFilter::ElementRole::kBorder, SK_ColorBLACK));
+  EXPECT_EQ(Target_For_White,
+            filter.InvertColorIfNeeded(SkColors::kWhite,
+                                       DarkModeFilter::ElementRole::kBorder,
+                                       SkColors::kBlack));
+  EXPECT_EQ(Target_For_Black,
+            filter.InvertColorIfNeeded(SkColors::kBlack,
+                                       DarkModeFilter::ElementRole::kBorder,
+                                       SkColors::kBlack));
 
   cc::PaintFlags flags;
-  flags.setColor(SK_ColorWHITE);
+  flags.setColor(SkColors::kWhite);
   auto flags_or_nullopt = filter.ApplyToFlagsIfNeeded(
-      flags, DarkModeFilter::ElementRole::kBorder, SK_ColorBLACK);
+      flags, DarkModeFilter::ElementRole::kBorder, SkColors::kBlack);
   ASSERT_NE(flags_or_nullopt, absl::nullopt);
-  EXPECT_EQ(SK_Target_For_White, flags_or_nullopt.value().getColor());
+  EXPECT_EQ(Target_For_White, flags_or_nullopt.value().getColor4f());
 }
 
 // crbug.com/1365680
@@ -115,27 +144,33 @@
   settings.background_brightness_threshold = 205;
   DarkModeFilter filter(settings);
 
-  constexpr SkColor SK_Darken_To_Black = SkColorSetRGB(0x09, 0xe6, 0x0c);
-  constexpr SkColor SK_High_Contrast = SkColorSetRGB(0x4c, 0xdc, 0x6d);
+  const SkColor4f Darken_To_Black =
+      SkColor4f::FromColor(SkColorSetRGB(0x09, 0xe6, 0x0c));
+  const SkColor4f High_Contrast =
+      SkColor4f::FromColor(SkColorSetRGB(0x4c, 0xdc, 0x6d));
 
-  constexpr SkColor SK_Darken_To_Black1 = SkColorSetRGB(0x02, 0xd7, 0x72);
-  constexpr SkColor SK_High_Contrast1 = SkColorSetRGB(0xcf, 0xea, 0x3b);
+  const SkColor4f Darken_To_Black1 =
+      SkColor4f::FromColor(SkColorSetRGB(0x02, 0xd7, 0x72));
+  const SkColor4f High_Contrast1 =
+      SkColor4f::FromColor(SkColorSetRGB(0xcf, 0xea, 0x3b));
 
-  constexpr SkColor SK_Darken_To_Black2 = SkColorSetRGB(0x09, 0xe6, 0x0c);
-  constexpr SkColor SK_High_Contrast2 = SkColorSetRGB(0x4c, 0xdc, 0x6d);
+  const SkColor4f Darken_To_Black2 =
+      SkColor4f::FromColor(SkColorSetRGB(0x09, 0xe6, 0x0c));
+  const SkColor4f High_Contrast2 =
+      SkColor4f::FromColor(SkColorSetRGB(0x4c, 0xdc, 0x6d));
 
-  EXPECT_EQ(SK_ColorBLACK,
-            filter.InvertColorIfNeeded(SK_Darken_To_Black,
+  EXPECT_EQ(SkColors::kBlack,
+            filter.InvertColorIfNeeded(Darken_To_Black,
                                        DarkModeFilter::ElementRole::kBorder,
-                                       SK_High_Contrast));
-  EXPECT_EQ(SK_ColorBLACK,
-            filter.InvertColorIfNeeded(SK_Darken_To_Black1,
+                                       High_Contrast));
+  EXPECT_EQ(SkColors::kBlack,
+            filter.InvertColorIfNeeded(Darken_To_Black1,
                                        DarkModeFilter::ElementRole::kBorder,
-                                       SK_High_Contrast1));
-  EXPECT_EQ(SK_ColorBLACK,
-            filter.InvertColorIfNeeded(SK_Darken_To_Black2,
+                                       High_Contrast1));
+  EXPECT_EQ(SkColors::kBlack,
+            filter.InvertColorIfNeeded(Darken_To_Black2,
                                        DarkModeFilter::ElementRole::kBorder,
-                                       SK_High_Contrast2));
+                                       High_Contrast2));
 }
 
 TEST(DarkModeFilterTest, InvertedColorCacheSize) {
@@ -143,14 +178,14 @@
   settings.mode = DarkModeInversionAlgorithm::kSimpleInvertForTesting;
   DarkModeFilter filter(settings);
   EXPECT_EQ(0u, filter.GetInvertedColorCacheSizeForTesting());
-  EXPECT_EQ(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kBlack,
             filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
+                SkColors::kWhite, DarkModeFilter::ElementRole::kBackground));
   EXPECT_EQ(1u, filter.GetInvertedColorCacheSizeForTesting());
   // Should get cached value.
-  EXPECT_EQ(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kBlack,
             filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
+                SkColors::kWhite, DarkModeFilter::ElementRole::kBackground));
   EXPECT_EQ(1u, filter.GetInvertedColorCacheSizeForTesting());
 }
 
@@ -160,22 +195,24 @@
   DarkModeFilter filter(settings);
 
   EXPECT_EQ(0u, filter.GetInvertedColorCacheSizeForTesting());
-  EXPECT_EQ(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kBlack,
             filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
+                SkColors::kWhite, DarkModeFilter::ElementRole::kBackground));
   EXPECT_EQ(1u, filter.GetInvertedColorCacheSizeForTesting());
-  EXPECT_EQ(SK_ColorTRANSPARENT,
-            filter.InvertColorIfNeeded(
-                SK_ColorTRANSPARENT, DarkModeFilter::ElementRole::kBackground));
+  EXPECT_EQ(
+      SkColors::kTransparent,
+      filter.InvertColorIfNeeded(SkColors::kTransparent,
+                                 DarkModeFilter::ElementRole::kBackground));
   EXPECT_EQ(2u, filter.GetInvertedColorCacheSizeForTesting());
 
   // Results returned from cache.
-  EXPECT_EQ(SK_ColorBLACK,
+  EXPECT_EQ(SkColors::kBlack,
             filter.InvertColorIfNeeded(
-                SK_ColorWHITE, DarkModeFilter::ElementRole::kBackground));
-  EXPECT_EQ(SK_ColorTRANSPARENT,
-            filter.InvertColorIfNeeded(
-                SK_ColorTRANSPARENT, DarkModeFilter::ElementRole::kBackground));
+                SkColors::kWhite, DarkModeFilter::ElementRole::kBackground));
+  EXPECT_EQ(
+      SkColors::kTransparent,
+      filter.InvertColorIfNeeded(SkColors::kTransparent,
+                                 DarkModeFilter::ElementRole::kBackground));
   EXPECT_EQ(2u, filter.GetInvertedColorCacheSizeForTesting());
 }
 
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 657ce74..2bc1646 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -1277,13 +1277,15 @@
     GrSurfaceOrigin origin = opengl_flip_y_extension_
                                  ? kTopLeft_GrSurfaceOrigin
                                  : kBottomLeft_GrSurfaceOrigin;
+    uint32_t usage = gpu::SHARED_IMAGE_USAGE_GLES2 |
+                     gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
+                     gpu::SHARED_IMAGE_USAGE_RASTER;
+    if (gpu::GetPlatformSpecificTextureTarget() == GL_TEXTURE_2D) {
+      usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
+    }
     premultiplied_alpha_false_mailbox_ = sii->CreateSharedImage(
         back_color_buffer_->format, size, back_color_buffer_->color_space,
-        origin, kUnpremul_SkAlphaType,
-        gpu::SHARED_IMAGE_USAGE_GLES2 |
-            gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
-            gpu::SHARED_IMAGE_USAGE_RASTER,
-        gpu::kNullSurfaceHandle);
+        origin, kUnpremul_SkAlphaType, usage, gpu::kNullSurfaceHandle);
     gpu::SyncToken sync_token = sii->GenUnverifiedSyncToken();
     gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
     premultiplied_alpha_false_texture_ =
diff --git a/third_party/blink/renderer/platform/graphics/gradient.cc b/third_party/blink/renderer/platform/graphics/gradient.cc
index 3206e6c..315e111 100644
--- a/third_party/blink/renderer/platform/graphics/gradient.cc
+++ b/third_party/blink/renderer/platform/graphics/gradient.cc
@@ -232,8 +232,10 @@
 
   if (is_dark_mode_enabled_) {
     for (auto& color : colors) {
-      color = EnsureDarkModeFilter().InvertColorIfNeeded(
-          SkColor(color), DarkModeFilter::ElementRole::kBackground);
+      color = EnsureDarkModeFilter()
+                  .InvertColorIfNeeded(SkColor4f::FromColor(color),
+                                       DarkModeFilter::ElementRole::kBackground)
+                  .toSkColor();
     }
   }
   // The matrix type is mutable and set lazily. Force it to be computed here to
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index 373187af..baf63c2d 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -95,11 +95,10 @@
                     const Color& color,
                     const AutoDarkMode& auto_dark_mode) {
   if (auto_dark_mode.enabled) {
-    // TODO(https://crbug.com/1351544): DarkModeFilter should operate on
-    // SkColor4f, not SkColor.
-    return Color::FromSkColor(context.GetDarkModeFilter()->InvertColorIfNeeded(
-        color.ToSkColorDeprecated(), auto_dark_mode.role,
-        auto_dark_mode.contrast_color));
+    return Color::FromSkColor4f(
+        context.GetDarkModeFilter()->InvertColorIfNeeded(
+            color.toSkColor4f(), auto_dark_mode.role,
+            SkColor4f::FromColor(auto_dark_mode.contrast_color)));
   }
   return color;
 }
@@ -120,7 +119,8 @@
                 const cc::PaintFlags& flags) {
     if (auto_dark_mode.enabled) {
       dark_mode_flags_ = context->GetDarkModeFilter()->ApplyToFlagsIfNeeded(
-          flags, auto_dark_mode.role, auto_dark_mode.contrast_color);
+          flags, auto_dark_mode.role,
+          SkColor4f::FromColor(auto_dark_mode.contrast_color));
       if (dark_mode_flags_) {
         flags_ = &dark_mode_flags_.value();
         return;
diff --git a/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc b/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
index 5760b6a5..4218bba 100644
--- a/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/cull_rect.cc
@@ -353,9 +353,6 @@
 bool CullRect::HasScrolledEnough(
     const gfx::Vector2dF& delta,
     const TransformPaintPropertyNode& scroll_translation) {
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return !delta.IsZero();
-
   if (!scroll_translation.ScrollNode() ||
       !scroll_translation.HasDirectCompositingReasons()) {
     return !delta.IsZero();
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
index 0a5f76f..fdb2c5e6 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
@@ -32,27 +32,6 @@
   rect.set_size(rect.size() + expansion);
 }
 
-// Walk up from the local transform to the ancestor. If the last transform
-// before hitting the ancestor is a fixed node, expand based on the min and
-// max scroll offsets.
-void ExpandFixedBoundsInScroller(const TransformPaintPropertyNode* local,
-                                 const TransformPaintPropertyNode* ancestor,
-                                 FloatClipRect& rect_to_map) {
-  DCHECK(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-  const TransformPaintPropertyNode* current = local->UnaliasedParent();
-  const TransformPaintPropertyNode* previous = local;
-  while (current != nullptr && current != ancestor) {
-    previous = current;
-    current = current->UnaliasedParent();
-  }
-
-  const auto* node = previous->ScrollTranslationForFixed();
-  if (!node)
-    return;
-
-  ExpandFixedVisualRectInScroller(*node, rect_to_map.Rect());
-}
-
 }  // namespace
 
 gfx::Transform GeometryMapper::SourceToDestinationProjection(
@@ -115,8 +94,7 @@
 
   if (source.Parent() && &destination == &source.Parent()->Unalias()) {
     extra_result.has_fixed = source.RequiresCompositingForFixedPosition();
-    if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-      extra_result.has_sticky = source.RequiresCompositingForStickyPosition();
+    extra_result.has_sticky = source.RequiresCompositingForStickyPosition();
     if (source.IsIdentityOr2dTranslation() && source.Origin().IsOrigin()) {
       // The result will be translate(origin)*matrix*translate(-origin) which
       // equals to matrix if the origin is zero or if the matrix is just
@@ -136,8 +114,7 @@
   const auto& destination_cache = destination.GetTransformCache();
 
   extra_result.has_fixed |= source_cache.has_fixed();
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    extra_result.has_sticky |= source_cache.has_sticky();
+  extra_result.has_sticky |= source_cache.has_sticky();
 
   // Case 1a (fast path of case 1b): check if source and destination are under
   // the same 2d translation root.
@@ -278,16 +255,6 @@
     rect_to_map = InfiniteLooseFloatClipRect();
   } else {
     rect_to_map.Map(projection);
-    if (for_compositing_overlap == ForCompositingOverlap::kYes &&
-        !RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled() &&
-        extra_result.has_fixed) {
-      ExpandFixedBoundsInScroller(&local_state.Transform(),
-                                  &ancestor_state.Transform(), rect_to_map);
-      // This early return skips the clipping below because the expansion for
-      // fixed-position is to avoid compositing update on viewport scroll, while
-      // the clips may depend on viewport scroll offset.
-      return !rect_to_map.Rect().IsEmpty();
-    }
   }
 
   FloatClipRect clip_rect =
@@ -481,42 +448,11 @@
   return clip;
 }
 
-bool GeometryMapper::MightOverlapForCompositingLegacy(
-    const gfx::RectF& rect1,
-    const PropertyTreeState& state1,
-    const gfx::RectF& rect2,
-    const PropertyTreeState& state2) {
-  DCHECK(!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled());
-
-  PropertyTreeState common_ancestor(
-      state1.Transform().LowestCommonAncestor(state2.Transform()).Unalias(),
-      state1.Clip().LowestCommonAncestor(state2.Clip()).Unalias(),
-      state1.Effect().LowestCommonAncestor(state2.Effect()).Unalias());
-  // Move the common clip up if some effect nodes have OutputClip escaping the
-  // common clip.
-  if (const auto* clip_a =
-          HighestOutputClipBetween(common_ancestor.Effect(), state1.Effect())) {
-    common_ancestor.SetClip(
-        clip_a->LowestCommonAncestor(common_ancestor.Clip()).Unalias());
-  }
-  if (const auto* clip_b =
-          HighestOutputClipBetween(common_ancestor.Effect(), state2.Effect())) {
-    common_ancestor.SetClip(
-        clip_b->LowestCommonAncestor(common_ancestor.Clip()).Unalias());
-  }
-  auto v1 = VisualRectForCompositingOverlap(rect1, state1, common_ancestor);
-  auto v2 = VisualRectForCompositingOverlap(rect2, state2, common_ancestor);
-  return v1.Intersects(v2);
-}
-
 bool GeometryMapper::MightOverlapForCompositing(
     const gfx::RectF& rect1,
     const PropertyTreeState& state1,
     const gfx::RectF& rect2,
     const PropertyTreeState& state2) {
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return MightOverlapForCompositingLegacy(rect1, state1, rect2, state2);
-
   if (&state1.Transform() == &state2.Transform())
     return MightOverlapForCompositingInternal(rect1, state1, rect2, state2);
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
index 4773215..5a0117c 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
@@ -208,10 +208,6 @@
       OverlayScrollbarClipBehavior,
       InclusiveIntersectOrNot);
 
-  static bool MightOverlapForCompositingLegacy(const gfx::RectF& rect1,
-                                               const PropertyTreeState& state1,
-                                               const gfx::RectF& rect2,
-                                               const PropertyTreeState& state2);
   static bool MightOverlapForCompositingInternal(
       const gfx::RectF& rect1,
       const PropertyTreeState& state1,
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
index 33632f35..7180b0a 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
@@ -156,10 +156,8 @@
   EXPECT_CLIP_RECT_EQ(expected_clip, cached_clip->clip_rect);
   EXPECT_EQ(expected_clip_has_transform_animation,
             cached_clip->has_transform_animation);
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    EXPECT_EQ(expected_clip_has_sticky_transform,
-              cached_clip->has_sticky_transform);
-  }
+  EXPECT_EQ(expected_clip_has_sticky_transform,
+            cached_clip->has_sticky_transform);
 }
 
 // See the data fields of GeometryMapperTest for variables that will be used in
@@ -651,10 +649,7 @@
   expected_visual_rect.Intersect(clip->LayoutClipRect());
   expected_visual_rect.Map(*expected_transform);
   // The clip has sticky transform, so it doesn't apply to the visual rect.
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    expected_visual_rect_expanded_for_compositing =
-        InfiniteLooseFloatClipRect();
-  }
+  expected_visual_rect_expanded_for_compositing = InfiniteLooseFloatClipRect();
   EXPECT_TRUE(expected_visual_rect.IsTight());
   expected_clip = clip->LayoutClipRect();
   expected_clip.Map(*expected_transform);
@@ -720,12 +715,10 @@
   EXPECT_TRUE(expected_visual_rect.IsTight());
   expected_clip = clip->LayoutClipRect();
   EXPECT_TRUE(expected_clip.IsTight());
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    // The visual rect is expanded first to infinity because of the sticky
-    // transform, then clipped by the clip.
-    expected_visual_rect_expanded_for_compositing = expected_clip;
-    expected_visual_rect_expanded_for_compositing->ClearIsTight();
-  }
+  // The visual rect is expanded first to infinity because of the sticky
+  // transform, then clipped by the clip.
+  expected_visual_rect_expanded_for_compositing = expected_clip;
+  expected_visual_rect_expanded_for_compositing->ClearIsTight();
   CheckMappings();
 }
 
@@ -809,54 +802,11 @@
   expected_visual_rect.Map(*expected_transform);
   expected_visual_rect.Intersect(expected_clip);
   EXPECT_TRUE(expected_visual_rect.IsTight());
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    // The visual rect is expanded to infinity because of the sticky transform,
-    // then clipped by clip1. clip2 doesn't apply because it's below the sticky
-    // transform.
-    expected_visual_rect_expanded_for_compositing = clip1->LayoutClipRect();
-    expected_visual_rect_expanded_for_compositing->ClearIsTight();
-  }
-  CheckMappings();
-}
-
-TEST_P(GeometryMapperTest, ExpandVisualRectForFixed) {
-  // With ScrollUpdateOptimizations, we don't expand visual rect for fixed in
-  // LocalToAncestorVisualRectInternal(), but check overlap before it.
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-
-  auto above_viewport = CreateTransform(t0(), gfx::Transform());
-  auto viewport = CreateTransform(*above_viewport, gfx::Transform());
-  auto scroll_state = CreateCompositedScrollTranslationState(
-      PropertyTreeState(*viewport, c0(), e0()), -100, -200,
-      gfx::Rect(0, 0, 800, 600), gfx::Size(2400, 1800));
-
-  auto fixed_transform = CreateFixedPositionTranslation(
-      *viewport, 200, 200, scroll_state.Transform());
-  auto child_of_fixed = Create2DTranslation(*fixed_transform, 50, 50);
-
-  local_state.SetTransform(*child_of_fixed);
-  ancestor_state.SetTransform(*viewport);
-
-  const gfx::SizeF child_of_fixed_size(100, 100);
-  input_rect = gfx::RectF(child_of_fixed_size);
-
-  const gfx::Vector2dF descendant_offset(250, 250);
-  expected_translation_2d = descendant_offset;
-  expected_transformed_rect = gfx::RectF(
-      gfx::PointAtOffsetFromOrigin(descendant_offset), child_of_fixed_size);
-  expected_visual_rect = FloatClipRect(expected_transformed_rect);
-  expected_visual_rect_expanded_for_compositing =
-      FloatClipRect(gfx::RectF(150, 50, 1700, 1300));
-
-  CheckMappings();
-
-  // If we're not mapping to the viewport, the fixed rect should not be
-  // expanded.
-  ancestor_state.SetTransform(*above_viewport);
-  expected_transform =
-      MakeTranslationMatrix(descendant_offset.x(), descendant_offset.y());
-  expected_visual_rect_expanded_for_compositing = expected_visual_rect;
+  // The visual rect is expanded to infinity because of the sticky transform,
+  // then clipped by clip1. clip2 doesn't apply because it's below the sticky
+  // transform.
+  expected_visual_rect_expanded_for_compositing = clip1->LayoutClipRect();
+  expected_visual_rect_expanded_for_compositing->ClearIsTight();
   CheckMappings();
 }
 
@@ -1165,18 +1115,17 @@
                  scroll_state1.GetPropertyTreeState());
   }
 
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled()) {
-    {
-      SCOPED_TRACE("fixed_state and scroll_state1");
-      auto scroll_state2 = CreateScrollTranslationState(
-          scroll_state1.GetPropertyTreeState(), -2345, -678,
-          gfx::Rect(20, 10, 200, 100), gfx::Size(3000, 2000));
-      // The result is false because the container rect of scroll_state2 doesn't
-      // intersect with the expanded fixed-position rect in scroll_state1.
-      EXPECT_FALSE(MightOverlapForCompositing(
-          gfx::RectF(0, 0, 100, 100), fixed_state, gfx::RectF(1, 2, 3, 4),
-          scroll_state2.GetPropertyTreeState()));
-    }
+  {
+    SCOPED_TRACE("fixed_state and scroll_state1");
+    auto scroll_state2 = CreateScrollTranslationState(
+        scroll_state1.GetPropertyTreeState(), -2345, -678,
+        gfx::Rect(20, 10, 200, 100), gfx::Size(3000, 2000));
+    // The result is false because the container rect of scroll_state2 doesn't
+    // intersect with the expanded fixed-position rect in scroll_state1.
+    EXPECT_FALSE(MightOverlapForCompositing(
+        gfx::RectF(0, 0, 100, 100), fixed_state, gfx::RectF(1, 2, 3, 4),
+        scroll_state2.GetPropertyTreeState()));
+  }
     {
       SCOPED_TRACE("fixed_state and scroll_state1");
       auto scroll_state3 = CreateScrollTranslationState(
@@ -1185,15 +1134,10 @@
       EXPECT_TRUE(MightOverlapForCompositing(
           gfx::RectF(0, 0, 100, 100), fixed_state, gfx::RectF(1, 2, 3, 4),
           scroll_state3.GetPropertyTreeState()));
-    }
   }
 }
 
 TEST_P(GeometryMapperTest, MightOverlapScroll) {
-  // This test applies only if ScrollUpdateOptimizationsEnabled.
-  if (!RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled())
-    return;
-
   auto viewport = CreateTransform(t0(), gfx::Transform());
   auto scroll_state1 = CreateScrollTranslationState(
       PropertyTreeState(*viewport, c0(), e0()), -1234, -567,
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
index a71d63c..2e5c0474 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
@@ -508,8 +508,6 @@
 }
 
 TEST_F(PaintPropertyNodeTest, StickyTranslationChange) {
-  ScopedScrollUpdateOptimizationsForTest scroll_optimizations(true);
-
   ResetAllChanged();
   ExpectUnchangedState();
   TransformPaintPropertyNode::State state;
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
index 81d5d86..ff7fe6e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
@@ -44,8 +44,7 @@
                : PaintPropertyChangeType::kChangedOnlyCompositedValues;
   }
 
-  if (RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled() &&
-      direct_compositing_reasons & CompositingReason::kStickyPosition) {
+  if (direct_compositing_reasons & CompositingReason::kStickyPosition) {
     // The compositor handles sticky offset changes automatically.
     DCHECK(transform_and_origin.matrix.Preserves2dAxisAlignment());
     DCHECK(other.matrix.Preserves2dAxisAlignment());
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 22ae2029..e9bb07d 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1186,6 +1186,13 @@
       status: "stable",
     },
     {
+      // This only exists so we can use RuntimeEnabled in the IDL file
+      // when either implied_by flag is enabled.
+      name: "FedCmIdentityProviderInterface",
+      public: true,
+      implied_by: ["FedCmIdpSigninStatus", "FedCmUserInfo"],
+    },
+    {
       name: "FedCmIdpSigninStatus",
       depends_on: ["FedCm"],
       public: true,
@@ -1209,6 +1216,11 @@
       public: true,
     },
     {
+      name: "FedCmUserInfo",
+      depends_on: ["FedCm"],
+      public: true,
+    },
+    {
       name: "FencedFrames",
       // This helps enable and expose the <fencedframe> element, but note that
       // blink::features::kFencedFrames must be enabled as well, similar to
@@ -2421,12 +2433,6 @@
       status: "stable",
     },
     {
-      // Whether to enable scroll update optimizations. See crbug.com/1346789.
-      name: "ScrollUpdateOptimizations",
-      base_feature: "ScrollUpdateOptimizations",
-      status: "stable",
-    },
-    {
       name: "SecureContextFixForSharedWorkers",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/testing/paint_test_configurations.h b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
index 01d408ea..13b1b7b 100644
--- a/third_party/blink/renderer/platform/testing/paint_test_configurations.h
+++ b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
@@ -18,19 +18,15 @@
 enum {
   kUnderInvalidationChecking = 1 << 0,
   kScrollUnification = 1 << 1,
-  kScrollUpdateOptimizations = 1 << 2,
 };
 
 class PaintTestConfigurations
     : public testing::WithParamInterface<unsigned>,
-      private ScopedPaintUnderInvalidationCheckingForTest,
-      private ScopedScrollUpdateOptimizationsForTest {
+      private ScopedPaintUnderInvalidationCheckingForTest {
  public:
   PaintTestConfigurations()
-      : ScopedPaintUnderInvalidationCheckingForTest(GetParam() &
-                                                    kUnderInvalidationChecking),
-        ScopedScrollUpdateOptimizationsForTest(GetParam() &
-                                               kScrollUpdateOptimizations) {
+      : ScopedPaintUnderInvalidationCheckingForTest(
+            GetParam() & kUnderInvalidationChecking) {
     if (GetParam() & kScrollUnification)
       feature_list_.InitAndEnableFeature(::features::kScrollUnification);
     else
@@ -54,9 +50,8 @@
 // For now this has only one configuration, but can be extended in the future
 // to include more configurations.
 #define INSTANTIATE_PAINT_TEST_SUITE_P(test_class) \
-  INSTANTIATE_TEST_SUITE_P(                        \
-      All, test_class,                             \
-      ::testing::Values(0, kScrollUnification, kScrollUpdateOptimizations))
+  INSTANTIATE_TEST_SUITE_P(All, test_class,        \
+                           ::testing::Values(0, kScrollUnification))
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
index 76b9bf7..284cc1a6 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_settings.cc
@@ -558,9 +558,6 @@
   settings.enable_backface_visibility_interop =
       RuntimeEnabledFeatures::BackfaceVisibilityInteropEnabled();
 
-  settings.enable_scroll_update_optimizations =
-      RuntimeEnabledFeatures::ScrollUpdateOptimizationsEnabled();
-
   settings.disable_frame_rate_limit =
       cmd.HasSwitch(::switches::kDisableFrameRateLimit);
 
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
index c3c2678..8770eeb 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
@@ -205,6 +205,12 @@
   delegate_->OnPauseRenderingChanged(paused);
 }
 
+void LayerTreeView::OnCommitRequested() {
+  if (!delegate_)
+    return;
+  delegate_->OnCommitRequested();
+}
+
 void LayerTreeView::OnDeferCommitsChanged(
     bool status,
     cc::PaintHoldingReason reason,
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
index 5bc164a..92a9c345 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
@@ -78,6 +78,7 @@
       cc::PaintHoldingReason reason,
       absl::optional<cc::PaintHoldingCommitTrigger> trigger) override;
   void OnPauseRenderingChanged(bool) override;
+  void OnCommitRequested() override;
   void BeginMainFrameNotExpectedSoon() override;
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
index e881f38..e5021ae 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
@@ -46,6 +46,7 @@
       cc::PaintHoldingReason reason,
       absl::optional<cc::PaintHoldingCommitTrigger> trigger) = 0;
   virtual void OnPauseRenderingChanged(bool) = 0;
+  virtual void OnCommitRequested() = 0;
 
   // Notifies that the layer tree host has completed a call to
   // RequestMainFrameUpdate in response to a BeginMainFrame.
diff --git a/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h b/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
index e76e8be8..dba3a160 100644
--- a/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
+++ b/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
@@ -33,6 +33,7 @@
       cc::PaintHoldingReason reason,
       absl::optional<cc::PaintHoldingCommitTrigger> trigger) override {}
   void OnPauseRenderingChanged(bool) override {}
+  void OnCommitRequested() override {}
   void DidBeginMainFrame() override {}
   void DidCommitAndDrawCompositorFrame() override {}
   void WillCommitCompositorFrame() override {}
diff --git a/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc b/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
index 4f51d90f..99412c1 100644
--- a/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
@@ -18,6 +18,7 @@
 
 class StubWidgetBaseClient : public WidgetBaseClient {
  public:
+  void OnCommitRequested() override {}
   void BeginMainFrame(base::TimeTicks) override {}
   void UpdateLifecycle(WebLifecycleUpdate, DocumentUpdateReason) override {}
   std::unique_ptr<cc::LayerTreeFrameSink> AllocateNewLayerTreeFrameSink()
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 81b95185..901e7b1 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -555,6 +555,10 @@
   widget_input_handler_manager_->OnPauseRenderingChanged(paused);
 }
 
+void WidgetBase::OnCommitRequested() {
+  client_->OnCommitRequested();
+}
+
 void WidgetBase::DidBeginMainFrame() {
   if (base::FeatureList::IsEnabled(features::kRunTextInputUpdatePostLifecycle))
     UpdateTextInputState();
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index b73ee82..142f19fb 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -158,6 +158,7 @@
       cc::PaintHoldingReason reason,
       absl::optional<cc::PaintHoldingCommitTrigger> trigger) override;
   void OnPauseRenderingChanged(bool) override;
+  void OnCommitRequested() override;
   void DidBeginMainFrame() override;
   void RequestNewLayerTreeFrameSink(
       LayerTreeFrameSinkCallback callback) override;
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h
index 34569f6..1756d6af 100644
--- a/third_party/blink/renderer/platform/widget/widget_base_client.h
+++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -38,6 +38,9 @@
 // will need to implement.
 class WidgetBaseClient {
  public:
+  // Called when a compositing update is first requested.
+  virtual void OnCommitRequested() {}
+
   // Called to record the time taken to dispatch rAF aligned input.
   virtual void RecordDispatchRafAlignedInputTime(
       base::TimeTicks raf_aligned_input_start_time) {}
diff --git a/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations.py b/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations.py
index 8338db7..4ad6ca5 100644
--- a/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations.py
+++ b/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations.py
@@ -29,6 +29,7 @@
 import contextlib
 import json
 import logging
+import posixpath
 import optparse
 import re
 import traceback
@@ -325,6 +326,13 @@
     virtual_suites = port.virtual_test_suites()
     virtual_suites.sort(key=lambda s: s.full_prefix)
 
+    wpt_tests = set()
+    for wpt_dir in port.WPT_DIRS:
+        with contextlib.suppress(FileNotFoundError):
+            wpt_tests.update(
+                posixpath.join(wpt_dir, url)
+                for url in port.wpt_manifest(wpt_dir).all_urls())
+
     failures = []
     for suite in virtual_suites:
         suite_comps = suite.full_prefix.split(port.TEST_PATH_SEPARATOR)
@@ -352,7 +360,7 @@
                 continue
             base_comps = base.split(port.TEST_PATH_SEPARATOR)
             absolute_base = port.abspath_for_test(base)
-            if fs.isfile(absolute_base):
+            if fs.isfile(absolute_base) or base in wpt_tests:
                 del base_comps[-1]
             elif not fs.isdir(absolute_base):
                 failure = 'Base "{}" in virtual suite "{}" must refer to a real file or directory'.format(
diff --git a/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations_unittest.py b/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations_unittest.py
index f07eeca..468a5c5 100644
--- a/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/lint_test_expectations_unittest.py
@@ -26,9 +26,11 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+import json
 import optparse
 import textwrap
 import unittest
+from unittest.mock import patch
 
 from blinkpy.common import exit_codes
 from blinkpy.common.host_mock import MockHost
@@ -492,6 +494,42 @@
             self.host, self.options)
         self.assertFalse(res)
 
+    def test_check_virtual_test_suites_any_js(self):
+        suites = [
+            VirtualTestSuite(prefix='wpt-any-js',
+                             platforms=['Linux', 'Mac', 'Win'],
+                             bases=[
+                                 'external/wpt/test.any.html',
+                                 'external/wpt/test.any.worker.html'
+                             ],
+                             args=['--arg']),
+        ]
+        fs = self.host.filesystem
+        fs.write_text_file(
+            fs.join(self.port.web_tests_dir(), 'virtual', 'wpt-any-js',
+                    'README.md'), '')
+        manifest = {
+            'items': {
+                'testharness': {
+                    'test.any.js': [
+                        'df2f8b048c370d3ab009946d73d7de6f8a412471',
+                        ['test.any.html', {}],
+                        ['test.any.worker.html', {}],
+                    ],
+                },
+            },
+        }
+        manifest_path = fs.join(self.port.web_tests_dir(), 'external', 'wpt',
+                                'MANIFEST.json')
+        with fs.open_text_file_for_writing(manifest_path) as manifest_file:
+            json.dump(manifest, manifest_file)
+        with patch.object(self.port,
+                          'virtual_test_suites',
+                          return_value=suites):
+            self.assertEqual(
+                lint_test_expectations.check_virtual_test_suites(
+                    self.host, self.options), [])
+
     def test_check_virtual_test_suites_redundant(self):
         self.port.virtual_test_suites = lambda: [
             VirtualTestSuite(prefix='foo', platforms=['Linux', 'Mac', 'Win'], bases=['test/sub', 'test'], args=['--foo']),
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7954ee46..7af45cd 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6983,14 +6983,9 @@
 crbug.com/1395450 http/tests/devtools/layers/layer-tree-model.js [ Failure Pass ]
 crbug.com/1395744 [ Linux ] external/wpt/css/filter-effects/effect-reference-on-transparent-element.html [ Failure ]
 
-# Sheriff 2022-12-6
-crbug.com/1396127 [ Linux ] virtual/gpu-rasterization/images/color-profile-image-canvas.html [ Crash Failure Pass ]
-
 # Sheriff 2022-12-07
 crbug.com/1396185 accessibility/reach-and-scroll-overflow-div-without-mouse.html [ Pass Timeout ]
 
-# Sheriff 2022-12-08
-crbug.com/1383847 [ Linux ] virtual/exotic-color-space/images/color-profile-image-canvas.html [ Crash Failure Pass ]
 
 # Temporarily disable tests to modify Shared Storage element in Application Panel.
 crbug.com/1372007 http/tests/devtools/application-panel/resources-panel-iframe-idb.js [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-focus-child-dialog.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-focus-child-dialog.html
new file mode 100644
index 0000000..c07d313c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-focus-child-dialog.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://chromium-review.googlesource.com/c/chromium/src/+/4021969">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=popover1 popover>
+  <dialog id=childdialog autofocus>
+    <button autofocus>hello world</button>
+  </dialog>
+</div>
+
+<div id=popover2 popover=manual>
+  <div id=childpopover popover=manual autofocus>
+    <button autofocus>hello world</button>
+  </div>
+</div>
+
+<script>
+test(t => {
+  t.add_cleanup(() => childdialog.close());
+  t.add_cleanup(() => popover1.hidePopover());
+
+  childdialog.showModal();
+  document.activeElement.blur();
+  popover1.showPopover();
+
+  assert_true(popover1.matches(':open'), 'The popover should be open.');
+  assert_true(childdialog.hasAttribute('open'), 'The dialog should be open.');
+  assert_equals(document.activeElement, document.body, 'Nothing should have gotten focused.');
+}, 'Popovers should not initially focus child dialog elements.');
+
+test(t => {
+  t.add_cleanup(() => childpopover.hidePopover());
+  t.add_cleanup(() => popover2.hidePopover());
+
+  childpopover.showPopover();
+  document.activeElement.blur();
+  popover2.showPopover();
+
+  assert_true(popover2.matches(':open'), 'The parent popover should be open.');
+  assert_true(childpopover.matches(':open'), 'The child popover should be open.');
+  assert_equals(document.activeElement, document.body, 'Nothing should have gotten focused.');
+}, 'Popovers should not initially focus child popover elements.');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html
new file mode 100644
index 0000000..36491be2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+promise_test(async t => {
+  // Open a cross-origin window.
+  let w = window.open(get_host_info().HTTP_REMOTE_ORIGIN + "/navigation-api/navigate-event/resources/opener-postMessage-onload.html");
+  await new Promise(resolve => window.onmessage = resolve);
+
+  // Navigate the opened window to this origin.
+  w.location = get_host_info().ORIGIN + "/navigation-api/navigate-event/resources/opener-postMessage-onload.html";
+  await new Promise(resolve => window.onmessage = resolve);
+  assert_equals(w.navigation.entries().length, 1);
+  assert_equals(w.navigation.currentEntry.index, 0);
+
+  // Go back. This should not fire a navigate event, because the traversal is
+  // cross-origin. Note that history.back() must be used instead of
+  // navigation.back() because navigation.back() can never go cross-origin.
+  w.navigation.onnavigate = t.unreached_func("navigate should not fire");
+  w.history.back();
+  await new Promise(resolve => window.onmessage = resolve);
+}, "A cross-origin traversal does not fire the navigate event");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/resources/opener-postMessage-onload.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/resources/opener-postMessage-onload.html
new file mode 100644
index 0000000..97e1d82
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/resources/opener-postMessage-onload.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<head>
+<script>
+window.onload = () => opener.postMessage("onload", "*");
+</script>
+</head>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercancel_touch.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercancel_touch.html
index b09ddd74..9d0314655 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercancel_touch.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercancel_touch.html
@@ -48,8 +48,16 @@
                         assert_equals(event.pointerId, pointerdown_event.pointerId, "pointerId should be the same for pointerdown and pointercancel");
                         assert_equals(event.pointerType, pointerdown_event.pointerType, "pointerType should be the same for pointerdown and pointercancel");
                         assert_equals(event.isPrimary, pointerdown_event.isPrimary, "isPrimary should be the same for pointerdown and pointercancel");
+                    });
+                    test_pointerEvent.step(function () {
                         check_PointerEvent(event);
                     });
+                    test_pointerEvent.step(function () {
+                        assert_equals(event.x, 0, "pointercancel.x must be zero");
+                        assert_equals(event.y, 0, "pointercancel.x must be zero");
+                        assert_equals(event.clientX, 0, "pointercancel.clientX must be zero");
+                        assert_equals(event.clientY, 0, "pointercancel.clientY must be zero");
+                    });
                 });
 
                 on_event(target0, "pointerout", function (event) {
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html
new file mode 100644
index 0000000..062d726
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/navigation-timing-requestStart-responseStart.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.sub.js"></script>
+
+<meta name="variant" content="">
+<meta name="variant" content="?prefetch=true">
+
+<script>
+const searchParams = new URLSearchParams(location.search);
+const prefetchEnabled = searchParams.has('prefetch');
+
+promise_test(async t => {
+    assert_implements(HTMLScriptElement.supports('speculationrules'),
+      "Speculation Rules not supported");
+
+    const agent = await spawnWindow(t);
+    // Some meaningless query param to avoid cached response.
+    const prefetchUrl = agent.getExecutorURL({ a: "b" });
+
+    if (prefetchEnabled)
+      await agent.forceSinglePrefetch(prefetchUrl);
+
+    await agent.navigate(prefetchUrl);
+
+    if (prefetchEnabled) {
+      assert_prefetched(await agent.getRequestHeaders(),
+        `Prefetch ${prefetchUrl.href} should work.`);
+    } else {
+      assert_not_prefetched(await agent.getRequestHeaders(),
+        `${prefetchUrl.href} should not be prefetched.`);
+    }
+
+    const entries = await agent.execute_script(
+      () => performance.getEntriesByType('navigation'));
+    assert_equals(entries.length, 1, 'Wrong number of navigation entries');
+    const entry = entries[0];
+
+    // Events timeline:
+    //   ... -> connectEnd --> requestStart --> responseStart --> ...
+    if (prefetchEnabled) {
+      assert_equals(entry.connectEnd, entry.requestStart);
+      assert_equals(entry.requestStart, entry.responseStart);
+    } else {
+      assert_less_than_equal(entry.connectEnd, entry.requestStart);
+      assert_less_than_equal(entry.requestStart, entry.responseStart);
+    }
+
+  }, "PerformanceNavigationTiming.requestStart/responseStart test, same origin prefetch.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webnn/idlharness.https.any-expected.txt b/third_party/blink/web_tests/external/wpt/webnn/idlharness.https.any-expected.txt
index d7d7abd0..ab8aed9 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/idlharness.https.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webnn/idlharness.https.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 389 tests; 164 PASS, 225 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 389 tests; 166 PASS, 223 FAIL, 0 TIMEOUT, 0 NOTRUN.
 FAIL idl_test setup assert_unreached: navigator.ml.createContext returned a Promise Reached unreachable code
 PASS idl_test validation
 PASS Partial interface MLContext: original interface defined
@@ -219,8 +219,8 @@
 PASS MLGraphBuilder interface: operation relu()
 PASS MLGraphBuilder interface: operation resample2d(MLOperand, optional MLResample2dOptions)
 PASS MLGraphBuilder interface: operation reshape(MLOperand, sequence<long>)
-FAIL MLGraphBuilder interface: operation sigmoid(MLOperand) assert_own_property: interface prototype object missing non-static operation expected property "sigmoid" missing
-FAIL MLGraphBuilder interface: operation sigmoid() assert_own_property: interface prototype object missing non-static operation expected property "sigmoid" missing
+PASS MLGraphBuilder interface: operation sigmoid(MLOperand)
+PASS MLGraphBuilder interface: operation sigmoid()
 FAIL MLGraphBuilder interface: operation slice(MLOperand, sequence<long>, sequence<long>, optional MLSliceOptions) assert_own_property: interface prototype object missing non-static operation expected property "slice" missing
 PASS MLGraphBuilder interface: operation softmax(MLOperand)
 FAIL MLGraphBuilder interface: operation softplus(MLOperand, optional MLSoftplusOptions) assert_own_property: interface prototype object missing non-static operation expected property "softplus" missing
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 116fcb0b1..7858e2ca 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -4891,6 +4891,11 @@
     attribute @@toStringTag
     getter token
     method constructor
+interface IdentityProvider
+    static method login
+    static method logout
+    attribute @@toStringTag
+    method constructor
 interface IdleDeadline
     attribute @@toStringTag
     getter didTimeout
@@ -5253,6 +5258,7 @@
     method relu
     method resample2d
     method reshape
+    method sigmoid
     method softmax
     method sub
 interface MLModelLoader
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 67a9760..2be69964 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -9,9 +9,6 @@
 // Please run the closure compiler before committing changes.
 // See https://chromium.googlesource.com/chromium/src/+/main/docs/closure_compilation.md
 
-// TODO(b/242243189): Disable automatic extern generation until fixed.
-// The longClick method was manually inserted.
-
 /**
  * @fileoverview Externs generated from namespace: automation
  * @externs
@@ -2371,7 +2368,7 @@
  * Simulates long click on node.
  * @see https://developer.chrome.com/extensions/automation#method-longClick
  */
- chrome.automation.AutomationNode.prototype.longClick = function() {};
+chrome.automation.AutomationNode.prototype.longClick = function() {};
 
 /**
  * Scrolls this scrollable container backward.
diff --git a/third_party/nearby/BUILD.gn b/third_party/nearby/BUILD.gn
index e257159..b1ae746 100644
--- a/third_party/nearby/BUILD.gn
+++ b/third_party/nearby/BUILD.gn
@@ -269,6 +269,7 @@
     "src/connections/implementation/mediums/bluetooth_radio.cc",
     "src/connections/implementation/mediums/mediums.cc",
     "src/connections/implementation/mediums/webrtc.cc",
+    "src/connections/implementation/mediums/wifi_direct.cc",
     "src/connections/implementation/mediums/wifi_hotspot.cc",
     "src/connections/implementation/mediums/wifi_lan.cc",
   ]
@@ -288,6 +289,7 @@
     "src/connections/implementation/mediums/lost_entity_tracker.h",
     "src/connections/implementation/mediums/mediums.h",
     "src/connections/implementation/mediums/webrtc.h",
+    "src/connections/implementation/mediums/wifi_direct.h",
     "src/connections/implementation/mediums/wifi_hotspot.h",
     "src/connections/implementation/mediums/wifi_lan.h",
   ]
@@ -731,8 +733,8 @@
     "src/presence/presence_service.h",
   ]
   public_deps = [
-    ":presence_types",
     ":presence_internal",
+    ":presence_types",
     "//third_party/abseil-cpp:absl",
   ]
   configs -= [ "//build/config/compiler:chromium_code" ]
@@ -818,8 +820,8 @@
     ":platform_public_logging",
     ":platform_public_types",
     ":presence_ldt_stub",
-    ":presence_types",
     ":presence_mediums",
+    ":presence_types",
     "//third_party/abseil-cpp:absl",
     "//third_party/boringssl:boringssl",
   ]
@@ -831,12 +833,8 @@
     ":nearby_include_config",
     ":nearby_defines",
   ]
-  sources = [
-    "src/presence/implementation/np_ldt.c",
-  ]
-  public = [
-    "src/presence/implementation/np_ldt.h",
-  ]
+  sources = [ "src/presence/implementation/np_ldt.c" ]
+  public = [ "src/presence/implementation/np_ldt.h" ]
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
 }
@@ -845,9 +843,7 @@
     ":nearby_include_config",
     ":nearby_defines",
   ]
-  public = [
-    "src/presence/implementation/sensor_fusion.h",
-  ]
+  public = [ "src/presence/implementation/sensor_fusion.h" ]
   public_deps = [
     ":presence_types",
     "//third_party/abseil-cpp:absl",
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 61f17b05..f7054cf 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: ba1578777ddaaf8bcc93f19500a23dcdd6bbbcbe
+Version: 32163382b9d9c02eaea9ab5cd576b3a91890f133
 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 1462608..d78aeaa1 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -836,7 +836,7 @@
       'linux-autofill-captured-sites-rel': 'release_bot',
       'linux-finch-smoke-chrome': 'official_reclient',
       'linux-password-manager-captured-sites-rel': 'release_bot',
-      'lorenz-graph-dbg': 'android_debug_static_external_bot_reclient',
+      'lorenz-graph-dbg': 'android_debug_static_external_bot_reclient_fastbuild',
       'mac-arm64-finch-smoke-chrome': 'official_reclient_mac_arm',
       # b/248132996: for siso experiments.
       'siso-comparison-android': {
@@ -1543,8 +1543,8 @@
       'android', 'debug_static_bot_reclient', 'x86', 'webview_shell',
     ],
 
-    'android_debug_static_external_bot_reclient': [
-      'android', 'debug_static_bot_reclient', 'android_external'
+    'android_debug_static_external_bot_reclient_fastbuild': [
+      'android', 'debug_static_bot_reclient', 'android_external', 'android_fastbuild',
     ],
 
     'android_debug_trybot_arm64': [
diff --git a/tools/mb/mb_config_expectations/internal.chrome.fyi.json b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
index 27afa51e..09c1354 100644
--- a/tools/mb/mb_config_expectations/internal.chrome.fyi.json
+++ b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
@@ -56,6 +56,7 @@
   },
   "lorenz-graph-dbg": {
     "gn_args": {
+      "android_static_analysis": "off",
       "debuggable_apks": false,
       "enable_chrome_android_internal": false,
       "ffmpeg_branding": "Chrome",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ce33049..0e61c6c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -41542,6 +41542,7 @@
   <int value="4423" label="DialogCloseWatcherCancelSkippedAndDefaultPrevented"/>
   <int value="4424" label="DialogCloseWatcherCloseSignalClosedMultiple"/>
   <int value="4425" label="NoVarySearch"/>
+  <int value="4426" label="FedCmUserInfo"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -50788,6 +50789,22 @@
   <int value="10" label="SUGGESTION_CHANGED_ACCENT"/>
 </enum>
 
+<enum name="IMEAutocorrectRejectionBreakdown">
+  <int value="0" label="SUGGESTION_REJECTED"/>
+  <int value="1" label="REJECTION_OTHER"/>
+  <int value="2" label="UNDO_WITHOUT_KEYBOARD"/>
+  <int value="3" label="UNDO_WITH_KEYBOARD"/>
+  <int value="4" label="UNDO_CTRL_Z"/>
+  <int value="5" label="REJECTED_BACKSPACE"/>
+  <int value="6" label="REJECTED_CTRL_BACKSPACE"/>
+  <int value="7" label="REJECTED_TYPING_FULL"/>
+  <int value="8" label="REJECTED_TYPING_PARTIAL"/>
+  <int value="9" label="REJECTED_TYPING_FULL_WITH_EXTERNAL"/>
+  <int value="10" label="REJECTED_TYPING_PARTIAL_WITH_EXTERNAL"/>
+  <int value="11" label="REJECTED_TYPING_NO_SELECTION"/>
+  <int value="12" label="REJECTED_SELECTED_INVALID_RANGE"/>
+</enum>
+
 <enum name="IMECommitType">
   <obsolete>
     Deprecated 03/2015, and replaced by IMECommitType2.
@@ -57128,6 +57145,7 @@
   <int value="-2007377632" label="DiscoverFeedMultiColumn:enabled"/>
   <int value="-2005089558" label="BackgroundVideoTrackOptimization:disabled"/>
   <int value="-2004882388" label="AutofillPruneSuggestions:enabled"/>
+  <int value="-2004862295" label="FedCmUserInfo:disabled"/>
   <int value="-2004231189" label="AppNotificationStatusMessaging:disabled"/>
   <int value="-2003354337"
       label="enable-search-button-in-omnibox-for-str-or-iip"/>
@@ -57936,6 +57954,7 @@
   <int value="-1538084940" label="PageInfoCookiesSubpage:disabled"/>
   <int value="-1537773844"
       label="ContextualSuggestionsAlternateCardLayout:disabled"/>
+  <int value="-1537701085" label="FedCmUserInfo:enabled"/>
   <int value="-1536293422" label="SharedArrayBuffer:enabled"/>
   <int value="-1536242739" label="security-chip"/>
   <int value="-1535758690" label="AutoplayIgnoreWebAudio:disabled"/>
@@ -72486,6 +72505,7 @@
   <int value="157" label="Privacy Hub Microphone and Camera"/>
   <int value="158" label="ARCVM Data Migration"/>
   <int value="159" label="WebHID"/>
+  <int value="160" label="Do Not Disturb"/>
 </enum>
 
 <enum name="NotificationDatabaseStatus">
@@ -81137,6 +81157,22 @@
   <int value="4" label="success already prefetched"/>
 </enum>
 
+<enum name="PrefetchStreamingURLLoaderStatus">
+  <int value="0" label="Waiting to receive head of response"/>
+  <int value="1" label="Head received, waiting to receive body of response"/>
+  <int value="2" label="Redirected"/>
+  <int value="3" label="Request successful, but prefetch not served"/>
+  <int value="4" label="Request successful, prefech served after completion"/>
+  <int value="5" label="Request successful, prefech served before completion"/>
+  <int value="6" label="Prefetch was a decoy"/>
+  <int value="7" label="Request failed, invalid head"/>
+  <int value="8" label="Request failed, invalid headers"/>
+  <int value="9" label="Request failed, non-2XX response code"/>
+  <int value="10" label="Request failed, MIME type not supported"/>
+  <int value="11" label="Request failed, net error"/>
+  <int value="12" label="Request failed, net error, but served before error"/>
+</enum>
+
 <enum name="PrefHashStoreVersion">
   <int value="0" label="VERSION_UNINITIALIZED"/>
   <int value="1" label="VERSION_PRE_MIGRATION"/>
@@ -104115,6 +104151,12 @@
   <int value="4" label="Evicted"/>
 </enum>
 
+<enum name="VkToUssMigrationStatus">
+  <int value="0" label="Success"/>
+  <int value="1" label="Failed persisting to USS"/>
+  <int value="2" label="Failed constructing input"/>
+</enum>
+
 <enum name="VoiceIntentTarget">
 <!-- This must be kept in sync with the VoiceIntentTarget variants in
 others/histograms.xml -->
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 34de63f7..04eb7e9 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1886,6 +1886,18 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.Layout.UpdateLayerPositionsAfterLayout"
+    units="microseconds" expires_after="2023-05-07">
+  <owner>pdr@chromium.org</owner>
+  <owner>ikilpatrick@chromium.org</owner>
+  <owner>layout-dev@chromium.org</owner>
+  <summary>
+    Time spent in PaintLayer::UpdateLayerPositionsAfterLayout.
+
+    Note: Not recorded on ~5% of Windows machines with low-resolution clocks.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Blink.Layout.UpdateTime" units="microseconds"
     expires_after="2023-05-27">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
@@ -3421,6 +3433,25 @@
       variants="BlinkVisibleLoadTimeSuffixes"/>
 </histogram>
 
+<histogram base="true" name="Blink.VisualUpdateDelay.UpdateTime"
+    units="microseconds" expires_after="2023-03-26">
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
+
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
+
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+
+  <owner>szager@chromium.org</owner>
+  <owner>paint-dev@chromium.org</owner>
+  <summary>
+    Elapsed time between when a widget first registers a state invalidation that
+    requires a visual refresh; and when the main thread begins a main frame that
+    will send the updated visual state to the compositor.
+
+    This histogram only records metrics on machines with high-resolution clocks.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Blink.WaitForCommit.UpdateTime"
     units="microseconds" expires_after="2023-06-04">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
diff --git a/tools/metrics/histograms/metadata/bookmarks/histograms.xml b/tools/metrics/histograms/metadata/bookmarks/histograms.xml
index ef1cae0..4416915 100644
--- a/tools/metrics/histograms/metadata/bookmarks/histograms.xml
+++ b/tools/metrics/histograms/metadata/bookmarks/histograms.xml
@@ -119,9 +119,9 @@
 </histogram>
 
 <histogram name="Bookmarks.BookmarksBar.DragDropType"
-    enum="BookmarkBarDragDropType" expires_after="2023-01-15">
+    enum="BookmarkBarDragDropType" expires_after="2023-06-04">
   <owner>dfried@chromium.org</owner>
-  <owner>mickeyburks@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
     Records whether a bookmarks bar drag-drop is from a URL (e.g. from the
     location bar) or from the bookmarks bar itself (which is effectively a
diff --git a/tools/metrics/histograms/metadata/cryptohome/histograms.xml b/tools/metrics/histograms/metadata/cryptohome/histograms.xml
index dcad8c330..3b65aff 100644
--- a/tools/metrics/histograms/metadata/cryptohome/histograms.xml
+++ b/tools/metrics/histograms/metadata/cryptohome/histograms.xml
@@ -989,6 +989,21 @@
   </token>
 </histogram>
 
+<histogram name="Cryptohome.VkToUssMigrationStatus"
+    enum="VkToUssMigrationStatus" expires_after="2023-12-07">
+  <owner>jadmanski@chromium.org</owner>
+  <owner>cros-hwsec+uma@chromium.org</owner>
+  <summary>
+    Records a status value on every attempt to migrate a vault keyset backed
+    authentication factor to the User Secret Stash. The status will either
+    indicate success or an error, with different errors suggesting the general
+    class of failure that occured.
+
+    Migration of a factor is attempted whenever said factor is successfully used
+    to authenticate a session; usually when you login or unlock.
+  </summary>
+</histogram>
+
 <histogram name="Cryptohome.WrappingKeyDerivation.Create"
     enum="WrappingKeyDerivation" expires_after="2023-05-01">
   <owner>tnagel@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/download/histograms.xml b/tools/metrics/histograms/metadata/download/histograms.xml
index f5325aa..22b4d348 100644
--- a/tools/metrics/histograms/metadata/download/histograms.xml
+++ b/tools/metrics/histograms/metadata/download/histograms.xml
@@ -715,9 +715,9 @@
     enum="MixedContentDownloadDialogEvent" expires_after="2022-12-11">
   <owner>qinmin@chromium.org</owner>
   <summary>
-    Records user interactions with the mixed content download dialog on Android.
-    Records when the dialog is shown, or when user clicks on a button or
-    dismisses the dialog.
+    Records user interactions with the insecure download (formerly the mixed
+    content download) dialog on Android. Records when the dialog is shown, or
+    when user clicks on a button or dismisses the dialog.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index b4107ee..cc58b74 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -2185,6 +2185,42 @@
   <summary>Result of the OpenNetworkConfiguration policy validation.</summary>
 </histogram>
 
+<histogram name="Enterprise.PlatformAuth.GetAuthData.Count" units="count"
+    expires_after="2023-10-31">
+  <owner>igorruvinov@chromium.org</owner>
+  <owner>zmin@chromium.org</owner>
+  <summary>
+    The number of proof-of-possession cookies retrieved from the platform to
+    authenticate the user to their IdP/STS. Recorded once per successful fetch.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.PlatformAuth.GetAuthData.FailureHresult"
+    enum="Hresult" expires_after="2023-10-31">
+  <owner>igorruvinov@chromium.org</owner>
+  <owner>zmin@chromium.org</owner>
+  <summary>
+    The HRESULT returned by either CoCreateInstance or GetCookieInfoForUri when
+    trying to fetch proof-of-possession cookies from the platform to
+    authenticate the user to their IdP/STS.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.PlatformAuth.GetAuthData.{Result}Time" units="ms"
+    expires_after="2023-10-31">
+  <owner>igorruvinov@chromium.org</owner>
+  <owner>zmin@chromium.org</owner>
+  <summary>
+    The elapsed time for a fetch of proof-of-possession cookies from the
+    platform to authenticate the user to their IdP/STS to {Result}. Recorded
+    once per fetch.
+  </summary>
+  <token key="Result">
+    <variant name="Failure" summary="fail"/>
+    <variant name="Success" summary="succeed"/>
+  </token>
+</histogram>
+
 <histogram name="Enterprise.Policies" enum="EnterprisePolicies"
     expires_after="2023-05-07">
   <owner>mnissler@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 179d1db..e1251e3 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -697,6 +697,7 @@
   <affected-histogram name="Blink.Style.UpdateTime"/>
   <affected-histogram name="Blink.UpdateViewportIntersection.UpdateTime"/>
   <affected-histogram name="Blink.UserDrivenDocumentUpdate.UpdateTime"/>
+  <affected-histogram name="Blink.VisualUpdateDelay.UpdateTime"/>
   <affected-histogram name="Blink.WaitForCommit.UpdateTime"/>
 </histogram_suffixes>
 
@@ -733,6 +734,7 @@
   <affected-histogram name="Blink.Style.UpdateTime"/>
   <affected-histogram name="Blink.UpdateViewportIntersection.UpdateTime"/>
   <affected-histogram name="Blink.UserDrivenDocumentUpdate.UpdateTime"/>
+  <affected-histogram name="Blink.VisualUpdateDelay.UpdateTime"/>
   <affected-histogram name="Blink.WaitForCommit.UpdateTime"/>
 </histogram_suffixes>
 
@@ -769,6 +771,7 @@
   <affected-histogram name="Blink.Style.UpdateTime"/>
   <affected-histogram name="Blink.UpdateViewportIntersection.UpdateTime"/>
   <affected-histogram name="Blink.UserDrivenDocumentUpdate.UpdateTime"/>
+  <affected-histogram name="Blink.VisualUpdateDelay.UpdateTime"/>
   <affected-histogram name="Blink.WaitForCommit.UpdateTime"/>
 </histogram_suffixes>
 
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 2e8db9f..29c9849 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -115,8 +115,8 @@
     interactions.
   </summary>
   <token key="Keyboard">
-    <variant name="PK" summary="physical keyboard"/>
-    <variant name="VK" summary="virtual keyboard"/>
+    <variant name="Pk" summary="physical keyboard"/>
+    <variant name="Vk" summary="virtual keyboard"/>
   </token>
   <token key="Interaction">
     <variant name="Accepted" summary="accepted"/>
@@ -134,8 +134,8 @@
     recorded once an autocorrect is accepted or rejected by user interactions.
   </summary>
   <token key="Keyboard">
-    <variant name="PK" summary="physical keyboard"/>
-    <variant name="VK" summary="virtual keyboard"/>
+    <variant name="Pk" summary="physical keyboard"/>
+    <variant name="Vk" summary="virtual keyboard"/>
   </token>
   <token key="Interaction">
     <variant name="Accepted" summary="accepted"/>
@@ -313,6 +313,21 @@
   </summary>
 </histogram>
 
+<histogram name="InputMethod.Assistive.AutocorrectV2.Rejection.{Keyboard}"
+    enum="IMEAutocorrectRejectionBreakdown" expires_after="2023-05-01">
+  <owner>zacpartridge@google.com</owner>
+  <owner>essential-inputs-team@google.com</owner>
+  <summary>
+    The histogram records the different interactions used to reject or revert an
+    autocorrect suggestion. These interactions include selecting undo with or
+    without the keyboard or deleting some or all of the word.
+  </summary>
+  <token key="Keyboard">
+    <variant name="PK" summary="physical keyboard"/>
+    <variant name="VK" summary="virtual keyboard"/>
+  </token>
+</histogram>
+
 <histogram
     name="InputMethod.Assistive.AutocorrectV2.UserPrefChange.{Type}.{Language}"
     enum="IMEAutocorrectPrefStateTransition" expires_after="2023-06-01">
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index 405039a..161b21e 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -91,7 +91,7 @@
   <token key="Process" variants="ProfiledProcess"/>
 </histogram>
 
-<histogram name="HeapProfiling.InProcess.ShortStacks{Process}" units="Boolean"
+<histogram name="HeapProfiling.InProcess.ShortStacks{Process}" enum="Boolean"
     expires_after="2023-06-05">
   <owner>joenotcharles@chromium.org</owner>
   <owner>chrome-memory@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 305f769b..3d5576e 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -958,7 +958,7 @@
 
 <histogram name="OptimizationGuide.PageTopicsOverrideList.FileLoadResult"
     enum="OptimizationGuidePageTopicsOverrideListFileLoadResult"
-    expires_after="2023-01-22">
+    expires_after="M112">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -971,7 +971,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PageTopicsOverrideList.GotFile"
-    enum="Boolean" expires_after="2023-01-22">
+    enum="Boolean" expires_after="M112">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -981,7 +981,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PageTopicsOverrideList.UsedOverride"
-    enum="Boolean" expires_after="2023-06-04">
+    enum="Boolean" expires_after="M112">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 6545594a..07e95f38 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -6256,7 +6256,7 @@
 </histogram>
 
 <histogram name="FirstPartySets.Database.InitStatus"
-    enum="FirstPartySetsDatabaseInitStatus" expires_after="2022-12-21">
+    enum="FirstPartySetsDatabaseInitStatus" expires_after="2023-08-20">
   <owner>shuuran@google.com</owner>
   <owner>kaustubhag@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/prefetch/histograms.xml b/tools/metrics/histograms/metadata/prefetch/histograms.xml
index f54454e..e18a0f8f 100644
--- a/tools/metrics/histograms/metadata/prefetch/histograms.xml
+++ b/tools/metrics/histograms/metadata/prefetch/histograms.xml
@@ -417,6 +417,22 @@
   </summary>
 </histogram>
 
+<histogram name="PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus"
+    units="PrefetchStreamingURLLoaderStatus" expires_after="2023-04-25">
+  <owner>curranmax@chromium.org</owner>
+  <owner>ryansturm@chromium.org</owner>
+  <owner>spelchat@chromium.org</owner>
+  <summary>
+    Records the final status of the PrefetchStreamingURLLoader used to request
+    the prefetch.
+
+    This is recorded once for each prefetch that is requested using a streaming
+    URL loader. It is recorded when the streaming URL loader is deleted which
+    can be when: 1) the URL loader is canceled, 2) the URL loader completed
+    serving the prefetch, or 3) the prefetch was deleted and not used.
+  </summary>
+</histogram>
+
 <histogram name="PrefetchProxy.Prefetch.Subresources.NetError"
     enum="NetErrorCodes" expires_after="2023-04-25">
   <owner>curranmax@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ukm/histograms.xml b/tools/metrics/histograms/metadata/ukm/histograms.xml
index 36c6f5ef0..5fca2979 100644
--- a/tools/metrics/histograms/metadata/ukm/histograms.xml
+++ b/tools/metrics/histograms/metadata/ukm/histograms.xml
@@ -57,6 +57,18 @@
   </summary>
 </histogram>
 
+<histogram name="UKM.ConsentObserver.AppSyncConsentChanged" enum="Boolean"
+    expires_after="2023-05-01">
+  <owner>andrewbregger@google.com</owner>
+  <owner>ukm-team@google.com</owner>
+  <summary>
+    Logged in UpdateUkmAllowedForAllProfiles from the UkmConsentStateObserver.
+    This records if App-sync was changed everytime a profile is updated. True
+    will be recorded when App-sync changes and false otherwise. This is to
+    understand how often App-sync is changed.
+  </summary>
+</histogram>
+
 <histogram name="UKM.ConsentObserver.Purge" enum="Boolean"
     expires_after="2023-07-01">
   <owner>rkaplow@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/web_audio/histograms.xml b/tools/metrics/histograms/metadata/web_audio/histograms.xml
index 0c12259..205a6a7 100644
--- a/tools/metrics/histograms/metadata/web_audio/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_audio/histograms.xml
@@ -231,7 +231,7 @@
 </histogram>
 
 <histogram name="WebAudio.ConvolverNode.ImpulseResponseLength" units="ms"
-    expires_after="2023-01-22">
+    expires_after="2023-04-01">
   <owner>hongchan@chromium.org</owner>
   <owner>mjwilson@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index ba2f842a..c38ba6d9 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -3280,6 +3280,12 @@
       Paint, in microseconds.
     </summary>
   </metric>
+  <metric name="VisualUpdateDelay">
+    <summary>
+      Time elapsed, in microseconds, between when an animation update is first
+      scheduled and when the resulting BeginMainFrame starts.
+    </summary>
+  </metric>
   <metric name="WaitForCommit">
     <summary>
       The time spent waiting for the compositor thread to begin processing a
@@ -4626,6 +4632,18 @@
       sampled frame, in microseconds.
     </summary>
   </metric>
+  <metric name="VisualUpdateDelay">
+    <summary>
+      Time elapsed between when an animation update is first scheduled and when
+      the resulting BeginMainFrame starts.
+    </summary>
+  </metric>
+  <metric name="VisualUpdateDelayBeginMainFrame">
+    <summary>
+      Time elapsed between when an animation update is first scheduled and when
+      the resulting BeginMainFrame starts.
+    </summary>
+  </metric>
   <metric name="WaitForCommit">
     <summary>
       The time spent waiting for the compositor thread to begin processing a
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index f1939650..b49eff4 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -13,8 +13,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "b3cb996b761d4fc21bc8118fdd2800d5a8d22543",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/404b02b04437917628244a583a5e2d3f56a3debb/trace_processor_shell"
+            "hash": "4f0adda7bdef9039b2aeb0592cf1e250dd7c0778",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/2648c757a2555e5e1c5946e46202fc385d68625b/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
diff --git a/tools/rust/update_rust.py b/tools/rust/update_rust.py
index 41d52e40..2614536 100755
--- a/tools/rust/update_rust.py
+++ b/tools/rust/update_rust.py
@@ -27,7 +27,7 @@
     os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'clang',
                  'scripts'))
 
-RUST_REVISION = '20221207'
+RUST_REVISION = '20221209'
 RUST_SUB_REVISION = 1
 
 # Trunk on 2022-10-15.
diff --git a/tools/typescript/definitions/windows.d.ts b/tools/typescript/definitions/windows.d.ts
index 762186b1..288fc4e 100644
--- a/tools/typescript/definitions/windows.d.ts
+++ b/tools/typescript/definitions/windows.d.ts
@@ -44,22 +44,6 @@
       PANEL = 'panel',
     }
 
-    export interface GetInfo {
-      populate?: boolean;
-      windowTypes?: WindowType[];
-    }
-
-    export const WINDOW_ID_NONE: number;
-    export const WINDOW_ID_CURRENT: number;
-
-    export function get(windowId: number, getInfo: (GetInfo|null),
-                        callback: (p1: Window) => void): void;
-    export function getCurrent(getInfo: (GetInfo|null),
-                               callback: (p1: Window) => void): void;
-    export function getLastFocused(getInfo: (GetInfo|null),
-                                   callback: (p1: Window) => void): void;
-    export function getAll(getInfo: (GetInfo|null),
-                           callback: (p1: Window[]) => void): void;
     interface CreateData {
       url?: (string|string[]);
       tabId?: number;
@@ -74,21 +58,6 @@
       setSelfAsOpener?: boolean;
     }
 
-    export function create(createData?: CreateData,
-                           callback?: (p1: Window) => void): void;
-
-    interface UpdateInfo {
-      left?: number;
-      top?: number;
-      width?: number;
-      height?: number;
-      focused?: boolean;
-      drawAttention?: boolean;
-      state?: WindowState;
-    }
-
-    export function update(windowId: number, updateInfo: UpdateInfo,
-                           callback?: (p1: Window) => void): void;
-    export function remove(windowId: number, callback?: () => void): void;
+    export function create(createData?: CreateData): Promise<Window>;
   }
 }
diff --git a/ui/README.md b/ui/README.md
index fc1e3f3..4d355c16 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -1,3 +1,3 @@
 This directory contains UI frameworks used to build various user
-interface features. This directory it not intended to contain UI
+interface features. This directory is not intended to contain UI
 features (such as a keyboard).
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index eb6bcc2..5a9fd7f 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -372,6 +372,7 @@
       cc::PaintHoldingReason,
       absl::optional<cc::PaintHoldingCommitTrigger>) override {}
   void OnPauseRenderingChanged(bool) override {}
+  void OnCommitRequested() override {}
   void WillUpdateLayers() override {}
   void DidUpdateLayers() override;
   void BeginMainFrame(const viz::BeginFrameArgs& args) override;
diff --git a/ui/gfx/color_utils.cc b/ui/gfx/color_utils.cc
index 1bb329fe..bd9367a 100644
--- a/ui/gfx/color_utils.cc
+++ b/ui/gfx/color_utils.cc
@@ -56,8 +56,7 @@
 }
 
 // Assumes sRGB.
-float Linearize(float eight_bit_component) {
-  const float component = eight_bit_component / 255.0f;
+float Linearize(float component) {
   // The W3C link in the header uses 0.03928 here.  See
   // https://en.wikipedia.org/wiki/SRGB#Theory_of_the_transformation for
   // discussion of why we use this value rather than that one.
@@ -309,6 +308,11 @@
                           GetRelativeLuminance(color_b));
 }
 
+float GetContrastRatio(SkColor4f color_a, SkColor4f color_b) {
+  return GetContrastRatio(GetRelativeLuminance4f(color_a),
+                          GetRelativeLuminance4f(color_b));
+}
+
 float GetContrastRatio(float luminance_a, float luminance_b) {
   DCHECK_GE(luminance_a, 0.0f);
   DCHECK_GE(luminance_b, 0.0f);
@@ -319,9 +323,12 @@
 }
 
 float GetRelativeLuminance(SkColor color) {
-  return (0.2126f * Linearize(SkColorGetR(color))) +
-         (0.7152f * Linearize(SkColorGetG(color))) +
-         (0.0722f * Linearize(SkColorGetB(color)));
+  return GetRelativeLuminance4f(SkColor4f::FromColor(color));
+}
+
+float GetRelativeLuminance4f(SkColor4f color) {
+  return (0.2126f * Linearize(color.fR)) + (0.7152f * Linearize(color.fG)) +
+         (0.0722f * Linearize(color.fB));
 }
 
 uint8_t GetLuma(SkColor color) {
diff --git a/ui/gfx/color_utils.h b/ui/gfx/color_utils.h
index 6ef6fb6..6449a33 100644
--- a/ui/gfx/color_utils.h
+++ b/ui/gfx/color_utils.h
@@ -40,12 +40,14 @@
 // Determines the contrast ratio of two colors or two relative luminance values
 // (as computed by RelativeLuminance()), calculated according to
 // http://www.w3.org/TR/WCAG20/#contrast-ratiodef .
+GFX_EXPORT float GetContrastRatio(SkColor4f color_a, SkColor4f color_b);
 GFX_EXPORT float GetContrastRatio(SkColor color_a, SkColor color_b);
 GFX_EXPORT float GetContrastRatio(float luminance_a, float luminance_b);
 
 // The relative luminance of |color|, that is, the weighted sum of the
 // linearized RGB components, normalized to 0..1, per BT.709.  See
 // http://www.w3.org/TR/WCAG20/#relativeluminancedef .
+GFX_EXPORT float GetRelativeLuminance4f(SkColor4f color);
 GFX_EXPORT float GetRelativeLuminance(SkColor color);
 
 // The luma of |color|, that is, the weighted sum of the gamma-compressed R'G'B'
diff --git a/ui/webui/examples/browser/ui/web/webui.cc b/ui/webui/examples/browser/ui/web/webui.cc
index fb6ec40..2a1214c 100644
--- a/ui/webui/examples/browser/ui/web/webui.cc
+++ b/ui/webui/examples/browser/ui/web/webui.cc
@@ -15,12 +15,23 @@
 
 namespace {
 
+void EnableTrustedTypesCSP(content::WebUIDataSource* source) {
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::RequireTrustedTypesFor,
+      "require-trusted-types-for 'script';");
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::TrustedTypes,
+      "trusted-types parse-html-subset sanitize-inner-html static-types "
+      // Add TrustedTypes policies for cr-lottie.
+      "lottie-worker-script-loader "
+      // Add TrustedTypes policies necessary for using Polymer.
+      "polymer-html-literal polymer-template-event-attribute-policy;");
+}
+
 void SetJSModuleDefaults(content::WebUIDataSource* source) {
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::ScriptSrc,
       "script-src chrome://resources 'self';");
-  // TODO(crbug.com/1098690): Trusted Type Polymer
-  source->DisableTrustedTypesCSP();
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::FrameSrc, "frame-src 'self';");
   source->OverrideContentSecurityPolicy(
@@ -32,6 +43,7 @@
                           base::span<const webui::ResourcePath> resources,
                           int default_resource) {
   SetJSModuleDefaults(source);
+  EnableTrustedTypesCSP(source);
   source->AddResourcePaths(resources);
   source->AddResourcePath("", default_resource);
 }
diff --git a/weblayer/browser/download_manager_delegate_impl.cc b/weblayer/browser/download_manager_delegate_impl.cc
index abc637b..5db47ad 100644
--- a/weblayer/browser/download_manager_delegate_impl.cc
+++ b/weblayer/browser/download_manager_delegate_impl.cc
@@ -89,7 +89,7 @@
         item->GetForcedFilePath(),
         download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-        download::DownloadItem::MixedContentStatus::UNKNOWN,
+        download::DownloadItem::InsecureDownloadStatus::UNKNOWN,
         item->GetForcedFilePath(), base::FilePath(),
         std::string() /*mime_type*/, download::DOWNLOAD_INTERRUPT_REASON_NONE);
     return true;
@@ -260,7 +260,7 @@
   std::move(callback).Run(
       suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
       download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      download::DownloadItem::MixedContentStatus::UNKNOWN,
+      download::DownloadItem::InsecureDownloadStatus::UNKNOWN,
       suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")),
       base::FilePath(), std::string() /*mime_type*/,
       download::DOWNLOAD_INTERRUPT_REASON_NONE);