diff --git a/DEPS b/DEPS
index 76c0908..523b4d8 100644
--- a/DEPS
+++ b/DEPS
@@ -306,15 +306,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': 'c0441ac66681bb025fe45ca06acbd21a72b8f39e',
+  'src_internal_revision': '7dad41b491ac6ef10b238af85d0cfe5a91124393',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '9b103a4148e291537909a1c0765b43a5125676d2',
+  'skia_revision': 'fe4aa8a3ea5380f02ba16568299b2d11a2c0a41e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '93c5bbc582e38f3a543ae0e5a28e593f2e7cc6dd',
+  'v8_revision': '4892aa3e774cdd84597e696d57b4468a6ae3d77f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -337,7 +337,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:18.20240127.1.1',
+  'fuchsia_version': 'version:18.20240207.3.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -381,11 +381,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '27e029f52955bf8ac9e35a1dacf4f56bb39d3eb8',
+  'catapult_revision': 'ca502d307f30e7ac2de76211d8d590ff31e5080f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': '94fec55cb64a630ad0790e1833dd195181ce6688',
+  'chromium_variations_revision': '8460157bc5ad13094b0e93276c7387b47932195d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -429,7 +429,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': '9c463fdd34e5975521f6211c5609d1f3945f3c15',
+  'quiche_revision': 'bccc7cc648236d93d8cd4f07c82d7a15a6218bf6',
   # 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.
@@ -481,7 +481,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.
-  'libunwind_revision':    'fc505746f02c927d792bdeb328307e0e87500342',
+  'libunwind_revision':    '8bad7bd6ec30f94bce82f7cb5b58ecbd6ce02996',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -501,7 +501,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       '9d119c1f4a097b7d27210874f4eba3fc91a83a4e',
+  'libcxx_revision':       'c51c9efb6c5a83e0e25c1e30864449d2beadd7a8',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:a2e2717ea670249a34b0de4b3e54f268d320bdfa',
@@ -825,12 +825,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'cbd13ba3774974cb20cd7dbaf2acc4860fe6d0f4',
+    'ab0b95197af40620e76f2054c376e0068d43a968',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'ac769493150a27e6a4dc32da747c9613496a9e87',
+    'url': Var('chromium_git') + '/website.git' + '@' + '7854b9d90b9fb1e57c50e6f039bc44fa587a9eca',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1197,7 +1197,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '784db7a5f04f5093941b6c2228e40b9a10d553b2',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '74a6ca92bba677fc87328637bf573deb5944ac91',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1652,7 +1652,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '739dc8a257547a54c71ecf73bba66bef0726825a',
+    Var('chromium_git') + '/openscreen' + '@' + '03c85f7fe80efa241092f258e33452f47b2ace4e',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '95fe35ffb383710a6e0567e958ead9a3b66e930c',
@@ -1663,7 +1663,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '74cdba3b28c2a48bb3c5b5cae32812eb6c76a8da',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '6821c955bdf682556ec4828a174a9cd916d0a02a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -1845,10 +1845,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f4bf599a8b575df685c31d9c4729a70a04e377ed',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e082b08475761a2ba6a3349dfea72f704c8b68d4',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6493b876ade082dc4e4d883691e8900d5b42f01c',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'aaa123debbd106669fc849425b92607edbd5d26f',
+    Var('webrtc_git') + '/src.git' + '@' + 'fe6178ef7d7f4f25ce93253bdda0b7daae0769a6',
 
   # 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 d5f49ab4..c7980532f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -974,13 +974,15 @@
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
     BanRule(
-      r'/(\babsl::Span\b|#include <span>)',
+      r'/(\babsl::Span\b|#include <span>|\bstd::span\b)',
       (
-        'absl::Span is banned and <span> is not allowed yet ',
+        'absl::Span and std::span are not allowed ',
         '(https://crbug.com/1414652). Use base::span instead.',
       ),
       True,
       [
+        # Included for conversions between base and std.
+        r'base/containers/span.h',
         # Test base::span<> compatibility against std::span<>.
         r'base/containers/span_unittest.cc',
         # Needed to use QUICHE API.
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index aa891392..8e9a73c5 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2726,6 +2726,8 @@
     "wm/snap_group/snap_group_controller.h",
     "wm/splitview/auto_snap_controller.cc",
     "wm/splitview/auto_snap_controller.h",
+    "wm/splitview/faster_split_view.cc",
+    "wm/splitview/faster_split_view.h",
     "wm/splitview/layout_divider_controller.h",
     "wm/splitview/split_view_constants.h",
     "wm/splitview/split_view_controller.cc",
diff --git a/ash/DEPS b/ash/DEPS
index a4ffe37..cea2d13 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -16,6 +16,7 @@
   "+components/live_caption",
   "+components/media_message_center",
   "+components/metrics/structured/structured_events.h",
+  "+components/metrics/structured/structured_metrics_client.h",
   "+components/pref_registry",
   "+components/prefs",
   "+components/quirks",
diff --git a/ash/app_list/views/app_list_bubble_apps_page.cc b/ash/app_list/views/app_list_bubble_apps_page.cc
index c317cea..b47d3df2 100644
--- a/ash/app_list/views/app_list_bubble_apps_page.cc
+++ b/ash/app_list/views/app_list_bubble_apps_page.cc
@@ -187,8 +187,8 @@
   // Set up scroll bars.
   scroll_view_->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
-  auto vertical_scroll =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kVerticalScrollInsets);
   vertical_scroll->SetSnapBackOnDragOutside(false);
   scroll_bar_ = vertical_scroll.get();
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index 209018b6..916eeeb0 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -728,8 +728,8 @@
   // Set up scroll bars.
   scroll_view_->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
-  auto vertical_scroll =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kVerticalScrollInsets);
   scroll_view_->SetVerticalScrollBar(std::move(vertical_scroll));
 
diff --git a/ash/app_list/views/app_list_search_view.cc b/ash/app_list/views/app_list_search_view.cc
index 2f28813b..cfb40db 100644
--- a/ash/app_list/views/app_list_search_view.cc
+++ b/ash/app_list/views/app_list_search_view.cc
@@ -89,8 +89,8 @@
   // Set up scroll bars.
   scroll_view_->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
-  auto vertical_scroll =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kVerticalScrollInsets);
   scroll_view_->SetVerticalScrollBar(std::move(vertical_scroll));
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index c54e69fd..f996f305 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2469,6 +2469,9 @@
       <message name="IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_SKIP" desc="The text for the Faster Splitscreen toast skip button.">
         Dismiss
       </message>
+      <message name="IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_DISMISS_WINDOW_SUGGESTIONS" desc="The a11y text for the Faster Splitscreen toast to dismiss window suggestions.">
+        Dismiss window suggestions
+      </message>
       <message name="IDS_ASH_OVERVIEW_WINDOW_CLOSING_A11Y_ALERT" desc="The accessibility alert read by screen readers to alert the user that a window in overview mode is closing.">
         Window <ph name="WINDOW_TITILE">$1<ex>1</ex></ph> closed.
       </message>
@@ -6945,6 +6948,15 @@
       <message name="IDS_PICKER_GIFS_CATEGORY_LABEL" translateable="false" desc="Label of the Picker list item which can be clicked to show gif results.">
         Gifs
       </message>
+      <message name="IDS_PICKER_OPEN_TABS_CATEGORY_LABEL" translateable="false" desc="Label of the Picker list item which can be clicked to show open tab results.">
+        Open tabs
+      </message>
+      <message name="IDS_PICKER_BROWSING_HISTORY_CATEGORY_LABEL" translateable="false" desc="Label of the Picker list item which can be clicked to show browsing history results.">
+        Browsing history
+      </message>
+      <message name="IDS_PICKER_BOOKMARKS_CATEGORY_LABEL" translateable="false" desc="Label of the Picker list item which can be clicked to show bookmark results.">
+        Bookmarks
+      </message>
       <message name="IDS_PICKER_ZERO_STATE_SEARCH_FIELD_PLACEHOLDER_TEXT" translateable="false" desc="Placeholder text for the search field shown in the Picker UI.">
         Search to insert
       </message>
@@ -6960,9 +6972,21 @@
       <message name="IDS_PICKER_GIFS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT" translateable="false" desc="Placeholder text for the search field shown in the Picker UI when the gifs category is selected.">
         Search Gifs
       </message>
+      <message name="IDS_PICKER_OPEN_TABS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT" translateable="false" desc="Placeholder text for the search field shown in the Picker UI when the open tabs category is selected.">
+        Search Open Tabs
+      </message>
+      <message name="IDS_PICKER_BROWSING_HISTORY_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT" translateable="false" desc="Placeholder text for the search field shown in the Picker UI when the browsing history category is selected.">
+        Search Browsing History
+      </message>
+      <message name="IDS_PICKER_BOOKMARKS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT" translateable="false" desc="Placeholder text for the search field shown in the Picker UI when the bookmarks category is selected.">
+        Search Bookmarks
+      </message>
       <message name="IDS_PICKER_EXPRESSIONS_CATEGORY_TYPE_SECTION_TITLE" translateable="false" desc="Title of the Picker section which contains expression categories, i.e. emojis, symbols, emoticons and gifs.">
         Expressions
       </message>
+      <message name="IDS_PICKER_LINKS_CATEGORY_TYPE_SECTION_TITLE" translateable="false" desc="Title of the Picker section which contains link categories, e.g. browsing history, open tabs, bookmarks.">
+        Links
+      </message>
 
       <!-- WM -->
       <message name="IDS_ENTER_PIP_A11Y_NOTIFICATION" is_accessibility_with_no_ui="true" desc="Accessibility text read by chromevox when a window starts picture-in-picture mode.">
diff --git a/ash/ash_strings_grd/IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_DISMISS_WINDOW_SUGGESTIONS.png.sha1 b/ash/ash_strings_grd/IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_DISMISS_WINDOW_SUGGESTIONS.png.sha1
new file mode 100644
index 0000000..3380a16
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_DISMISS_WINDOW_SUGGESTIONS.png.sha1
@@ -0,0 +1 @@
+f3be8dadf2d9794d73f8a4f1c329f4dcae04ad9a
\ No newline at end of file
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index a22f211..fb86d7e 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1143,12 +1143,6 @@
              "FilesTrashDrive",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// If enabled, the jelly colors will be used in the firmware update app.
-// Requires jelly-colors flag to also be enabled.
-BASE_FEATURE(kFirmwareUpdateJelly,
-             "FirmwareUpdateJelly",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables the v2 version of the Firmware Updates app.
 BASE_FEATURE(kFirmwareUpdateUIV2,
              "FirmwareUpdateUIV2",
@@ -2248,12 +2242,6 @@
              "PreferConstantFrameRate",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// If enabled, the jelly colors will be used in the print management app.
-// Requires jelly-colors flag to also be enabled.
-BASE_FEATURE(kPrintManagementJelly,
-             "PrintManagementJelly",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, ChromeOS print preview app is available. Enabling does not
 // replace the existing Chrome print preview UI, and will require an additional
 // flag and pref configured to facilitate. See b/323421684 for more information.
@@ -2406,11 +2394,6 @@
              "ReleaseNotesNotificationAllChannels",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables or disables Release Notes suggestion chip on ChromeOS.
-BASE_FEATURE(kReleaseNotesSuggestionChip,
-             "ReleaseNotesSuggestionChip",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables rendering ARC notifications using ChromeOS notification framework
 BASE_FEATURE(kRenderArcNotificationsByChrome,
              "RenderArcNotificationsByChrome",
@@ -3696,21 +3679,11 @@
          base::FeatureList::IsEnabled(kDiagnosticsAppJelly);
 }
 
-bool IsJellyEnabledForFirmwareUpdate() {
-  return chromeos::features::IsJellyEnabled() &&
-         base::FeatureList::IsEnabled(kFirmwareUpdateJelly);
-}
-
 bool IsJellyEnabledForOsFeedback() {
   return chromeos::features::IsJellyEnabled() &&
          base::FeatureList::IsEnabled(kOsFeedbackJelly);
 }
 
-bool IsJellyEnabledForPrintManagement() {
-  return chromeos::features::IsJellyEnabled() &&
-         base::FeatureList::IsEnabled(kPrintManagementJelly);
-}
-
 bool IsJellyEnabledForScanningApp() {
   return chromeos::features::IsJellyEnabled() &&
          base::FeatureList::IsEnabled(kScanningAppJelly);
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 08e7066..3d09e166 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -352,8 +352,6 @@
 BASE_DECLARE_FEATURE(kFilesSinglePartitionFormat);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesTrashDrive);
 COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kFirmwareUpdateJelly);
-COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kFirmwareUpdateUIV2);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFSPsInRecents);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -696,7 +694,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kPrinterSettingsPrinterStatus);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kPrinterSettingsRevamp);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kPrintManagementJelly);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kPrintPreviewCrosApp);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kPrintPreviewDiscoveredPrinters);
@@ -738,8 +735,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kReleaseNotesNotificationAllChannels);
 COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kReleaseNotesSuggestionChip);
-COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kRenderArcNotificationsByChrome);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kRemoveStalePolicyPinnedAppsFromShelf);
@@ -1084,9 +1079,7 @@
 bool IsInternalServerSideSpeechRecognitionControlEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsIppClientInfoEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForDiagnosticsApp();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForFirmwareUpdate();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForOsFeedback();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForPrintManagement();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForScanningApp();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsJellyEnabledForShortcutCustomization();
 COMPONENT_EXPORT(ASH_CONSTANTS)
diff --git a/ash/controls/rounded_scroll_bar.cc b/ash/controls/rounded_scroll_bar.cc
index 5ba0858..aaab627 100644
--- a/ash/controls/rounded_scroll_bar.cc
+++ b/ash/controls/rounded_scroll_bar.cc
@@ -148,8 +148,8 @@
   views::Button::ButtonState current_state_ = views::Button::STATE_NORMAL;
 };
 
-RoundedScrollBar::RoundedScrollBar(bool horizontal)
-    : ScrollBar(horizontal),
+RoundedScrollBar::RoundedScrollBar(Orientation orientation)
+    : ScrollBar(orientation),
       thumb_(new Thumb(this)),  // Owned by views hierarchy.
       hide_scrollbar_timer_(
           FROM_HERE,
@@ -192,8 +192,9 @@
 
 int RoundedScrollBar::GetThickness() const {
   // Extend the thickness by the insets on the sides of the bar.
-  const int sides = IsHorizontal() ? insets_.top() + insets_.bottom()
-                                   : insets_.left() + insets_.right();
+  const int sides = GetOrientation() == Orientation::kHorizontal
+                        ? insets_.top() + insets_.bottom()
+                        : insets_.left() + insets_.right();
   return thumb_->GetThumbThickness() + sides;
 }
 
diff --git a/ash/controls/rounded_scroll_bar.h b/ash/controls/rounded_scroll_bar.h
index 76cf97c..61855389 100644
--- a/ash/controls/rounded_scroll_bar.h
+++ b/ash/controls/rounded_scroll_bar.h
@@ -27,7 +27,7 @@
   METADATA_HEADER(RoundedScrollBar, views::ScrollBar)
 
  public:
-  explicit RoundedScrollBar(bool horizontal);
+  explicit RoundedScrollBar(Orientation orientation);
   RoundedScrollBar(const RoundedScrollBar&) = delete;
   RoundedScrollBar& operator=(const RoundedScrollBar&) = delete;
   ~RoundedScrollBar() override;
diff --git a/ash/controls/rounded_scroll_bar_unittest.cc b/ash/controls/rounded_scroll_bar_unittest.cc
index 9fe8833..99baa35 100644
--- a/ash/controls/rounded_scroll_bar_unittest.cc
+++ b/ash/controls/rounded_scroll_bar_unittest.cc
@@ -77,8 +77,8 @@
 
     // Add a vertical scrollbar along the right edge.
     auto* contents = widget_->SetContentsView(std::make_unique<views::View>());
-    scroll_bar_ = contents->AddChildView(
-        std::make_unique<RoundedScrollBar>(/*horizontal=*/false));
+    scroll_bar_ = contents->AddChildView(std::make_unique<RoundedScrollBar>(
+        views::ScrollBar::Orientation::kVertical));
     scroll_bar_->set_controller(&controller_);
     scroll_bar_->SetBounds(90, 0, kScrollBarWidth, kViewportHeight);
     scroll_bar_->Update(kViewportHeight, kContentHeight,
diff --git a/ash/display/display_prefs.cc b/ash/display/display_prefs.cc
index fbb4399..5fd4e92 100644
--- a/ash/display/display_prefs.cc
+++ b/ash/display/display_prefs.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <string>
+#include <utility>
 
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
@@ -21,6 +22,7 @@
 #include "base/system/sys_info.h"
 #include "base/values.h"
 #include "components/metrics/structured/structured_events.h"
+#include "components/metrics/structured/structured_metrics_client.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -813,18 +815,19 @@
     int product_id;
     base::StringToInt(display.product_id(), &product_id);
 
-    metrics::structured::events::v2::popular_displays::MonitorInfo()
-        .SetDisplayName(display.name())
-        .SetManufacturerId(display.manufacturer_id())
-        .SetProductId(product_id)
-        .SetNativeModeSize(native_mode->size().ToString())
-        .SetNativeModeRefreshRate(native_mode->refresh_rate())
-        .SetPhysicalSize(display.physical_size().ToString())
-        .SetConnectionType(
-            display::DisplayConnectionTypeString(display.connection_type()))
-        .SetIsVrrCapable(display.variable_refresh_rate_state() <
-                         display::VariableRefreshRateState::kVrrNotCapable)
-        .Record();
+    metrics::structured::StructuredMetricsClient::Record(std::move(
+        metrics::structured::events::v2::popular_displays::MonitorInfo()
+            .SetDisplayName(display.name())
+            .SetManufacturerId(display.manufacturer_id())
+            .SetProductId(product_id)
+            .SetNativeModeSize(native_mode->size().ToString())
+            .SetNativeModeRefreshRate(native_mode->refresh_rate())
+            .SetPhysicalSize(display.physical_size().ToString())
+            .SetConnectionType(
+                display::DisplayConnectionTypeString(display.connection_type()))
+            .SetIsVrrCapable(
+                display.variable_refresh_rate_state() <
+                display::VariableRefreshRateState::kVrrNotCapable)));
 
     cached_list.Append(display_id);
   }
diff --git a/ash/login/ui/lock_debug_view.cc b/ash/login/ui/lock_debug_view.cc
index 8e01f3a..d9da359d 100644
--- a/ash/login/ui/lock_debug_view.cc
+++ b/ash/login/ui/lock_debug_view.cc
@@ -944,10 +944,10 @@
     scroll->SetPreferredSize(gfx::Size(600, height));
     scroll->SetContents(base::WrapUnique(content));
     scroll->SetBackgroundColor(std::nullopt);
-    scroll->SetVerticalScrollBar(
-        std::make_unique<views::OverlayScrollBar>(false));
-    scroll->SetHorizontalScrollBar(
-        std::make_unique<views::OverlayScrollBar>(true));
+    scroll->SetVerticalScrollBar(std::make_unique<views::OverlayScrollBar>(
+        views::ScrollBar::Orientation::kVertical));
+    scroll->SetHorizontalScrollBar(std::make_unique<views::OverlayScrollBar>(
+        views::ScrollBar::Orientation::kHorizontal));
     return scroll;
   };
   container_->AddChildView(make_scroll(global_action_view_container_, 110));
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index 42097eff..34f081f 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -223,10 +223,12 @@
   SetBackgroundColor(std::nullopt);
   SetDrawOverflowIndicator(false);
 
-  auto vertical_scroll = std::make_unique<RoundedScrollBar>(false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kVerticalScrollInsets);
   SetVerticalScrollBar(std::move(vertical_scroll));
-  SetHorizontalScrollBar(std::make_unique<RoundedScrollBar>(true));
+  SetHorizontalScrollBar(std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kHorizontal));
 
   observation_.Observe(Shell::Get()->wallpaper_controller());
 }
diff --git a/ash/picker/model/picker_category.h b/ash/picker/model/picker_category.h
index a83017ba..60fcfc1 100644
--- a/ash/picker/model/picker_category.h
+++ b/ash/picker/model/picker_category.h
@@ -15,6 +15,9 @@
   kSymbols,
   kEmoticons,
   kGifs,
+  kOpenTabs,
+  kBrowsingHistory,
+  kBookmarks,
 };
 
 }  // namespace ash
diff --git a/ash/picker/model/picker_model.cc b/ash/picker/model/picker_model.cc
index 3c2b03ae..0d753b77 100644
--- a/ash/picker/model/picker_model.cc
+++ b/ash/picker/model/picker_model.cc
@@ -10,10 +10,10 @@
 
 std::vector<PickerCategory> PickerModel::GetAvailableCategories() const {
   return std::vector<PickerCategory>{
-      PickerCategory::kEmojis,
-      PickerCategory::kSymbols,
-      PickerCategory::kEmoticons,
-      PickerCategory::kGifs,
+      PickerCategory::kEmojis,    PickerCategory::kSymbols,
+      PickerCategory::kEmoticons, PickerCategory::kGifs,
+      PickerCategory::kOpenTabs,  PickerCategory::kBrowsingHistory,
+      PickerCategory::kBookmarks,
   };
 }
 
diff --git a/ash/picker/model/picker_model_unittest.cc b/ash/picker/model/picker_model_unittest.cc
index 36d476f..8a8b45a 100644
--- a/ash/picker/model/picker_model_unittest.cc
+++ b/ash/picker/model/picker_model_unittest.cc
@@ -15,9 +15,12 @@
 
 TEST(PickerModel, AvailableCategories) {
   PickerModel model;
-  EXPECT_THAT(model.GetAvailableCategories(),
-              ElementsAre(PickerCategory::kEmojis, PickerCategory::kSymbols,
-                          PickerCategory::kEmoticons, PickerCategory::kGifs));
+  EXPECT_THAT(
+      model.GetAvailableCategories(),
+      ElementsAre(PickerCategory::kEmojis, PickerCategory::kSymbols,
+                  PickerCategory::kEmoticons, PickerCategory::kGifs,
+                  PickerCategory::kOpenTabs, PickerCategory::kBrowsingHistory,
+                  PickerCategory::kBookmarks));
 }
 
 }  // namespace
diff --git a/ash/picker/views/picker_category_type.cc b/ash/picker/views/picker_category_type.cc
index a080a85..3357f48 100644
--- a/ash/picker/views/picker_category_type.cc
+++ b/ash/picker/views/picker_category_type.cc
@@ -15,6 +15,10 @@
     case PickerCategory::kEmoticons:
     case PickerCategory::kGifs:
       return PickerCategoryType::kExpressions;
+    case PickerCategory::kOpenTabs:
+    case PickerCategory::kBrowsingHistory:
+    case PickerCategory::kBookmarks:
+      return PickerCategoryType::kLinks;
   }
 }
 
diff --git a/ash/picker/views/picker_category_type.h b/ash/picker/views/picker_category_type.h
index b69e848d..b08fc1a 100644
--- a/ash/picker/views/picker_category_type.h
+++ b/ash/picker/views/picker_category_type.h
@@ -13,6 +13,7 @@
 // Used to group related categories together.
 enum class ASH_EXPORT PickerCategoryType {
   kExpressions,
+  kLinks,
 };
 
 ASH_EXPORT PickerCategoryType GetPickerCategoryType(PickerCategory category);
diff --git a/ash/picker/views/picker_contents_view.cc b/ash/picker/views/picker_contents_view.cc
index 29bb3e0..fedf580 100644
--- a/ash/picker/views/picker_contents_view.cc
+++ b/ash/picker/views/picker_contents_view.cc
@@ -80,8 +80,8 @@
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
   auto* scroll_view = AddChildView(std::make_unique<PickerScrollView>());
-  auto vertical_scroll_bar =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll_bar = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll_bar->SetInsets(GetPickerScrollBarInsets(layout_type));
   scroll_view->SetVerticalScrollBar(std::move(vertical_scroll_bar));
 
diff --git a/ash/picker/views/picker_icons.cc b/ash/picker/views/picker_icons.cc
index aaa2d95..0e3f09f6 100644
--- a/ash/picker/views/picker_icons.cc
+++ b/ash/picker/views/picker_icons.cc
@@ -22,6 +22,12 @@
       return kPickerEmoticonIcon;
     case PickerCategory::kGifs:
       return kPickerGifIcon;
+    case PickerCategory::kOpenTabs:
+      return kPickerOpenTabIcon;
+    case PickerCategory::kBrowsingHistory:
+      return kPickerBrowsingHistoryIcon;
+    case PickerCategory::kBookmarks:
+      return kPickerBookmarkIcon;
   }
 }
 
diff --git a/ash/picker/views/picker_search_field_view.cc b/ash/picker/views/picker_search_field_view.cc
index 03a5ec6..165b70c 100644
--- a/ash/picker/views/picker_search_field_view.cc
+++ b/ash/picker/views/picker_search_field_view.cc
@@ -34,22 +34,22 @@
     PickerSessionMetrics* session_metrics)
     : search_callback_(std::move(search_callback)),
       session_metrics_(session_metrics) {
-  SetLayoutManager(std::make_unique<views::FillLayout>());
-
-  textfield_ = AddChildView(
-      views::Builder<views::Textfield>()
-          .SetController(this)
-          .SetBorder(views::CreateEmptyBorder(kSearchFieldBorderInsets))
-          .SetBackgroundColor(SK_ColorTRANSPARENT)
-          .SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
-              TypographyToken::kCrosBody2))
-          .SetPlaceholderText(l10n_util::GetStringUTF16(
-              IDS_PICKER_ZERO_STATE_SEARCH_FIELD_PLACEHOLDER_TEXT))
-          // TODO(b/309706053): Replace this once the strings are finalized.
-          .SetAccessibleName(u"placeholder")
-          .Build());
-
-  SetProperty(views::kMarginsKey, kSearchFieldVerticalPadding);
+  views::Builder<PickerSearchFieldView>(this)
+      .SetUseDefaultFillLayout(true)
+      .SetProperty(views::kMarginsKey, kSearchFieldVerticalPadding)
+      .AddChild(
+          views::Builder<views::Textfield>()
+              .CopyAddressTo(&textfield_)
+              .SetController(this)
+              .SetBorder(views::CreateEmptyBorder(kSearchFieldBorderInsets))
+              .SetBackgroundColor(SK_ColorTRANSPARENT)
+              .SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
+                  TypographyToken::kCrosBody2))
+              .SetPlaceholderText(l10n_util::GetStringUTF16(
+                  IDS_PICKER_ZERO_STATE_SEARCH_FIELD_PLACEHOLDER_TEXT))
+              // TODO(b/309706053): Replace this once the strings are finalized.
+              .SetAccessibleName(u"placeholder"))
+      .BuildChildren();
 }
 
 PickerSearchFieldView::~PickerSearchFieldView() = default;
diff --git a/ash/picker/views/picker_search_field_view.h b/ash/picker/views/picker_search_field_view.h
index e4e4c9d..f4a6e66 100644
--- a/ash/picker/views/picker_search_field_view.h
+++ b/ash/picker/views/picker_search_field_view.h
@@ -12,6 +12,7 @@
 #include "base/functional/callback_forward.h"
 #include "ui/views/controls/textfield/textfield_controller.h"
 #include "ui/views/focus/focus_manager.h"
+#include "ui/views/metadata/view_factory.h"
 #include "ui/views/view.h"
 
 namespace views {
@@ -64,6 +65,11 @@
   raw_ptr<views::Textfield> textfield_ = nullptr;
 };
 
+BEGIN_VIEW_BUILDER(ASH_EXPORT, PickerSearchFieldView, views::View)
+END_VIEW_BUILDER
+
 }  // namespace ash
 
+DEFINE_VIEW_BUILDER(ASH_EXPORT, ash::PickerSearchFieldView)
+
 #endif  // ASH_PICKER_VIEWS_PICKER_SEARCH_FIELD_VIEW_H_
diff --git a/ash/picker/views/picker_strings.cc b/ash/picker/views/picker_strings.cc
index 125dec03..42c19b1 100644
--- a/ash/picker/views/picker_strings.cc
+++ b/ash/picker/views/picker_strings.cc
@@ -21,6 +21,13 @@
       return l10n_util::GetStringUTF16(IDS_PICKER_EMOTICONS_CATEGORY_LABEL);
     case PickerCategory::kGifs:
       return l10n_util::GetStringUTF16(IDS_PICKER_GIFS_CATEGORY_LABEL);
+    case PickerCategory::kOpenTabs:
+      return l10n_util::GetStringUTF16(IDS_PICKER_OPEN_TABS_CATEGORY_LABEL);
+    case PickerCategory::kBrowsingHistory:
+      return l10n_util::GetStringUTF16(
+          IDS_PICKER_BROWSING_HISTORY_CATEGORY_LABEL);
+    case PickerCategory::kBookmarks:
+      return l10n_util::GetStringUTF16(IDS_PICKER_BOOKMARKS_CATEGORY_LABEL);
   }
 }
 
@@ -39,6 +46,15 @@
     case PickerCategory::kGifs:
       return l10n_util::GetStringUTF16(
           IDS_PICKER_GIFS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT);
+    case PickerCategory::kOpenTabs:
+      return l10n_util::GetStringUTF16(
+          IDS_PICKER_OPEN_TABS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT);
+    case PickerCategory::kBrowsingHistory:
+      return l10n_util::GetStringUTF16(
+          IDS_PICKER_BROWSING_HISTORY_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT);
+    case PickerCategory::kBookmarks:
+      return l10n_util::GetStringUTF16(
+          IDS_PICKER_BOOKMARKS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT);
   }
 }
 
@@ -48,6 +64,9 @@
     case PickerCategoryType::kExpressions:
       return l10n_util::GetStringUTF16(
           IDS_PICKER_EXPRESSIONS_CATEGORY_TYPE_SECTION_TITLE);
+    case PickerCategoryType::kLinks:
+      return l10n_util::GetStringUTF16(
+          IDS_PICKER_LINKS_CATEGORY_TYPE_SECTION_TITLE);
   }
 }
 
diff --git a/ash/picker/views/picker_view.h b/ash/picker/views/picker_view.h
index d7dd66d..98c5660 100644
--- a/ash/picker/views/picker_view.h
+++ b/ash/picker/views/picker_view.h
@@ -84,6 +84,7 @@
   PickerSearchFieldView& search_field_view_for_testing() {
     return *search_field_view_;
   }
+  PickerContentsView& contents_view_for_testing() { return *contents_view_; }
   PickerSearchResultsView& search_results_view_for_testing() {
     return *search_results_view_;
   }
diff --git a/ash/picker/views/picker_view_unittest.cc b/ash/picker/views/picker_view_unittest.cc
index 84556376..8f2bf49 100644
--- a/ash/picker/views/picker_view_unittest.cc
+++ b/ash/picker/views/picker_view_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/picker/model/picker_search_results.h"
 #include "ash/picker/views/picker_category_type.h"
 #include "ash/picker/views/picker_category_view.h"
+#include "ash/picker/views/picker_contents_view.h"
 #include "ash/picker/views/picker_search_field_view.h"
 #include "ash/picker/views/picker_search_results_view.h"
 #include "ash/picker/views/picker_section_view.h"
@@ -555,7 +556,7 @@
   widget->Show();
 
   PickerView* view = GetPickerViewFromWidget(*widget);
-  EXPECT_GE(view->zero_state_view_for_testing().GetBoundsInScreen().y(),
+  EXPECT_GE(view->contents_view_for_testing().GetBoundsInScreen().y(),
             view->search_field_view_for_testing().GetBoundsInScreen().bottom());
 }
 
@@ -571,7 +572,7 @@
   widget->Show();
 
   PickerView* view = GetPickerViewFromWidget(*widget);
-  EXPECT_LE(view->zero_state_view_for_testing().GetBoundsInScreen().bottom(),
+  EXPECT_LE(view->contents_view_for_testing().GetBoundsInScreen().bottom(),
             view->search_field_view_for_testing().GetBoundsInScreen().y());
 }
 
diff --git a/ash/picker/views/picker_zero_state_view_unittest.cc b/ash/picker/views/picker_zero_state_view_unittest.cc
index 714b781..0a4d5b6 100644
--- a/ash/picker/views/picker_zero_state_view_unittest.cc
+++ b/ash/picker/views/picker_zero_state_view_unittest.cc
@@ -22,6 +22,7 @@
 namespace {
 
 using ::testing::Contains;
+using ::testing::ElementsAre;
 using ::testing::IsEmpty;
 using ::testing::Key;
 using ::testing::Not;
@@ -30,11 +31,12 @@
 
 using PickerZeroStateViewTest = AshTestBase;
 
-TEST_F(PickerZeroStateViewTest, CreatesExpressionsSection) {
+TEST_F(PickerZeroStateViewTest, CreatesCategorySections) {
   PickerZeroStateView view(kPickerWidth, base::DoNothing());
 
   EXPECT_THAT(view.section_views_for_testing(),
-              Contains(Key(PickerCategoryType::kExpressions)));
+              ElementsAre(Key(PickerCategoryType::kExpressions),
+                          Key(PickerCategoryType::kLinks)));
 }
 
 TEST_F(PickerZeroStateViewTest, LeftClickSelectsCategory) {
diff --git a/ash/public/cpp/picker/picker_client.cc b/ash/public/cpp/picker/picker_client.cc
index 6b0f18f9a..ee933a07 100644
--- a/ash/public/cpp/picker/picker_client.cc
+++ b/ash/public/cpp/picker/picker_client.cc
@@ -16,6 +16,10 @@
   return std::nullopt;
 }
 
+ValidGifUrl ValidGifUrl::CreateForTesting(const GURL& url) {
+  return ValidGifUrl(url);
+}
+
 ValidGifUrl::~ValidGifUrl() = default;
 
 GURL ValidGifUrl::ToGURL() const {
diff --git a/ash/public/cpp/picker/picker_client.h b/ash/public/cpp/picker/picker_client.h
index 0a4edd3..00f1b16 100644
--- a/ash/public/cpp/picker/picker_client.h
+++ b/ash/public/cpp/picker/picker_client.h
@@ -23,6 +23,9 @@
  public:
   static std::optional<ValidGifUrl> Create(const GURL& url);
 
+  // Creates an instance bypassing the validation checks.
+  static ValidGifUrl CreateForTesting(const GURL& url);
+
   ~ValidGifUrl();
 
   GURL ToGURL() const;
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 6be78e9..ea6b623 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -315,9 +315,12 @@
     "phone_hub_mobile_no_sim.icon",
     "phone_hub_phone.icon",
     "phone_hub_silence_phone.icon",
+    "picker_bookmark.icon",
+    "picker_browsing_history.icon",
     "picker_emoji.icon",
     "picker_emoticon.icon",
     "picker_gif.icon",
+    "picker_open_tab.icon",
     "picker_symbol.icon",
     "pin_request_lock.icon",
     "pinned.icon",
diff --git a/ash/resources/vector_icons/picker_bookmark.icon b/ash/resources/vector_icons/picker_bookmark.icon
new file mode 100644
index 0000000..059defc
--- /dev/null
+++ b/ash/resources/vector_icons/picker_bookmark.icon
@@ -0,0 +1,29 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 7.43f, 13.68f,
+LINE_TO, 10, 12.16f,
+R_LINE_TO, 2.59f, 1.52f,
+R_LINE_TO, -0.69f, -2.89f,
+R_LINE_TO, 2.23f, -1.91f,
+R_LINE_TO, -2.95f, -0.26f,
+LINE_TO, 10, 5.87f,
+LINE_TO, 8.82f, 8.62f,
+R_LINE_TO, -2.95f, 0.25f,
+R_LINE_TO, 2.25f, 1.91f,
+R_LINE_TO, -0.69f, 2.89f,
+CLOSE,
+R_MOVE_TO, -2.62f, 3.58f,
+R_LINE_TO, 1.38f, -5.82f,
+LINE_TO, 1.6f, 7.52f,
+LINE_TO, 7.64f, 7,
+LINE_TO, 10, 1.5f,
+R_LINE_TO, 2.36f, 5.52f,
+R_LINE_TO, 6.04f, 0.5f,
+R_LINE_TO, -4.6f, 3.93f,
+R_LINE_TO, 1.38f, 5.82f,
+R_LINE_TO, -5.19f, -3.09f,
+R_LINE_TO, -5.19f, 3.09f,
+CLOSE
diff --git a/ash/resources/vector_icons/picker_browsing_history.icon b/ash/resources/vector_icons/picker_browsing_history.icon
new file mode 100644
index 0000000..e940aaa
--- /dev/null
+++ b/ash/resources/vector_icons/picker_browsing_history.icon
@@ -0,0 +1,43 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 10, 17.17f,
+R_CUBIC_TO, -2, 0, -3.7f, -0.69f, -5.1f, -2.08f,
+CUBIC_TO, 3.51f, 13.69f, 2.82f, 12, 2.83f, 10,
+R_H_LINE_TO, 1.71f,
+R_CUBIC_TO, 0.01f, 1.5f, 0.55f, 2.79f, 1.6f, 3.85f,
+CUBIC_TO, 7.22f, 14.91f, 8.5f, 15.44f, 10, 15.44f,
+R_CUBIC_TO, 1.49f, 0, 2.76f, -0.53f, 3.83f, -1.58f,
+R_CUBIC_TO, 1.07f, -1.07f, 1.6f, -2.35f, 1.6f, -3.85f,
+R_CUBIC_TO, 0, -1.5f, -0.53f, -2.78f, -1.6f, -3.83f,
+R_CUBIC_TO, -1.07f, -1.07f, -2.35f, -1.6f, -3.83f, -1.6f,
+R_CUBIC_TO, -0.83f, 0, -1.6f, 0.17f, -2.31f, 0.5f,
+R_ARC_TO, 4.99f, 4.99f, 0, 0, 0, -1.77f, 1.35f,
+H_LINE_TO, 8,
+V_LINE_TO, 8,
+H_LINE_TO, 2.88f,
+V_LINE_TO, 2.88f,
+R_H_LINE_TO, 1.56f,
+V_LINE_TO, 5.5f,
+R_ARC_TO, 7.28f, 7.28f, 0, 0, 1, 2.42f, -1.94f,
+R_CUBIC_TO, 0.96f, -0.49f, 2.01f, -0.73f, 3.15f, -0.73f,
+R_CUBIC_TO, 0.99f, 0, 1.91f, 0.19f, 2.77f, 0.56f,
+R_ARC_TO, 7, 7, 0, 0, 1, 2.27f, 1.54f,
+R_ARC_TO, 7, 7, 0, 0, 1, 1.54f, 2.27f,
+ARC_TO, 6.78f, 6.78f, 0, 0, 1, 17.17f, 10,
+R_CUBIC_TO, 0, 0.99f, -0.19f, 1.92f, -0.58f, 2.79f,
+R_ARC_TO, 7.24f, 7.24f, 0, 0, 1, -1.54f, 2.29f,
+R_ARC_TO, 7.17f, 7.17f, 0, 0, 1, -2.27f, 1.52f,
+R_ARC_TO, 6.86f, 6.86f, 0, 0, 1, -2.77f, 0.56f,
+CLOSE,
+R_MOVE_TO, 2.04f, -4.29f,
+R_LINE_TO, -2.83f, -2.83f,
+V_LINE_TO, 6,
+R_H_LINE_TO, 1.58f,
+R_V_LINE_TO, 3.38f,
+R_LINE_TO, 2.38f, 2.38f,
+R_LINE_TO, -1.12f, 1.13f,
+CLOSE,
+NEW_PATH
diff --git a/ash/resources/vector_icons/picker_open_tab.icon b/ash/resources/vector_icons/picker_open_tab.icon
new file mode 100644
index 0000000..de435c7f
--- /dev/null
+++ b/ash/resources/vector_icons/picker_open_tab.icon
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 3.56f, 14.44f,
+R_H_LINE_TO, 12.87f,
+V_LINE_TO, 9,
+H_LINE_TO, 11,
+V_LINE_TO, 5.56f,
+H_LINE_TO, 3.56f,
+R_V_LINE_TO, 8.88f,
+CLOSE,
+R_MOVE_TO, 0, 1.73f,
+R_CUBIC_TO, -0.49f, 0, -0.9f, -0.17f, -1.23f, -0.5f,
+R_ARC_TO, 1.67f, 1.67f, 0, 0, 1, -0.5f, -1.23f,
+V_LINE_TO, 5.56f,
+R_CUBIC_TO, 0, -0.49f, 0.17f, -0.9f, 0.5f, -1.23f,
+R_ARC_TO, 1.67f, 1.67f, 0, 0, 1, 1.23f, -0.5f,
+R_H_LINE_TO, 12.87f,
+R_CUBIC_TO, 0.49f, 0, 0.9f, 0.17f, 1.23f, 0.5f,
+R_CUBIC_TO, 0.33f, 0.33f, 0.5f, 0.74f, 0.5f, 1.23f,
+R_V_LINE_TO, 8.88f,
+R_CUBIC_TO, 0, 0.49f, -0.17f, 0.9f, -0.5f, 1.23f,
+R_CUBIC_TO, -0.33f, 0.33f, -0.74f, 0.5f, -1.23f, 0.5f,
+H_LINE_TO, 3.56f,
+CLOSE,
+R_MOVE_TO, 0, -1.73f,
+V_LINE_TO, 5.56f,
+R_V_LINE_TO, 8.88f,
+CLOSE,
+NEW_PATH
diff --git a/ash/system/notification_center/views/message_center_scroll_bar.cc b/ash/system/notification_center/views/message_center_scroll_bar.cc
index b7b5e6ec..b2a93b5 100644
--- a/ash/system/notification_center/views/message_center_scroll_bar.cc
+++ b/ash/system/notification_center/views/message_center_scroll_bar.cc
@@ -46,7 +46,7 @@
 END_METADATA
 
 MessageCenterScrollBar::MessageCenterScrollBar()
-    : RoundedScrollBar(/*horizontal=*/false) {
+    : RoundedScrollBar(views::ScrollBar::Orientation::kVertical) {
   GetThumb()->layer()->SetVisible(features::IsNotificationScrollBarEnabled());
   GetThumb()->layer()->CompleteAllAnimations();
 }
diff --git a/ash/system/notification_center/views/notifier_settings_view.cc b/ash/system/notification_center/views/notifier_settings_view.cc
index cb5ddb6..31997d6 100644
--- a/ash/system/notification_center/views/notifier_settings_view.cc
+++ b/ash/system/notification_center/views/notifier_settings_view.cc
@@ -661,7 +661,8 @@
     auto scroller = std::make_unique<views::ScrollView>();
     scroller->SetBackgroundColor(std::nullopt);
     scroll_bar_ = scroller->SetVerticalScrollBar(
-        std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
+        std::make_unique<views::OverlayScrollBar>(
+            views::ScrollBar::Orientation::kVertical));
     scroller->SetDrawOverflowIndicator(false);
     scroller_ = AddChildView(std::move(scroller));
 
diff --git a/ash/system/phonehub/app_stream_launcher_view.cc b/ash/system/phonehub/app_stream_launcher_view.cc
index 021091e..6b1f279 100644
--- a/ash/system/phonehub/app_stream_launcher_view.cc
+++ b/ash/system/phonehub/app_stream_launcher_view.cc
@@ -153,8 +153,8 @@
   // Set up scroll bars.
   scroll_view->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
-  auto vertical_scroll =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kVerticalScrollInsets);
   vertical_scroll->SetSnapBackOnDragOutside(false);
   scroll_view->SetVerticalScrollBar(std::move(vertical_scroll));
diff --git a/ash/system/tray/tray_detailed_view.cc b/ash/system/tray/tray_detailed_view.cc
index 2a83854..49e476e 100644
--- a/ash/system/tray/tray_detailed_view.cc
+++ b/ash/system/tray/tray_detailed_view.cc
@@ -172,7 +172,7 @@
   scroll_content_ = scroller_->SetContents(std::move(scroll_content));
 
   auto vertical_scroll = std::make_unique<RoundedScrollBar>(
-      /*horizontal=*/false);
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kScrollBarInsets);
   scroller_->SetVerticalScrollBar(std::move(vertical_scroll));
   scroller_->SetProperty(views::kMarginsKey, delegate_->GetScrollViewMargin());
diff --git a/ash/webui/firmware_update_ui/firmware_update_app_ui.cc b/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
index a3634be6..a6abe6d 100644
--- a/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
+++ b/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
@@ -38,8 +38,6 @@
   source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
   source->AddResourcePath("test_loader_util.js",
                           IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
-  source->AddBoolean("isJellyEnabledForFirmwareUpdate",
-                     ash::features::IsJellyEnabledForFirmwareUpdate());
   source->AddBoolean("isFirmwareUpdateUIV2Enabled",
                      ash::features::IsFirmwareUpdateUIV2Enabled());
 }
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_app.ts b/ash/webui/firmware_update_ui/resources/firmware_update_app.ts
index 71ac3e4..435b735 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_app.ts
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_app.ts
@@ -9,7 +9,6 @@
 import './peripheral_updates_list.js';
 import './strings.m.js';
 
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -35,22 +34,11 @@
 
   override connectedCallback(): void {
     super.connectedCallback();
-    if (loadTimeData.getBoolean('isJellyEnabledForFirmwareUpdate')) {
-      // TODO(b/276493795): After the Jelly experiment is launched, replace
-      // `cros_styles.css` with `theme/colors.css` directly in `index.html`.
-      // Also add `theme/typography.css` to `index.html`.
-      document.querySelector('link[href*=\'cros_styles.css\']')
-          ?.setAttribute('href', 'chrome://theme/colors.css?sets=legacy,sys');
-      const typographyLink = document.createElement('link');
-      typographyLink.href = 'chrome://theme/typography.css';
-      typographyLink.rel = 'stylesheet';
-      document.head.appendChild(typographyLink);
-      document.body.classList.add('jelly-enabled');
-      /** @suppress {checkTypes} */
-      (function() {
-        ColorChangeUpdater.forDocument().start();
-      })();
-    }
+
+    /** @suppress {checkTypes} */
+    (function() {
+      ColorChangeUpdater.forDocument().start();
+    })();
   }
 }
 
diff --git a/ash/webui/firmware_update_ui/resources/index.html b/ash/webui/firmware_update_ui/resources/index.html
index c1ed4f2b..f508194 100644
--- a/ash/webui/firmware_update_ui/resources/index.html
+++ b/ash/webui/firmware_update_ui/resources/index.html
@@ -9,7 +9,8 @@
   <title></title>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
-  <link rel="stylesheet" href="chrome://resources/chromeos/colors/cros_styles.css">
+  <link rel="stylesheet" href="chrome://theme/colors.css?sets=legacy,sys">
+  <link rel="stylesheet" href="chrome://theme/typography.css">
   <style>
     body {
       background-color: var(--cros-bg-color);
@@ -27,7 +28,7 @@
     }
   </style>
 </head>
-<body>
+<body class="jelly-enabled">
     <firmware-update-app></firmware-update-app>
 
     <script type="module" src="firmware_update_app.js"></script>
diff --git a/ash/webui/print_management/print_management_ui.cc b/ash/webui/print_management/print_management_ui.cc
index 2ee5f57..9b18721 100644
--- a/ash/webui/print_management/print_management_ui.cc
+++ b/ash/webui/print_management/print_management_ui.cc
@@ -42,8 +42,6 @@
   source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
   source->AddResourcePath("test_loader_util.js",
                           IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
-  source->AddBoolean("isJellyEnabledForPrintManagement",
-                     ash::features::IsJellyEnabledForPrintManagement());
   source->AddBoolean("isSetupAssistanceEnabled",
                      base::FeatureList::IsEnabled(
                          ash::features::kPrintManagementSetupAssistance));
diff --git a/ash/webui/print_management/resources/index.html b/ash/webui/print_management/resources/index.html
index 7916b02..b4703ce 100644
--- a/ash/webui/print_management/resources/index.html
+++ b/ash/webui/print_management/resources/index.html
@@ -8,7 +8,9 @@
     <meta name="color-scheme" content="light dark">
     <title>$i18n{printJobTitle}</title>
     <link rel="stylesheet"
-        href="chrome://resources/chromeos/colors/cros_styles.css">
+        href="chrome://theme/colors.css?sets=legacy,sys">
+    <link rel="stylesheet"
+        href="chrome://theme/typography.css">
     <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
     <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
     <style>
@@ -27,7 +29,7 @@
       }
     </style>
   </head>
-  <body>
+  <body class="jelly-enabled">
     <print-management></print-management>
     <script type="module" src="print_management.js"></script>
     <script type="module" src="mojo_interface_provider.js"></script>
diff --git a/ash/webui/print_management/resources/print_management.ts b/ash/webui/print_management/resources/print_management.ts
index c56c633a..01a6253 100644
--- a/ash/webui/print_management/resources/print_management.ts
+++ b/ash/webui/print_management/resources/print_management.ts
@@ -185,19 +185,7 @@
     this.startObservingPrintJobs();
     this.fetchDeletePrintJobHistoryPolicy();
 
-    if (loadTimeData.getBoolean('isJellyEnabledForPrintManagement')) {
-      // TODO(b/276493795): After the Jelly experiment is launched, replace
-      // `cros_styles.css` with `theme/colors.css` directly in `index.html`.
-      // Also add `theme/typography.css` to `index.html`.
-      document.querySelector('link[href*=\'cros_styles.css\']')
-          ?.setAttribute('href', 'chrome://theme/colors.css?sets=legacy,sys');
-      const typographyLink = document.createElement('link');
-      typographyLink.href = 'chrome://theme/typography.css';
-      typographyLink.rel = 'stylesheet';
-      document.head.appendChild(typographyLink);
-      document.body.classList.add('jelly-enabled');
-      ColorChangeUpdater.forDocument().start();
-    }
+    ColorChangeUpdater.forDocument().start();
   }
 
   override disconnectedCallback(): void {
diff --git a/ash/wm/desks/desk_bar_view_base.cc b/ash/wm/desks/desk_bar_view_base.cc
index b74135a..24cd83ac 100644
--- a/ash/wm/desks/desk_bar_view_base.cc
+++ b/ash/wm/desks/desk_bar_view_base.cc
@@ -538,7 +538,7 @@
   new_desk_button_label_->SetPaintToLayer();
   new_desk_button_label_->layer()->SetFillsBoundsOpaquely(false);
 
-  if (saved_desk_util::IsSavedDesksEnabled()) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons()) {
     int button_text_id = IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_LIBRARY;
     if (!saved_desk_util::AreDesksTemplatesEnabled()) {
       button_text_id = IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_SAVED_FOR_LATER;
@@ -882,7 +882,7 @@
 }
 
 void DeskBarViewBase::UpdateButtonsForSavedDeskGrid() {
-  if (IsZeroState() || !saved_desk_util::IsSavedDesksEnabled()) {
+  if (IsZeroState() || !saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
   }
 
@@ -906,7 +906,7 @@
 }
 
 void DeskBarViewBase::UpdateLibraryButtonVisibility() {
-  if (!saved_desk_util::IsSavedDesksEnabled()) {
+  if (!saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
   }
 
@@ -987,7 +987,7 @@
 bool DeskBarViewBase::ShouldShowLibraryUi() {
   // Only update visibility when needed. This will save a lot of repeated work.
   if (library_ui_visibility_ == LibraryUiVisibility::kToBeChecked) {
-    if (!saved_desk_util::IsSavedDesksEnabled() ||
+    if (!saved_desk_util::ShouldShowSavedDesksButtons() ||
         display::Screen::GetScreen()->InTabletMode()) {
       library_ui_visibility_ = LibraryUiVisibility::kHidden;
     } else {
diff --git a/ash/wm/desks/desks_util.cc b/ash/wm/desks/desks_util.cc
index 7370256..17a68f0 100644
--- a/ash/wm/desks/desks_util.cc
+++ b/ash/wm/desks/desks_util.cc
@@ -184,8 +184,13 @@
 }
 
 bool ShouldDesksBarBeCreated() {
-  return !display::Screen::GetScreen()->InTabletMode() ||
-         DesksController::Get()->desks().size() > 1;
+  if (display::Screen::GetScreen()->InTabletMode()) {
+    return DesksController::Get()->desks().size() > 1;
+  }
+
+  // If in clamshell mode, and overview was started by faster splitscreen setup,
+  // don't show the desk bar.
+  return !window_util::IsInFasterSplitScreenSetupSession();
 }
 
 ui::Compositor* GetSelectedCompositorForPerformanceMetrics() {
diff --git a/ash/wm/desks/templates/saved_desk_library_view.cc b/ash/wm/desks/templates/saved_desk_library_view.cc
index b1457a8..1bfbbf2 100644
--- a/ash/wm/desks/templates/saved_desk_library_view.cc
+++ b/ash/wm/desks/templates/saved_desk_library_view.cc
@@ -262,8 +262,8 @@
   scroll_view_->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
   // Use ash style rounded scroll bar just like `AppListBubbleAppsPage`.
-  auto vertical_scroll =
-      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  auto vertical_scroll = std::make_unique<RoundedScrollBar>(
+      views::ScrollBar::Orientation::kVertical);
   vertical_scroll->SetInsets(kLibraryPageVerticalScrollInsets);
   vertical_scroll->SetSnapBackOnDragOutside(false);
   scroll_view_->SetVerticalScrollBar(std::move(vertical_scroll));
@@ -291,7 +291,7 @@
 
     scroll_contents->AddChildView(std::move(group_contents));
   }
-  if (saved_desk_util::IsSavedDesksEnabled()) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons()) {
     auto group_contents = GetLabelAndGridGroupContents();
     save_and_recall_grid_view_ =
         group_contents->AddChildView(std::make_unique<SavedDeskGridView>());
diff --git a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
index 7158220f..b4bff89 100644
--- a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
+++ b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
@@ -159,7 +159,7 @@
             &kSaveDeskAsTemplateIcon));
   }
 
-  if (saved_desk_util::IsSavedDesksEnabled()) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons()) {
     save_desk_for_later_button_ =
         AddChildView(std::make_unique<SavedDeskSaveDeskButton>(
             save_for_later_callback,
diff --git a/ash/wm/desks/templates/saved_desk_util.cc b/ash/wm/desks/templates/saved_desk_util.cc
index d8bc2b9..568e834 100644
--- a/ash/wm/desks/templates/saved_desk_util.cc
+++ b/ash/wm/desks/templates/saved_desk_util.cc
@@ -13,6 +13,7 @@
 #include "ash/wm/desks/templates/saved_desk_constants.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_session.h"
+#include "ash/wm/window_util.h"
 #include "base/containers/adapters.h"
 #include "components/app_restore/full_restore_utils.h"
 #include "components/app_restore/window_properties.h"
@@ -80,9 +81,10 @@
   return features::AreDesksTemplatesEnabled();
 }
 
-bool IsSavedDesksEnabled() {
-  return !IsGuestSession();
+bool ShouldShowSavedDesksButtons() {
+  return !IsGuestSession() && !window_util::IsInFasterSplitScreenSetupSession();
 }
+
 SavedDeskDialogController* GetSavedDeskDialogController() {
   auto* overview_controller = Shell::Get()->overview_controller();
   if (!overview_controller->InOverviewSession())
diff --git a/ash/wm/desks/templates/saved_desk_util.h b/ash/wm/desks/templates/saved_desk_util.h
index cdb5c2a..7759699a 100644
--- a/ash/wm/desks/templates/saved_desk_util.h
+++ b/ash/wm/desks/templates/saved_desk_util.h
@@ -28,7 +28,7 @@
 
 ASH_EXPORT bool AreDesksTemplatesEnabled();
 
-ASH_EXPORT bool IsSavedDesksEnabled();
+ASH_EXPORT bool ShouldShowSavedDesksButtons();
 
 // Will return null if overview mode is not active.
 ASH_EXPORT SavedDeskDialogController* GetSavedDeskDialogController();
diff --git a/ash/wm/overview/overview_focus_cycler.cc b/ash/wm/overview/overview_focus_cycler.cc
index 03213c1e..9695f655 100644
--- a/ash/wm/overview/overview_focus_cycler.cc
+++ b/ash/wm/overview/overview_focus_cycler.cc
@@ -8,6 +8,7 @@
 #include "ash/accessibility/magnifier/magnifier_utils.h"
 #include "ash/accessibility/scoped_a11y_override_window_setter.h"
 #include "ash/shell.h"
+#include "ash/style/icon_button.h"
 #include "ash/wm/desks/desk_icon_button.h"
 #include "ash/wm/desks/desk_mini_view.h"
 #include "ash/wm/desks/desk_name_view.h"
@@ -25,6 +26,7 @@
 #include "ash/wm/overview/overview_item_view.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/splitview/faster_split_view.h"
 #include "base/containers/contains.h"
 #include "base/ranges/algorithm.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -274,6 +276,10 @@
     if (grid->IsSaveDeskForLaterButtonVisible()) {
       traversable_views.push_back(grid->GetSaveDeskForLaterButton());
     }
+    if (auto* faster_split_view = grid->GetFasterSplitView()) {
+      traversable_views.push_back(faster_split_view->toast());
+      traversable_views.push_back(faster_split_view->settings_button());
+    }
   }
   return traversable_views;
 }
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index fbab0fb..eef661a 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -24,8 +24,6 @@
 #include "ash/shell_delegate.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/icon_button.h"
-#include "ash/style/system_toast_style.h"
 #include "ash/system/toast/toast_manager_impl.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/desks/default_desk_button.h"
@@ -61,6 +59,7 @@
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/overview/overview_window_drag_controller.h"
 #include "ash/wm/overview/scoped_overview_animation_settings.h"
+#include "ash/wm/splitview/faster_split_view.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_divider.h"
@@ -122,10 +121,6 @@
 // splitscreen toast widget.
 constexpr int kFasterSplitScreenToastSpacingDp = 40;
 
-// Distance from the right of the faster splitscreen toast to the left of the
-// settings button.
-constexpr int kSettingsButtonSpacingDp = 8;
-
 // Windows are not allowed to get taller than this.
 constexpr int kMaxHeight = 512;
 
@@ -815,6 +810,8 @@
   }
 
   UpdateSaveDeskButtons();
+  // Needed to include the toast when we init the grid.
+  UpdateFasterSplitViewWidget();
 
   // This is a no-op if the feature ContinuousOverviewScrollAnimation is not
   // enabled. Once windows are placed at their final positions, clear transforms
@@ -2098,8 +2095,9 @@
   // overview grid changes, i.e. switches between active desks and/or the
   // saved desk grid. This will be needed when we make it so that switching
   // desks keeps us in overview mode.
-  if (!saved_desk_util::IsSavedDesksEnabled())
+  if (!saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
+  }
 
   // If there is only one item and it is animating to close, hide the widget as
   // the closing window cannot be saved as part of a template.
@@ -2306,6 +2304,13 @@
              : nullptr;
 }
 
+FasterSplitView* OverviewGrid::GetFasterSplitView() {
+  return faster_splitview_widget_
+             ? views::AsViewClass<FasterSplitView>(
+                   faster_splitview_widget_->GetContentsView())
+             : nullptr;
+}
+
 void OverviewGrid::OnSplitViewStateChanged(
     SplitViewController::State previous_state,
     SplitViewController::State state) {
@@ -2824,8 +2829,9 @@
 void OverviewGrid::UpdateNumSavedDeskUnsupportedWindows(
     const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows,
     bool increment) {
-  if (!saved_desk_util::IsSavedDesksEnabled())
+  if (!saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
+  }
 
   int addend = increment ? 1 : -1;
 
@@ -2926,33 +2932,12 @@
     params.init_properties_container.SetProperty(kOverviewUiKey, true);
     faster_splitview_widget_ =
         std::make_unique<views::Widget>(std::move(params));
-    auto* box_layout_view = faster_splitview_widget_->SetContentsView(
-        std::make_unique<views::BoxLayoutView>());
-    box_layout_view->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
-    box_layout_view->SetBetweenChildSpacing(kSettingsButtonSpacingDp);
-
-    box_layout_view->AddChildView(std::make_unique<SystemToastStyle>(
+    faster_splitview_widget_->GetLayer()->SetFillsBoundsOpaquely(false);
+    faster_splitview_widget_->SetContentsView(std::make_unique<FasterSplitView>(
         base::BindRepeating(&OverviewGrid::OnSkipButtonPressed,
                             weak_ptr_factory_.GetWeakPtr()),
-        l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST),
-        l10n_util::GetStringUTF16(
-            IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_SKIP)));
-
-    auto* settings_button =
-        box_layout_view->AddChildView(std::make_unique<IconButton>(
-            base::BindRepeating(&OverviewGrid::OnSettingsButtonPressed,
-                                weak_ptr_factory_.GetWeakPtr()),
-            IconButton::Type::kLarge, &kOverviewSettingsIcon,
-            IDS_ASH_OVERVIEW_SETTINGS_BUTTON_LABEL));
-
-    // TODO(b/323199185): Consider refactoring this from `SystemToastStyle`.
-    const int toast_height = settings_button->GetPreferredSize().height();
-    const float toast_corner_radius = toast_height / 2.0f;
-    settings_button->SetBorder(std::make_unique<views::HighlightBorder>(
-        toast_corner_radius,
-        views::HighlightBorder::Type::kHighlightBorderOnShadow));
-    settings_button->SetBackgroundColor(kColorAshShieldAndBase80);
-
+        base::BindRepeating(&OverviewGrid::OnSettingsButtonPressed,
+                            weak_ptr_factory_.GetWeakPtr())));
     faster_splitview_widget_->Show();
   }
 
@@ -2977,7 +2962,7 @@
                         kFasterSplitScreenToastSpacingDp);
   faster_splitview_widget_->SetBounds(centered_bounds);
 
-  // TODO(b/323409897): Add a11y focus traversal and Chromevox support.
+  overview_session_->UpdateAccessibilityFocus();
 }
 
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index 2244721..8d965c7e 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -12,6 +12,7 @@
 
 #include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h"
 #include "ash/rotator/screen_rotation_animator_observer.h"
+#include "ash/style/icon_button.h"
 #include "ash/style/rounded_label_widget.h"
 #include "ash/wm/desks/templates/saved_desk_save_desk_button_container.h"
 #include "ash/wm/overview/overview_item.h"
@@ -41,6 +42,7 @@
 
 namespace ash {
 
+class FasterSplitView;
 class LegacyDeskBarView;
 class OverviewDropTarget;
 class OverviewGridEventHandler;
@@ -399,6 +401,8 @@
   SavedDeskSaveDeskButtonContainer* GetSaveDeskButtonContainer();
   const SavedDeskSaveDeskButtonContainer* GetSaveDeskButtonContainer() const;
 
+  FasterSplitView* GetFasterSplitView();
+
   // SplitViewObserver:
   void OnSplitViewStateChanged(SplitViewController::State previous_state,
                                SplitViewController::State state) override;
@@ -428,6 +432,7 @@
 
   // Returns the root window in which the grid displays the windows.
   aura::Window* root_window() { return root_window_; }
+  const aura::Window* root_window() const { return root_window_; }
 
   OverviewSession* overview_session() { return overview_session_; }
 
@@ -466,6 +471,10 @@
     return save_desk_button_container_widget_.get();
   }
 
+  views::Widget* faster_splitview_widget() {
+    return faster_splitview_widget_.get();
+  }
+
   int num_incognito_windows() const { return num_incognito_windows_; }
 
   int num_unsupported_windows() const { return num_unsupported_windows_; }
@@ -473,9 +482,6 @@
   const gfx::Rect bounds_for_testing() const { return bounds_; }
   float scroll_offset_for_testing() const { return scroll_offset_; }
   views::Widget* pine_widget_for_testing() const { return pine_widget_.get(); }
-  views::Widget* faster_splitview_widget_for_testing() {
-    return faster_splitview_widget_.get();
-  }
 
  private:
   friend class DesksTemplatesTest;
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 9dc7767..693222c 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -175,7 +175,7 @@
 
   Shell::Get()->AddShellObserver(this);
 
-  if (saved_desk_util::IsSavedDesksEnabled()) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons()) {
     hide_windows_for_saved_desks_grid_ = std::make_unique<
         ScopedOverviewHideWindows>(
         /*windows=*/std::vector<raw_ptr<aura::Window, VectorExperimental>>{},
@@ -192,7 +192,8 @@
   }
 
   // Create this before the desks bar widget.
-  if (saved_desk_util::IsSavedDesksEnabled() && !saved_desk_presenter_) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons() &&
+      !saved_desk_presenter_) {
     saved_desk_presenter_ = std::make_unique<SavedDeskPresenter>(this);
     saved_desk_dialog_controller_ =
         std::make_unique<SavedDeskDialogController>();
@@ -938,8 +939,9 @@
 }
 
 bool OverviewSession::IsSavedDeskUiLosingActivation(aura::Window* lost_active) {
-  if (!saved_desk_util::IsSavedDesksEnabled() || !lost_active)
+  if (!saved_desk_util::ShouldShowSavedDesksButtons() || !lost_active) {
     return false;
+  }
 
   for (auto& grid : grid_list_) {
     auto* desk_library_view = grid->GetSavedDeskLibraryView();
@@ -1025,7 +1027,10 @@
 }
 
 void OverviewSession::OnRootWindowClosing(aura::Window* root) {
-  auto iter = base::ranges::find(grid_list_, root, &OverviewGrid::root_window);
+  auto iter = base::ranges::find_if(
+      grid_list_, [root](const std::unique_ptr<OverviewGrid>& grid) {
+        return grid->root_window() == root;
+      });
   DCHECK(iter != grid_list_.end());
   (*iter)->Shutdown(OverviewEnterExitType::kImmediateExit);
   grid_list_.erase(iter);
@@ -1215,15 +1220,21 @@
       for (const auto& item : grid->window_list())
         a11y_widgets.push_back(item->item_widget());
     }
-    if (grid->desks_widget())
+    if (grid->desks_widget()) {
       a11y_widgets.push_back(const_cast<views::Widget*>(grid->desks_widget()));
+    }
 
-    if (grid->IsSaveDeskButtonContainerVisible())
+    if (grid->IsSaveDeskButtonContainerVisible()) {
       a11y_widgets.push_back(grid->save_desk_button_container_widget());
+    }
 
     if (auto* no_windows_widget = grid->no_windows_widget()) {
       a11y_widgets.push_back(no_windows_widget);
     }
+
+    if (auto* faster_splitview_widget = grid->faster_splitview_widget()) {
+      a11y_widgets.push_back(faster_splitview_widget);
+    }
   }
 
   if (a11y_widgets.empty())
@@ -1581,7 +1592,7 @@
 }
 
 void OverviewSession::OnTabletModeChanged() {
-  DCHECK(saved_desk_util::IsSavedDesksEnabled());
+  DCHECK(saved_desk_util::ShouldShowSavedDesksButtons());
   DCHECK(saved_desk_presenter_);
   saved_desk_presenter_->UpdateUIForSavedDeskLibrary();
 }
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index a1c3492..05f4da03 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -3212,7 +3212,7 @@
   // flip for Save & Recall has truly landed, remove the `NoSavedDesks` variant
   // of this test below and remove the Save & Recall feature check at the start
   // of this test.
-  if (GetParam() || !saved_desk_util::IsSavedDesksEnabled()) {
+  if (GetParam() || !saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
   }
 
@@ -3265,7 +3265,7 @@
 TEST_P(OverviewSessionTest, AccessibilityFocusAnnotatorNoSavedDesks) {
   // If saved desk is enabled, the a11y order changes. This is tested in
   // the saved desk test suite.
-  if (GetParam() || saved_desk_util::IsSavedDesksEnabled()) {
+  if (GetParam() || saved_desk_util::ShouldShowSavedDesksButtons()) {
     return;
   }
 
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index 2a50999..e8438c8 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -40,6 +40,7 @@
 #include "ash/wm/overview/overview_window_drag_controller.h"
 #include "ash/wm/overview/scoped_overview_transform_window.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
+#include "ash/wm/splitview/faster_split_view.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_divider.h"
@@ -178,8 +179,10 @@
 
   if (!Shell::Get()->IsInTabletMode() && faster_split_screen_setup) {
     auto* overview_grid = GetOverviewGridForRoot(window->GetRootWindow());
-    EXPECT_TRUE(overview_grid->faster_splitview_widget_for_testing());
+    EXPECT_TRUE(overview_grid->faster_splitview_widget());
     EXPECT_FALSE(overview_grid->no_windows_widget());
+    EXPECT_FALSE(overview_grid->GetSaveDeskButtonContainer());
+    EXPECT_FALSE(overview_grid->desks_bar_view());
   }
 
   return split_view_overview_session;
@@ -249,7 +252,7 @@
 
 // Test fixture to verify faster split screen feature.
 
-class FasterSplitScreenTest : public AshTestBase {
+class FasterSplitScreenTest : public OverviewTestBase {
  public:
   FasterSplitScreenTest()
       : scoped_feature_list_(features::kFasterSplitScreenSetup) {}
@@ -259,7 +262,7 @@
 
   // AshTestBase:
   void SetUp() override {
-    AshTestBase::SetUp();
+    OverviewTestBase::SetUp();
     WindowCycleList::SetDisableInitialDelayForTesting(true);
   }
 
@@ -336,7 +339,7 @@
   ToggleOverview();
   auto* overview_grid = GetOverviewGridForRoot(w1->GetRootWindow());
   EXPECT_FALSE(overview_grid->no_windows_widget());
-  EXPECT_FALSE(overview_grid->faster_splitview_widget_for_testing());
+  EXPECT_FALSE(overview_grid->faster_splitview_widget());
 }
 
 // Tests that faster split screen can only start with certain snap action
@@ -636,8 +639,7 @@
 
   auto* overview_grid = GetOverviewGridForRoot(w1->GetRootWindow());
   ASSERT_TRUE(overview_grid);
-  auto* faster_splitview_widget =
-      overview_grid->faster_splitview_widget_for_testing();
+  auto* faster_splitview_widget = overview_grid->faster_splitview_widget();
   ASSERT_TRUE(faster_splitview_widget);
   auto* toast_view = views::AsViewClass<SystemToastStyle>(
       faster_splitview_widget->GetContentsView()->children()[0]);
@@ -1041,6 +1043,68 @@
   GetEventGenerator()->GestureTapAt(divider_center);
 }
 
+TEST_F(FasterSplitScreenTest, BasicTabKeyNavigation) {
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+
+  const WindowSnapWMEvent snap_event(WM_EVENT_SNAP_PRIMARY,
+                                     WindowSnapActionSource::kTest);
+  WindowState::Get(window1.get())->OnWMEvent(&snap_event);
+  ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
+
+  // Tab until we get to the first overview item.
+  SendKeyUntilOverviewItemIsFocused(ui::VKEY_TAB);
+  const std::vector<std::unique_ptr<OverviewItemBase>>& overview_windows =
+      GetOverviewItemsForRoot(0);
+  EXPECT_EQ(overview_windows[0]->GetWindow(), GetOverviewFocusedWindow());
+
+  OverviewFocusCycler* focus_cycler = GetOverviewSession()->focus_cycler();
+  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
+
+  // Tab to the toast dismiss button.
+  PressAndReleaseKey(ui::VKEY_TAB);
+  EXPECT_EQ(grid->GetFasterSplitView()->toast()->dismiss_button(),
+            focus_cycler->focused_view()->GetView());
+
+  // Tab to the settings button.
+  PressAndReleaseKey(ui::VKEY_TAB);
+  EXPECT_EQ(grid->GetFasterSplitView()->settings_button(),
+            focus_cycler->focused_view());
+}
+
+TEST_F(FasterSplitScreenTest, AccessibilityFocusAnnotator) {
+  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
+  auto window0 = CreateTestWindow(gfx::Rect(100, 100));
+
+  // Snap `window0`, so it is excluded from the overview list.
+  SnapOneTestWindow(window0.get(), chromeos::WindowStateType::kPrimarySnapped,
+                    chromeos::kDefaultSnapRatio,
+                    WindowSnapActionSource::kDragWindowToEdgeToSnap);
+
+  auto* focus_widget = views::Widget::GetWidgetForNativeWindow(
+      GetOverviewSession()->GetOverviewFocusWindow());
+  ASSERT_TRUE(focus_widget);
+  OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get();
+  ASSERT_FALSE(grid->desks_widget());
+  ASSERT_FALSE(grid->GetSaveDeskForLaterButton());
+  auto* faster_splitview_widget = grid->faster_splitview_widget();
+  ASSERT_TRUE(faster_splitview_widget);
+
+  // Overview items are in MRU order, so the expected order in the grid list is
+  // the reverse creation order.
+  auto* item_widget1 = GetOverviewItemForWindow(window1.get())->item_widget();
+
+  // Order should be [focus_widget, item_widget1, faster_splitview_widget].
+  CheckA11yOverrides("focus", focus_widget,
+                     /*expected_previous=*/faster_splitview_widget,
+                     /*expected_next=*/item_widget1);
+  CheckA11yOverrides("item1", item_widget1, /*expected_previous=*/focus_widget,
+                     /*expected_next=*/faster_splitview_widget);
+  CheckA11yOverrides("splitview", faster_splitview_widget,
+                     /*expected_previous=*/item_widget1,
+                     /*expected_next=*/focus_widget);
+}
+
 // Tests the histograms for the split view overview session exit points are
 // recorded correctly in clamshell.
 TEST_F(FasterSplitScreenTest,
diff --git a/ash/wm/splitview/faster_split_view.cc b/ash/wm/splitview/faster_split_view.cc
new file mode 100644
index 0000000..502e6d59
--- /dev/null
+++ b/ash/wm/splitview/faster_split_view.cc
@@ -0,0 +1,115 @@
+// Copyright 2024 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/wm/splitview/faster_split_view.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
+#include "ash/style/system_toast_style.h"
+#include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/overview/overview_utils.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/highlight_border.h"
+#include "ui/views/view_utils.h"
+
+namespace ash {
+
+namespace {
+
+// Distance from the right of the faster splitscreen toast to the left of the
+// settings button.
+constexpr int kSettingsButtonSpacingDp = 8;
+
+}  // namespace
+
+FasterSplitViewToast::FasterSplitViewToast(base::RepeatingClosure skip_callback)
+    : SystemToastStyle(
+          std::move(skip_callback),
+          l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST),
+          l10n_util::GetStringUTF16(
+              IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_SKIP)) {
+  dismiss_button()->SetTooltipText(l10n_util::GetStringUTF16(
+      IDS_ASH_OVERVIEW_FASTER_SPLITSCREEN_TOAST_DISMISS_WINDOW_SUGGESTIONS));
+}
+
+views::View* FasterSplitViewToast::GetView() {
+  return dismiss_button();
+}
+
+void FasterSplitViewToast::MaybeActivateFocusedView() {
+  // TODO(sophiewen): Copy `skip_callback` and run it.
+  // Destroys `this`.
+  OverviewController::Get()->EndOverview(OverviewEndAction::kKeyEscapeOrBack);
+}
+
+void FasterSplitViewToast::MaybeCloseFocusedView(bool primary_action) {}
+
+void FasterSplitViewToast::MaybeSwapFocusedView(bool right) {}
+
+void FasterSplitViewToast::OnFocusableViewFocused() {}
+
+void FasterSplitViewToast::OnFocusableViewBlurred() {}
+
+BEGIN_METADATA(FasterSplitViewToast, SystemToastStyle)
+END_METADATA
+
+FasterSplitViewSettingsButton::FasterSplitViewSettingsButton(
+    views::Button::PressedCallback settings_callback)
+    : IconButton(std::move(settings_callback),
+                 IconButton::Type::kLarge,
+                 &kOverviewSettingsIcon,
+                 IDS_ASH_OVERVIEW_SETTINGS_BUTTON_LABEL) {}
+
+views::View* FasterSplitViewSettingsButton::GetView() {
+  return this;
+}
+void FasterSplitViewSettingsButton::MaybeActivateFocusedView() {
+  // TODO(sophiewen): Copy `settings_callback` and run it.
+  // Destroys `this`.
+  Shell::Get()->shell_delegate()->OpenMultitaskingSettings();
+}
+void FasterSplitViewSettingsButton::MaybeCloseFocusedView(bool primary_action) {
+}
+
+void FasterSplitViewSettingsButton::MaybeSwapFocusedView(bool right) {}
+
+void FasterSplitViewSettingsButton::OnFocusableViewFocused() {}
+
+void FasterSplitViewSettingsButton::OnFocusableViewBlurred() {}
+
+BEGIN_METADATA(FasterSplitViewSettingsButton, views::View)
+END_METADATA
+
+FasterSplitView::FasterSplitView(
+    base::RepeatingClosure skip_callback,
+    views::Button::PressedCallback settings_callback) {
+  SetOrientation(views::BoxLayout::Orientation::kHorizontal);
+  SetBetweenChildSpacing(kSettingsButtonSpacingDp);
+
+  toast_ = AddChildView(
+      std::make_unique<FasterSplitViewToast>(std::move(skip_callback)));
+
+  settings_button_ =
+      AddChildView(std::make_unique<FasterSplitViewSettingsButton>(
+          std::move(settings_callback)));
+
+  // TODO(b/323199185): Consider refactoring this from `SystemToastStyle`.
+  const int toast_height = settings_button_->GetPreferredSize().height();
+  const float toast_corner_radius = toast_height / 2.0f;
+  settings_button_->SetBorder(std::make_unique<views::HighlightBorder>(
+      toast_corner_radius,
+      views::HighlightBorder::Type::kHighlightBorderOnShadow));
+  settings_button_->SetBackgroundColor(kColorAshShieldAndBase80);
+}
+
+BEGIN_METADATA(FasterSplitView, views::BoxLayoutView)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/wm/splitview/faster_split_view.h b/ash/wm/splitview/faster_split_view.h
new file mode 100644
index 0000000..0727a41f
--- /dev/null
+++ b/ash/wm/splitview/faster_split_view.h
@@ -0,0 +1,83 @@
+// Copyright 2024 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_WM_SPLITVIEW_FASTER_SPLIT_VIEW_H_
+#define ASH_WM_SPLITVIEW_FASTER_SPLIT_VIEW_H_
+
+#include "ash/style/icon_button.h"
+#include "ash/style/system_toast_style.h"
+#include "ash/wm/overview/overview_focusable_view.h"
+#include "base/functional/callback.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/layout/box_layout_view.h"
+
+namespace ash {
+
+// A toast in faster splitscreen setup. Contains a dialog and skip button.
+class FasterSplitViewToast : public SystemToastStyle,
+                             public OverviewFocusableView {
+ public:
+  METADATA_HEADER(FasterSplitViewToast);
+
+  explicit FasterSplitViewToast(base::RepeatingClosure skip_callback);
+  FasterSplitViewToast(const FasterSplitViewToast&) = delete;
+  FasterSplitViewToast& operator=(const FasterSplitViewToast&) = delete;
+  ~FasterSplitViewToast() override = default;
+
+  // OverviewFocusableView:
+  views::View* GetView() override;
+  void MaybeActivateFocusedView() override;
+  void MaybeCloseFocusedView(bool primary_action) override;
+  void MaybeSwapFocusedView(bool right) override;
+  void OnFocusableViewFocused() override;
+  void OnFocusableViewBlurred() override;
+};
+
+// A settings button in faster splitscreen setup.
+class FasterSplitViewSettingsButton : public IconButton,
+                                      public OverviewFocusableView {
+ public:
+  METADATA_HEADER(FasterSplitViewSettingsButton);
+
+  explicit FasterSplitViewSettingsButton(
+      views::Button::PressedCallback settings_callback);
+  FasterSplitViewSettingsButton(const FasterSplitViewSettingsButton&) = delete;
+  FasterSplitViewSettingsButton& operator=(
+      const FasterSplitViewSettingsButton&) = delete;
+  ~FasterSplitViewSettingsButton() override = default;
+
+  // OverviewFocusableView:
+  views::View* GetView() override;
+  void MaybeActivateFocusedView() override;
+  void MaybeCloseFocusedView(bool primary_action) override;
+  void MaybeSwapFocusedView(bool right) override;
+  void OnFocusableViewFocused() override;
+  void OnFocusableViewBlurred() override;
+};
+
+// A container for the contents view of the faster splitscreen setup widget.
+// TODO(b/324347613): Find a better name for this class.
+class FasterSplitView : public views::BoxLayoutView {
+  METADATA_HEADER(FasterSplitView, views::BoxLayoutView)
+
+ public:
+  FasterSplitView(base::RepeatingClosure skip_callback,
+                  views::Button::PressedCallback settings_callback);
+  FasterSplitView(const FasterSplitView&) = delete;
+  FasterSplitView& operator=(const FasterSplitView&) = delete;
+  ~FasterSplitView() override = default;
+
+  // TODO(sophiewen): Store these as OverviewFocusableViews and private these
+  // from the header.
+  FasterSplitViewToast* toast() { return toast_; }
+  FasterSplitViewSettingsButton* settings_button() { return settings_button_; }
+
+ private:
+  raw_ptr<FasterSplitViewToast> toast_ = nullptr;
+  raw_ptr<FasterSplitViewSettingsButton> settings_button_ = nullptr;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_SPLITVIEW_FASTER_SPLIT_VIEW_H_
diff --git a/ash/wm/splitview/split_view_overview_session.cc b/ash/wm/splitview/split_view_overview_session.cc
index b699a4ea..69ccb951 100644
--- a/ash/wm/splitview/split_view_overview_session.cc
+++ b/ash/wm/splitview/split_view_overview_session.cc
@@ -64,10 +64,12 @@
     return;
   }
 
+  // Set the type before we start overview, which will initialize the grid and
+  // check whether to create the desk bar and buttons based on `setup_type_`.
+  setup_type_ = SplitViewOverviewSetupType::kSnapThenAutomaticOverview;
   OverviewController::Get()->StartOverview(
       action.value_or(OverviewStartAction::kFasterSplitScreenSetup),
       type.value_or(OverviewEnterExitType::kNormal));
-  setup_type_ = SplitViewOverviewSetupType::kSnapThenAutomaticOverview;
 }
 
 void SplitViewOverviewSession::RecordSplitViewOverviewSessionExitPointMetrics(
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 721a6fb..496fe8b 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -734,7 +734,7 @@
           SnapGroupController::Get());
 }
 
-bool IsInFasterSplitScreenSetupSession(aura::Window* window) {
+bool IsInFasterSplitScreenSetupSession(const aura::Window* window) {
   SplitViewOverviewSession* split_view_overview_session =
       RootWindowController::ForWindow(window)->split_view_overview_session();
   return !Shell::Get()->IsInTabletMode() && split_view_overview_session &&
@@ -742,4 +742,18 @@
              SplitViewOverviewSetupType::kSnapThenAutomaticOverview;
 }
 
+bool IsInFasterSplitScreenSetupSession() {
+  if (!IsInOverviewSession() || display::Screen::GetScreen()->InTabletMode()) {
+    return false;
+  }
+  auto* overview_session = GetOverviewSession();
+  for (const auto& grid : overview_session->grid_list()) {
+    // Return true if any grid is in faster splitscreen setup.
+    if (IsInFasterSplitScreenSetupSession(grid->root_window())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace ash::window_util
diff --git a/ash/wm/window_util.h b/ash/wm/window_util.h
index e10e109..67ea391 100644
--- a/ash/wm/window_util.h
+++ b/ash/wm/window_util.h
@@ -219,7 +219,12 @@
 // Returns true if `SplitViewOverviewSession` is created through faster split
 // screen setup, i.e. partial overview is started on the other side of the
 // screen when `window` is snapped.
-bool IsInFasterSplitScreenSetupSession(aura::Window* window);
+bool IsInFasterSplitScreenSetupSession(const aura::Window* window);
+
+// Returns true if overview is in session in clamshell mode and any overview
+// grid is in faster splitview. This is a specific mode during which we don't
+// show the desk bar or save desk buttons.
+bool IsInFasterSplitScreenSetupSession();
 
 }  // namespace ash::window_util
 
diff --git a/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.cc b/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.cc
index 62449af4..537304f 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.cc
+++ b/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.cc
@@ -62,7 +62,7 @@
     branch_size_in_bytes_ += usable_size;
     PA_DCHECK(branch_size_in_bytes_ <= capacity_in_bytes);
 
-    slots_.emplace_back(object, usable_size);
+    slots_.emplace_back(slot_start, usable_size);
 
     // Swap randomly so that the quarantine list remain shuffled.
     // This is not uniformly random, but sufficiently random.
@@ -79,6 +79,17 @@
   return true;
 }
 
+bool LightweightQuarantineBranch::IsQuarantinedForTesting(void* object) {
+  ConditionalScopedGuard guard(lock_required_, lock_);
+  uintptr_t slot_start = root_.allocator_root_.ObjectToSlotStart(object);
+  for (const auto& slot : slots_) {
+    if (slot.slot_start == slot_start) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void LightweightQuarantineBranch::PurgeInternal(size_t target_size_in_bytes) {
   size_t size_in_bytes = root_.size_in_bytes_.load(std::memory_order_acquire);
   int64_t freed_count = 0;
@@ -91,14 +102,13 @@
     const auto& to_free = slots_.back();
     size_t to_free_size = to_free.usable_size;
 
-    auto* slot_span = SlotSpanMetadata::FromObject(to_free.object);
-    uintptr_t slot_start =
-        root_.allocator_root_.ObjectToSlotStart(to_free.object);
-    PA_DCHECK(slot_span == SlotSpanMetadata::FromSlotStart(slot_start));
+    auto* slot_span = SlotSpanMetadata::FromSlotStart(to_free.slot_start);
+    void* object = root_.allocator_root_.SlotStartToObject(to_free.slot_start);
+    PA_DCHECK(slot_span == SlotSpanMetadata::FromObject(object));
 
-    PA_DCHECK(to_free.object);
-    root_.allocator_root_.FreeNoHooksImmediate(to_free.object, slot_span,
-                                               slot_start);
+    PA_DCHECK(to_free.slot_start);
+    root_.allocator_root_.FreeNoHooksImmediate(object, slot_span,
+                                               to_free.slot_start);
 
     freed_count++;
     freed_size_in_bytes += to_free_size;
diff --git a/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.h b/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.h
index 5f763b23..aebff11 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.h
+++ b/base/allocator/partition_allocator/src/partition_alloc/lightweight_quarantine.h
@@ -127,15 +127,7 @@
   }
 
   // Determines this list contains an object.
-  bool IsQuarantinedForTesting(void* object) {
-    ConditionalScopedGuard guard(lock_required_, lock_);
-    for (const auto& slot : slots_) {
-      if (slot.object == object) {
-        return true;
-      }
-    }
-    return false;
-  }
+  bool IsQuarantinedForTesting(void* object);
 
   Root& GetRoot() { return root_; }
 
@@ -182,7 +174,7 @@
 
   // `slots_` hold quarantined entries.
   struct QuarantineSlot {
-    void* object;
+    uintptr_t slot_start;
     size_t usable_size;
   };
   std::vector<QuarantineSlot, InternalAllocator<QuarantineSlot>> slots_
diff --git a/base/containers/span.h b/base/containers/span.h
index 8beadb6..a2686a8 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -14,6 +14,7 @@
 #include <iterator>
 #include <limits>
 #include <memory>
+#include <span>
 #include <type_traits>
 #include <utility>
 
@@ -429,6 +430,33 @@
     std::copy(other.data(), other.data() + other.size(), data());
   }
 
+  // Implicit conversion from std::span<T, N> to base::span<T, N>.
+  //
+  // We get other conversions for free from std::span's constructors, but it
+  // does not deduce N on its range constructor.
+  span(std::span<std::remove_const_t<T>, N> other)
+      :  // SAFETY: std::span contains a valid data pointer and size such
+         // that pointer+size remains valid.
+        UNSAFE_BUFFERS(
+            span(std::ranges::data(other), std::ranges::size(other))) {}
+  span(std::span<T, N> other)
+    requires(std::is_const_v<T>)
+      :  // SAFETY: std::span contains a valid data pointer and size such
+         // that pointer+size remains valid.
+        UNSAFE_BUFFERS(
+            span(std::ranges::data(other), std::ranges::size(other))) {}
+
+  // Implicit conversion from base::span<T, N> to std::span<T, N>.
+  //
+  // We get other conversions for free from std::span's constructors, but it
+  // does not deduce N on its range constructor.
+  operator std::span<T, N>() const { return std::span<T, N>(*this); }
+  operator std::span<const T, N>() const
+    requires(!std::is_const_v<T>)
+  {
+    return std::span<const T, N>(*this);
+  }
+
  private:
   // This field is not a raw_ptr<> because it was filtered by the rewriter
   // for: #constexpr-ctor-field-initializer, #global-scope, #union
@@ -831,8 +859,8 @@
 //   * |std::size| should be preferred for plain arrays.
 //   * In run-time contexts, functions such as |std::array::size| should be
 //     preferred.
-#define EXTENT(x)                                        \
-  ::base::internal::must_not_be_dynamic_extent<decltype( \
-      ::base::make_span(x))::extent>()
+#define EXTENT(x)                                                          \
+  ::base::internal::must_not_be_dynamic_extent<decltype(::base::make_span( \
+      x))::extent>()
 
 #endif  // BASE_CONTAINERS_SPAN_H_
diff --git a/base/containers/span_nocompile.nc b/base/containers/span_nocompile.nc
index 9c5a3ef1..41506d2a 100644
--- a/base/containers/span_nocompile.nc
+++ b/base/containers/span_nocompile.nc
@@ -167,6 +167,18 @@
   span s2(vector.data(), kSize2);              // expected-error@*:* {{The source type is out of range for the destination type}}
 }
 
+void BadConstConversionsWithStdSpan() {
+  int kData[] = {10, 11, 12};
+  {
+    base::span<const int, 3u> fixed_base_span(kData);
+    std::span<int, 3u> s(fixed_base_span);  // expected-error {{no matching constructor}}
+  }
+  {
+    std::span<const int, 3u> fixed_std_span(kData);
+    base::span<int, 3u> s(fixed_std_span);  // expected-error {{no matching constructor}}
+  }
+}
+
 void FromVolatileArrayDisallowed() {
   static volatile int array[] = {1, 2, 3};
   span<int> s(array);  // expected-error {{no matching constructor for initialization of 'span<int>'}}
diff --git a/base/containers/span_unittest.cc b/base/containers/span_unittest.cc
index f09e7c3..34f979d2 100644
--- a/base/containers/span_unittest.cc
+++ b/base/containers/span_unittest.cc
@@ -1988,29 +1988,106 @@
 // API decides to use it. The size() and data() convention should mean
 // that everyone's spans are compatible with each other.
 TEST(SpanTest, FromStdSpan) {
-  const int kData[] = {10, 11, 12};
-  std::span std_span(kData);
+  int kData[] = {10, 11, 12};
+  std::span<const int> std_span(kData);
+  std::span<int> mut_std_span(kData);
+  std::span<const int, 3u> fixed_std_span(kData);
+  std::span<int, 3u> mut_fixed_std_span(kData);
 
-  base::span base_span(std_span);
-  EXPECT_EQ(base_span.size(), 3u);
-  EXPECT_EQ(base_span.data(), kData);
+  // Tests *implicit* conversions through assignment construction.
+  {
+    base::span<const int> base_span = std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
+  {
+    base::span<const int> base_span = mut_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
+  {
+    base::span<const int> base_span = fixed_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
+  {
+    base::span<const int> base_span = mut_fixed_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
 
-  auto base_made_span = base::make_span(std_span);
-  EXPECT_EQ(base_made_span.size(), 3u);
-  EXPECT_EQ(base_made_span.data(), kData);
+  {
+    base::span<const int, 3u> base_span = fixed_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
+  {
+    base::span<const int, 3u> base_span = mut_fixed_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
+  {
+    base::span<int, 3u> base_span = mut_fixed_std_span;
+    EXPECT_EQ(base_span.size(), 3u);
+    EXPECT_EQ(base_span.data(), kData);
+  }
 
-  auto base_byte_span = base::as_byte_span(std_span);
-  EXPECT_EQ(base_byte_span.size(), sizeof(int) * 3u);
-  EXPECT_EQ(base_byte_span.data(), reinterpret_cast<const uint8_t*>(kData));
+  {
+    auto base_made_span = base::make_span(std_span);
+    EXPECT_EQ(base_made_span.size(), 3u);
+    EXPECT_EQ(base_made_span.data(), kData);
+  }
+  {
+    auto base_byte_span = base::as_byte_span(std_span);
+    EXPECT_EQ(base_byte_span.size(), sizeof(int) * 3u);
+    EXPECT_EQ(base_byte_span.data(), reinterpret_cast<const uint8_t*>(kData));
+  }
 }
 
 TEST(SpanTest, ToStdSpan) {
-  const int kData[] = {10, 11, 12};
-  base::span base_span(kData);
+  int kData[] = {10, 11, 12};
+  base::span<const int> base_span(kData);
+  base::span<int> mut_base_span(kData);
+  base::span<const int, 3u> fixed_base_span(kData);
+  base::span<int, 3u> mut_fixed_base_span(kData);
 
-  std::span std_span(base_span);
-  EXPECT_EQ(std_span.size(), 3u);
-  EXPECT_EQ(std_span.data(), kData);
+  // Tests *implicit* conversions through assignment construction.
+  {
+    std::span<const int> std_span = base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+  {
+    std::span<const int> std_span = mut_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+  {
+    std::span<const int> std_span = fixed_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+  {
+    std::span<const int> std_span = mut_fixed_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+
+  {
+    std::span<const int, 3u> std_span = fixed_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+  {
+    std::span<const int, 3u> std_span = mut_fixed_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
+  {
+    std::span<int, 3u> std_span = mut_fixed_base_span;
+    EXPECT_EQ(std_span.size(), 3u);
+    EXPECT_EQ(std_span.data(), kData);
+  }
 
   // no make_span() or as_byte_span() in std::span.
 }
diff --git a/base/task/thread_pool/thread_group.cc b/base/task/thread_pool/thread_group.cc
index 00cc588..91d68d6 100644
--- a/base/task/thread_pool/thread_group.cc
+++ b/base/task/thread_pool/thread_group.cc
@@ -189,8 +189,7 @@
 
   max_tasks_ = max_tasks;
   DCHECK_GE(max_tasks_, 1U);
-  in_start().initial_max_tasks = max_tasks_;
-  DCHECK_LE(in_start().initial_max_tasks, kMaxNumberOfWorkers);
+  in_start().initial_max_tasks = std::min(max_tasks_, kMaxNumberOfWorkers);
   max_best_effort_tasks_ = max_best_effort_tasks;
   in_start().suggested_reclaim_time = suggested_reclaim_time;
   in_start().worker_environment = worker_environment;
diff --git a/base/task/thread_pool/thread_pool_instance.h b/base/task/thread_pool/thread_pool_instance.h
index a4f78b3..1fd429d 100644
--- a/base/task/thread_pool/thread_pool_instance.h
+++ b/base/task/thread_pool/thread_pool_instance.h
@@ -63,7 +63,9 @@
     ~InitParams();
 
     // Maximum number of unblocked tasks that can run concurrently in the
-    // foreground thread group.
+    // foreground thread group. This is capped at 256 (and should likely not be
+    // configured anywhere close to this in a browser, approaching that limit is
+    // most useful on compute farms running tests or compiles in parallel).
     size_t max_num_foreground_threads;
 
     // Maximum number of unblocked tasks that can run concurrently in the
diff --git a/build/config/siso/clang_code_coverage_wrapper.star b/build/config/siso/clang_code_coverage_wrapper.star
index 70f18d4..9eb1c1c 100644
--- a/build/config/siso/clang_code_coverage_wrapper.star
+++ b/build/config/siso/clang_code_coverage_wrapper.star
@@ -204,7 +204,9 @@
     files_to_instrument = []
     if instrument_file:
         files_to_instrument = str(ctx.fs.read(ctx.fs.canonpath(instrument_file))).splitlines()
-        files_to_instrument = [ctx.fs.canonpath(f) for f in files_to_instrument]
+
+        # strip() is for removing '\r' on Windows.
+        files_to_instrument = [ctx.fs.canonpath(f).strip() for f in files_to_instrument]
 
     should_remove_flags = False
     if compile_source_file not in force_list:
@@ -215,6 +217,7 @@
 
     if should_remove_flags:
         return _remove_flags_from_command(compile_command)
+    print("Keeping code coverage flags for %s" % compile_source_file)
     return compile_command
 
 clang_code_coverage_wrapper = module(
diff --git a/build/config/siso/clang_linux.star b/build/config/siso/clang_linux.star
index f74bbf2a..a4793525 100644
--- a/build/config/siso/clang_linux.star
+++ b/build/config/siso/clang_linux.star
@@ -9,6 +9,8 @@
 load("./android.star", "android")
 load("./clang_all.star", "clang_all")
 load("./clang_code_coverage_wrapper.star", "clang_code_coverage_wrapper")
+load("./config.star", "config")
+load("./cros.star", "cros")
 
 # TODO: b/323091468 - Propagate target android ABI and android SDK version
 # from GN, and remove remove hardcoded filegroups.
@@ -52,6 +54,26 @@
             "type": "glob",
             "includes": ["*"],
         },
+        "third_party/llvm-build/Release+Asserts/lib/clang/18/lib:libs": {
+            "type": "glob",
+            "includes": ["*"],
+        },
+        "third_party/llvm-build/Release+Asserts/lib/clang/18/share:share": {
+            "type": "glob",
+            "includes": ["*"],
+        },
+        "build/linux/debian_bullseye_amd64-sysroot/lib/x86_64-linux-gnu:libso": {
+            "type": "glob",
+            "includes": ["*.so*"],
+        },
+        "build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu:libs": {
+            "type": "glob",
+            "includes": ["*.o", "*.so*", "lib*.a"],
+        },
+        "build/linux/debian_bullseye_amd64-sysroot/usr/lib/gcc/x86_64-linux-gnu:libgcc": {
+            "type": "glob",
+            "includes": ["*.o", "*.a", "*.so"],
+        },
     }
     if android.enabled(ctx):
         for arch in android_archs:
@@ -84,6 +106,23 @@
             "build/linux/debian_bullseye_i386-sysroot/usr/include:include",
             "build/linux/debian_bullseye_i386-sysroot/usr/lib:headers",
         ],
+        "build/linux/debian_bullseye_amd64-sysroot:link": [
+            "build/linux/debian_bullseye_amd64-sysroot/lib/x86_64-linux-gnu:libso",
+            "build/linux/debian_bullseye_amd64-sysroot/lib64/ld-linux-x86-64.so.2",
+            "build/linux/debian_bullseye_amd64-sysroot/usr/lib/gcc/x86_64-linux-gnu:libgcc",
+            "build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu:libs",
+            "third_party/llvm-build/Release+Asserts/bin/clang",
+            "third_party/llvm-build/Release+Asserts/bin/clang++",
+            "third_party/llvm-build/Release+Asserts/bin/ld.lld",
+            "third_party/llvm-build/Release+Asserts/bin/lld",
+            "third_party/llvm-build/Release+Asserts/bin/llvm-nm",
+            "third_party/llvm-build/Release+Asserts/bin/llvm-readelf",
+            "third_party/llvm-build/Release+Asserts/bin/llvm-readobj",
+            # The following inputs are used for sanitizer builds.
+            # It might be better to add them only for sanitizer builds if there is a performance issue.
+            "third_party/llvm-build/Release+Asserts/lib/clang/18/lib:libs",
+            "third_party/llvm-build/Release+Asserts/lib/clang/18/share:share",
+        ],
         "third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot:headers": [
             "third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include:include",
             "third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/local/include:include",
@@ -142,6 +181,52 @@
             "timeout": "2m",
         },
     ])
+
+    # TODO: b/316267242 - Enable remote links for Android and CrOS toolchain builds.
+    if not android.enabled(ctx) and not (cros.custom_toolchain(ctx) or cros.custom_sysroot(ctx)):
+        step_config["rules"].extend([
+            {
+                "name": "clang/alink/llvm-ar",
+                "action": "(.*_)?alink",
+                "inputs": [
+                    # TODO: b/316267242 - Add inputs to GN config.
+                    "third_party/llvm-build/Release+Asserts/bin/llvm-ar",
+                ],
+                "exclude_input_patterns": [
+                    "*.cc",
+                    "*.h",
+                    "*.js",
+                    "*.pak",
+                    "*.py",
+                    "*.stamp",
+                ],
+                "remote": config.get(ctx, "remote-library-link"),
+                "platform_ref": "large",
+                "accumulate": True,
+            },
+            {
+                "name": "clang/solink/gcc_solink_wrapper",
+                "action": "(.*_)?solink",
+                "command_prefix": "\"python3\" \"../../build/toolchain/gcc_solink_wrapper.py\"",
+                "inputs": [
+                    # TODO: b/316267242 - Add inputs to GN config.
+                    "build/toolchain/gcc_solink_wrapper.py",
+                    "build/toolchain/whole_archive.py",
+                    "build/toolchain/wrapper_utils.py",
+                    "build/linux/debian_bullseye_amd64-sysroot:link",
+                ],
+                "exclude_input_patterns": [
+                    "*.cc",
+                    "*.h",
+                    "*.js",
+                    "*.pak",
+                    "*.py",
+                    "*.stamp",
+                ],
+                "remote": config.get(ctx, "remote-library-link"),
+                "platform_ref": "large",
+            },
+        ])
     return step_config
 
 clang = module(
diff --git a/build/config/siso/clang_mac.star b/build/config/siso/clang_mac.star
index 415a932..1c21a1a 100644
--- a/build/config/siso/clang_mac.star
+++ b/build/config/siso/clang_mac.star
@@ -75,8 +75,9 @@
             "clang_large": largePlatform,
         })
         step_config["input_deps"].update(clang_all.input_deps)
+
         # TODO: https://issues.chromium.org/40120210 - remove this
-	# once we can use relative path in hmap.
+        # once we can use relative path in hmap.
         need_input_root_absolute_path_for_objc = False
         gn_args = gn.args(ctx)
         if gn_args.get("target_os") == "\"ios\"":
diff --git a/build/config/siso/config.star b/build/config/siso/config.star
index 562cec7f..422345c 100644
--- a/build/config/siso/config.star
+++ b/build/config/siso/config.star
@@ -12,6 +12,9 @@
 
     # TODO: b/308405411 - Enable this config for all builders.
     "remote-devtools-frontend-typescript",
+
+    # TODO: b/316267242 - Enable remote links after confirming performance.
+    "remote-library-link",
 ]
 
 def __check(ctx):
diff --git a/build/config/siso/cros.star b/build/config/siso/cros.star
index 076eb86..6b95e04e 100644
--- a/build/config/siso/cros.star
+++ b/build/config/siso/cros.star
@@ -122,6 +122,8 @@
 
 cros = module(
     "cros",
+    custom_toolchain = __custom_toolchain,
+    custom_sysroot = __custom_sysroot,
     filegroups = __filegroups,
     handlers = __handlers,
     step_config = __step_config,
diff --git a/build/config/siso/linux.star b/build/config/siso/linux.star
index 3067516..9969dac 100644
--- a/build/config/siso/linux.star
+++ b/build/config/siso/linux.star
@@ -47,10 +47,13 @@
     if android.enabled(ctx):
         step_config = android.step_config(ctx, step_config)
 
+    # nacl step config should be added before clang step config for link step
+    # rules.
+    step_config = nacl.step_config(ctx, step_config)
+
     step_config = clang.step_config(ctx, step_config)
     step_config = cros.step_config(ctx, step_config)
     step_config = devtools_frontend.step_config(ctx, step_config)
-    step_config = nacl.step_config(ctx, step_config)
     step_config = nasm.step_config(ctx, step_config)
     step_config = proto.step_config(ctx, step_config)
     step_config = rust.step_config(ctx, step_config)
diff --git a/build/config/siso/nacl_linux.star b/build/config/siso/nacl_linux.star
index e296d016..f6ee6e5 100644
--- a/build/config/siso/nacl_linux.star
+++ b/build/config/siso/nacl_linux.star
@@ -51,6 +51,7 @@
 
 def __step_config(ctx, step_config):
     step_config["rules"].extend([
+        # pnacl
         {
             "name": "nacl/pnacl-clang++",
             "action": "newlib_pnacl.*_cxx",
@@ -86,18 +87,34 @@
             "input_root_absolute_path": True,
         },
         {
+            "name": "nacl/pnacl-ar",
+            "action": "newlib_pnacl_alink",
+            "remote": False,
+        },
+        # glibc
+        {
             "name": "nacl/glibc/x86_64-nacl-g++",
             "action": "glibc_x64_cxx",
             "inputs": [
                 "native_client/toolchain/linux_x86/nacl_x86_glibc/bin/x86_64-nacl-g++",
             ],
             # ELF-32 doesn't work on gVisor,
-            # so will local-fallback if gVisor is used.
-            # TODO(b/278485912): remote=True for trusted instance.
             "remote": False,
-            "input_root_absolute_path": True,
         },
         {
+            "name": "nacl/glibc/x86_64-nacl-ar",
+            "action": "glibc_x64_alink",
+            # ELF-32 doesn't work on gVisor,
+            "remote": False,
+        },
+        {
+            "name": "nacl/glibc/x86_64_solink/gcc_solink_wrapper",
+            "action": "glibc_x64_solink",
+            # ELF-32 doesn't work on gVisor,
+            "remote": False,
+        },
+        # pnacl_newlib
+        {
             "name": "nacl/pnacl_newlib/x86_64-nacl-clang++",
             "action": "clang_newlib_x64_cxx",
             "inputs": [
@@ -120,6 +137,12 @@
             "timeout": "2m",
         },
         {
+            "name": "nacl/pnacl_newlib/x86_64-nacl-ar",
+            "action": "clang_newlib_x64_alink",
+            "remote": False,
+        },
+        # saigo_newlib
+        {
             "name": "nacl/saigo_newlib/x86_64-nacl-clang++",
             "action": "irt_x64_cxx",
             "command_prefix": "../../native_client/toolchain/linux_x86/saigo_newlib/bin/x86_64-nacl-clang++",
@@ -141,6 +164,11 @@
             "input_root_absolute_path": True,
             "timeout": "2m",
         },
+        {
+            "name": "nacl/saigo_newlib/x86_64-nacl-ar",
+            "action": "(.*_)?irt_x64_alink",
+            "remote": False,
+        },
     ])
 
     step_config["input_deps"].update({
diff --git a/build/config/siso/reproxy.star b/build/config/siso/reproxy.star
index 7a78208a..8b264b6f 100644
--- a/build/config/siso/reproxy.star
+++ b/build/config/siso/reproxy.star
@@ -283,11 +283,12 @@
             new_rules.append(new_rule)
             continue
 
-        # clang will always have rewrapper config when use_remoteexec=true.
+        # clang cxx/cc will always have rewrapper config when use_remoteexec=true.
         # Remove the native siso handling and replace with custom rewrapper-specific handling.
         # All other rule values are not reused, instead use rewrapper config via handler.
         # (In particular, command_prefix should be avoided because it will be rewrapper.)
-        if rule["name"].startswith("clang/") or rule["name"].startswith("clang-cl/"):
+        if (rule["name"].startswith("clang/cxx") or rule["name"].startswith("clang/cc") or
+            rule["name"].startswith("clang-cl/cxx") or rule["name"].startswith("clang-cl/cc")):
             if not rule.get("action"):
                 fail("clang rule %s found without action" % rule["name"])
 
diff --git a/build/fuchsia/test/flash_device.py b/build/fuchsia/test/flash_device.py
index 41eaeff4..c4fd13b 100755
--- a/build/fuchsia/test/flash_device.py
+++ b/build/fuchsia/test/flash_device.py
@@ -83,12 +83,16 @@
     # This prevents multiple fastboot binaries from flashing concurrently,
     # which should increase the odds of flashing success.
     with lock(_FF_LOCK, timeout=_FF_LOCK_ACQ_TIMEOUT):
+        # The ffx.fastboot.inline_target has negative impact when ffx
+        # discovering devices in fastboot, so it's inlined here to limit its
+        # scope. See the discussion in https://fxbug.dev/issues/317228141.
         logging.info(
             'Flash result %s',
             common.run_ffx_command(cmd=('target', 'flash', '-b',
                                         system_image_dir,
                                         '--no-bootloader-reboot'),
                                    target_id=target_id,
+                                   configs=['ffx.fastboot.inline_target=true'],
                                    capture_output=True).stdout)
 
 
diff --git a/build/fuchsia/test/isolate_daemon.py b/build/fuchsia/test/isolate_daemon.py
index 30d6dd6..9e260b17 100755
--- a/build/fuchsia/test/isolate_daemon.py
+++ b/build/fuchsia/test/isolate_daemon.py
@@ -47,7 +47,6 @@
             ScopedFfxConfig('fastboot.flash.timeout_rate', '1'),
             ScopedFfxConfig('fastboot.reboot.reconnect_timeout', '120'),
             ScopedFfxConfig('fastboot.usb.disabled', 'true'),
-            ScopedFfxConfig('ffx.fastboot.inline_target', 'true'),
             ScopedFfxConfig('log.level', 'debug'),
             ScopedFfxConfig('repository.server.listen', '"[::]:0"'),
         ] + (extra_inits or [])
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 798353c7..88fcca8a 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "9d119c1f4a097b7d27210874f4eba3fc91a83a4e"
+  libcxx_revision = "c51c9efb6c5a83e0e25c1e30864449d2beadd7a8"
 }
diff --git a/buildtools/reclient_cfgs/linux/chromium-browser-clang/rewrapper_linux.cfg b/buildtools/reclient_cfgs/linux/chromium-browser-clang/rewrapper_linux.cfg
index 37fca98..b2ec06d 100644
--- a/buildtools/reclient_cfgs/linux/chromium-browser-clang/rewrapper_linux.cfg
+++ b/buildtools/reclient_cfgs/linux/chromium-browser-clang/rewrapper_linux.cfg
@@ -1,7 +1,7 @@
 platform=container-image=docker://gcr.io/chops-public-images-prod/rbe/siso-chromium/linux@sha256:26de99218a1a8b527d4840490bcbf1690ee0b55c84316300b60776e6b3a03fe1,label:action_default=1
 server_address=unix:///tmp/reproxy.sock
 labels=type=compile,compiler=clang,lang=cpp
-exec_strategy=remote_local_fallback
+exec_strategy=racing
 dial_timeout=10m
 exec_timeout=2m
 reclient_timeout=2m
diff --git a/chrome/VERSION b/chrome/VERSION
index 835953f..9dd77b2 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=123
 MINOR=0
-BUILD=6288
+BUILD=6289
 PATCH=0
diff --git a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
index 70e7318..9dd7bc28 100644
--- a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
+++ b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
@@ -372,6 +372,10 @@
   getDistortionCoefficientsCount();
   getLeftEyeFieldOfViewAngles(int);
 }
+# Prevent native methods which are used from being obfuscated.
+-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class com.google.cardboard.sdk.** {
+  native <methods>;
+}
 
 # File: ../../android_webview/support_library/boundary_interfaces/proguard.flags
 # Copyright 2018 The Chromium Authors
diff --git a/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected b/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
index 17e22eee..354c3da6 100644
--- a/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
+++ b/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
@@ -372,6 +372,10 @@
   getDistortionCoefficientsCount();
   getLeftEyeFieldOfViewAngles(int);
 }
+# Prevent native methods which are used from being obfuscated.
+-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class com.google.cardboard.sdk.** {
+  native <methods>;
+}
 
 # File: ../../android_webview/support_library/boundary_interfaces/proguard.flags
 # Copyright 2018 The Chromium Authors
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
index 256c1020..93f8c95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -68,7 +68,9 @@
 import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.toolbar.top.TabStripTransitionCoordinator.TabStripHeightObserver;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
+import org.chromium.components.browser_ui.widget.scrim.ScrimProperties;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.PageTransition;
@@ -162,6 +164,7 @@
     private boolean mIsHidden;
     private boolean mIsTransitioning;
     private final ToolbarManager mToolbarManager;
+    private final StatusBarColorController mStatusBarColorController;
     private TabStripSceneLayer mTabStripTreeProvider;
     private TabStripEventHandler mTabStripEventHandler;
     private TabSwitcherLayoutObserver mTabSwitcherLayoutObserver;
@@ -452,6 +455,7 @@
         }
 
         mToolbarManager = toolbarManager;
+        mStatusBarColorController = mToolbarManager.getStatusBarColorController();
 
         mNormalHelper =
                 new StripLayoutHelper(
@@ -545,6 +549,7 @@
     /** Mark whether tab strip |isHidden|. */
     public void setIsTabStripHidden(boolean isHidden) {
         mIsHidden = isHidden;
+        mStatusBarColorController.setTabStripHiddenOnTablet(mIsHidden);
     }
 
     @Override
@@ -606,6 +611,8 @@
             // The fade-out is implemented by adding a scrim layer on top of the tab strip, with the
             // same bg as the toolbar background color.
             scrimOpacity = calculateScrimOpacityDuringTransition(visibleHeight);
+            mStatusBarColorController.setTabStripColorOverlay(
+                    getStripTransitionScrimColor(), scrimOpacity);
 
             yOffset = 0;
         } else if (mIsHidden) {
@@ -627,7 +634,7 @@
     }
 
     private int getStripTransitionScrimColor() {
-        if (!ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.getValue()) {
+        if (!ToolbarFeatures.shouldUseToolbarBgColorForStripTransitionScrim()) {
             return getBackgroundColor();
         }
         return mToolbarManager.getPrimaryColor();
@@ -676,11 +683,26 @@
     public void onHeightChanged(int newHeight) {
         mIsTransitioning = true;
         mIsHidden = newHeight == 0;
+        // Update the strip visibility state in StatusBarController just after the margins are
+        // updated during a hide->show transition so that the status bar assumes the base tab strip
+        // color for the remaining duration of the transition while a scrim is applied.
+        if (!mIsHidden) {
+            mStatusBarColorController.setTabStripHiddenOnTablet(false);
+        }
+        // Set the status bar color and scrim overlay at the start of the transition.
+        mStatusBarColorController.setTabStripColorOverlay(
+                getStripTransitionScrimColor(), mIsHidden ? 0f : 1f);
     }
 
     @Override
     public void onTransitionFinished() {
         mIsTransitioning = false;
+        //  Update the strip visibility state in StatusBarColorController only after a show->hide
+        // transition, so that the status bar assumes the toolbar color when the strip is hidden.
+        if (mIsHidden) {
+            mStatusBarColorController.setTabStripHiddenOnTablet(true);
+        }
+        mStatusBarColorController.setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
     }
 
     private boolean duringTabStripTransition() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 7e1c59e..17f158e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -109,6 +109,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
 import org.chromium.chrome.browser.tasks.tab_management.UndoGroupSnackbarController;
 import org.chromium.chrome.browser.toolbar.ToolbarButtonInProductHelpController;
+import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.ToolbarIntentMetadata;
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
@@ -395,6 +396,8 @@
                 };
         mMultiInstanceManager = multiInstanceManager;
         mHubManagerSupplier = hubManagerSupplier;
+        mStatusBarColorController.setAllowToolbarColorOnTablets(
+                ToolbarFeatures.shouldUseToolbarBgColorForStripTransitionScrim());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index b5ed2fa..e345fe8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -2117,6 +2117,13 @@
     }
 
     /**
+     * @return The {@link StatusBarColorController} instance maintained by this class.
+     */
+    public StatusBarColorController getStatusBarColorController() {
+        return mStatusBarColorController;
+    }
+
+    /**
      * Updates the primary color used by the model to the given color.
      * @param color The primary color for the current tab.
      * @param shouldAnimate Whether the change of color should be animated.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
index 73aaac8..7e946287 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
@@ -117,6 +117,12 @@
     private StartSurface.StateObserver mStartSurfaceStateObserver;
     private @StartSurfaceState int mStartSurfaceState = StartSurfaceState.NOT_SHOWN;
 
+    // Tab strip transition states.
+    private boolean mTabStripHiddenOnTablet;
+    private @ColorInt int mTabStripTransitionOverlayColor = ScrimProperties.INVALID_COLOR;
+    private float mTabStripTransitionOverlayAlpha;
+    private boolean mAllowToolbarColorOnTablets;
+
     private final LayoutStateObserver mLayoutStateObserver =
             new LayoutStateObserver() {
                 @Override
@@ -171,6 +177,7 @@
         mStatusBarColorProvider = statusBarColorProvider;
         mStartSurfaceSupplier = startSurfaceSupplier;
         mIsSurfacePolishEnabled = ChromeFeatureList.sSurfacePolish.isEnabled();
+        mAllowToolbarColorOnTablets = false;
 
         mStandardPrimaryBgColor = ChromeColors.getPrimaryBackgroundColor(context, false);
         mIncognitoPrimaryBgColor = ChromeColors.getPrimaryBackgroundColor(context, true);
@@ -372,12 +379,13 @@
     // TopToolbarCoordinator.ToolbarColorObserver implementation.
     @Override
     public void onToolbarColorChanged(@ColorInt int color) {
-        if (!OmniboxFeatures.shouldMatchToolbarAndStatusBarColor()) {
-            return;
-        }
-
-        // Status bar on tablets should not change at all times.
-        if (mIsTablet) {
+        boolean shouldUseToolbarColorOnPhone =
+                !mIsTablet && OmniboxFeatures.shouldMatchToolbarAndStatusBarColor();
+        // Status bar color on tablets could change when the DYNAMIC_TOP_CHROME feature is enabled,
+        // where it might be required for the status bar color to match the toolbar color when the
+        // tab strip is hidden.
+        boolean shouldUseToolbarColorOnTablet = mIsTablet && mAllowToolbarColorOnTablets;
+        if (!shouldUseToolbarColorOnPhone && !shouldUseToolbarColorOnTablet) {
             return;
         }
 
@@ -438,6 +446,20 @@
     }
 
     /**
+     * Add the tab strip transition scrim overlay on the status bar during a tab strip transition.
+     *
+     * @param overlayColor The overlay color.
+     * @param overlayAlpha The alpha that |overlayColor| should have on the status bar color.
+     */
+    public void setTabStripColorOverlay(@ColorInt int overlayColor, float overlayAlpha) {
+        assert mIsTablet;
+        if (!mAllowToolbarColorOnTablets) return;
+        mTabStripTransitionOverlayColor = overlayColor;
+        mTabStripTransitionOverlayAlpha = overlayAlpha;
+        updateStatusBarColor();
+    }
+
+    /**
      * @param tabModelSelector The {@link TabModelSelector} to check whether incognito model is
      *                         selected.
      */
@@ -456,11 +478,24 @@
         mStatusBarColorWithoutStatusIndicator = calculateBaseStatusBarColor();
         @ColorInt
         int statusBarColor = applyStatusBarIndicatorColor(mStatusBarColorWithoutStatusIndicator);
+        statusBarColor = applyTabStripOverlay(statusBarColor);
         statusBarColor = applyCurrentScrimToColor(statusBarColor);
         setStatusBarColor(mWindow, statusBarColor);
     }
 
     /**
+     * Set whether the tab strip is hidden or visible on a tablet. This state will be used to
+     * determine the base status bar color on a tablet.
+     *
+     * @param tabStripHiddenOnTablet Whether the tab strip is hidden or visible on a tablet.
+     */
+    public void setTabStripHiddenOnTablet(boolean tabStripHiddenOnTablet) {
+        assert mIsTablet;
+        if (!mAllowToolbarColorOnTablets) return;
+        mTabStripHiddenOnTablet = tabStripHiddenOnTablet;
+    }
+
+    /**
      * @return The status bar color without the status indicator's color taken into consideration.
      *         However, scrimming isn't included since it's managed completely by this class.
      */
@@ -480,7 +515,9 @@
         }
 
         if (mIsTablet) {
-            return TabUiThemeUtil.getTabStripBackgroundColor(mWindow.getContext(), mIsIncognito);
+            return mTabStripHiddenOnTablet
+                    ? mToolbarColor
+                    : TabUiThemeUtil.getTabStripBackgroundColor(mWindow.getContext(), mIsIncognito);
         }
 
         // When Omnibox gains focus, we want to clear the status bar theme color.
@@ -587,6 +624,31 @@
     }
 
     /**
+     * Apply and get the tab strip overlay applied color if the strip transition scrim is showing.
+     *
+     * @param color Base color to apply the overlay to.
+     */
+    private @ColorInt int applyTabStripOverlay(@ColorInt int color) {
+        // If the tab strip is transitioning on a tablet, apply the strip overlay on the status bar.
+        if (mIsTablet && mTabStripTransitionOverlayAlpha > 0) {
+            return ColorUtils.getColorWithOverlay(
+                    color, mTabStripTransitionOverlayColor, mTabStripTransitionOverlayAlpha);
+        }
+        return color;
+    }
+
+    /**
+     * Determines whether the status bar color could use the toolbar color on tablets when certain
+     * conditions are met. This is currently supported on ChromeTabbedActivity's with the
+     * DYNAMIC_TOP_CHROME feature enabled.
+     *
+     * @param allowToolbarColorOnTablets Whether the status bar color could use the toolbar color.
+     */
+    public void setAllowToolbarColorOnTablets(boolean allowToolbarColorOnTablets) {
+        mAllowToolbarColorOnTablets = allowToolbarColorOnTablets;
+    }
+
+    /**
      * @return Whether or not the current tab is a new tab page in standard mode.
      */
     private boolean isStandardNtp() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
index 8321410..86f4e26e7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
@@ -46,6 +46,7 @@
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeUtil;
+import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
 import org.chromium.chrome.features.start_surface.StartSurfaceTestUtils;
@@ -57,6 +58,7 @@
 import org.chromium.chrome.test.util.OmniboxTestUtils;
 import org.chromium.chrome.test.util.browser.ThemeTestUtils;
 import org.chromium.components.browser_ui.styles.ChromeColors;
+import org.chromium.components.browser_ui.widget.scrim.ScrimProperties;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
@@ -490,7 +492,7 @@
         UiRestriction.RESTRICTION_TYPE_TABLET,
         DeviceRestriction.RESTRICTION_TYPE_NON_AUTO
     })
-    public void testStatusBarColorForTabStripRedesignFolioTablet() throws Exception {
+    public void testStatusBarColorForTabStripRedesignFolioTablet() {
         final ChromeActivity activity = sActivityTestRule.getActivity();
         final StatusBarColorController statusBarColorController =
                 sActivityTestRule
@@ -498,12 +500,6 @@
                         .getRootUiCoordinatorForTesting()
                         .getStatusBarColorController();
 
-        // Before enable tab strip redesign, status bar should be black.
-        assertEquals(
-                "Wrong initial value returned before enable Tab Strip Redesign Folio",
-                Color.BLACK,
-                activity.getWindow().getStatusBarColor());
-
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> statusBarColorController.updateStatusBarColor());
         assertEquals(
@@ -512,6 +508,70 @@
                 activity.getWindow().getStatusBarColor());
     }
 
+    @Test
+    @LargeTest
+    @Feature({"StatusBar"})
+    @Restriction({
+        UiRestriction.RESTRICTION_TYPE_TABLET,
+        DeviceRestriction.RESTRICTION_TYPE_NON_AUTO
+    })
+    public void testStatusBarColorOnTabletDuringTabStripTransition() {
+        final ChromeActivity activity = sActivityTestRule.getActivity();
+        final StatusBarColorController statusBarColorController =
+                sActivityTestRule
+                        .getActivity()
+                        .getRootUiCoordinatorForTesting()
+                        .getStatusBarColorController();
+        statusBarColorController.setAllowToolbarColorOnTablets(true);
+
+        var toolbarColor = sActivityTestRule.getActivity().getToolbarManager().getPrimaryColor();
+
+        // We will invoke #onToolbarColorChanged() on a tablet that in turn invokes
+        // #updateStatusBarColor() to assert that it sets |mToolbarColor| as expected. The status
+        // bar should use |mToolbarColor| when the tab strip is hidden, and the tab strip background
+        // color otherwise.
+
+        // Assume that the tab strip is initially hidden.
+        statusBarColorController.setTabStripHiddenOnTablet(true);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> statusBarColorController.onToolbarColorChanged(toolbarColor));
+        assertEquals(
+                "Status bar color on tablet should match the toolbar background when the tab strip"
+                        + " is hidden.",
+                toolbarColor,
+                activity.getWindow().getStatusBarColor());
+
+        // Simulate an in-progress hide->show transition, where a scrim will be added on the status
+        // bar.
+        // TabStripHeightObserver#onHeightChanged() is expected to update the final strip visibility
+        // state in StatusBarColorController for this transition once the control container margins
+        // are updated and before the transition runs to completion.
+        statusBarColorController.setTabStripHiddenOnTablet(false);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> statusBarColorController.setTabStripColorOverlay(toolbarColor, 0.5f));
+        assertEquals(
+                "Status bar color on tablet should use the tab strip transition scrim overlay"
+                        + " during a strip transition.",
+                ColorUtils.getColorWithOverlay(
+                        TabUiThemeUtil.getTabStripBackgroundColor(activity, false),
+                        toolbarColor,
+                        0.5f),
+                activity.getWindow().getStatusBarColor());
+
+        // Simulate transition completion by resetting the transition overlay state in
+        // StatusBarColorController.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () ->
+                        statusBarColorController.setTabStripColorOverlay(
+                                ScrimProperties.INVALID_COLOR, 0f));
+        assertEquals(
+                "Status bar color on tablet should match the tab strip background when the tab"
+                        + " strip is visible.",
+                TabUiThemeUtil.getTabStripBackgroundColor(activity, false),
+                activity.getWindow().getStatusBarColor());
+        ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.setForTesting(false);
+    }
+
     /** Test status bar is always black in Automotive devices. */
     @Test
     @SmallTest
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
index 394fd74..8095a52 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
@@ -14,6 +14,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -37,7 +38,9 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
@@ -72,8 +75,10 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
+import org.chromium.components.browser_ui.widget.scrim.ScrimProperties;
 import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.dragdrop.DragAndDropDelegate;
@@ -108,6 +113,7 @@
     @Mock private BrowserControlsStateProvider mBrowserControlStateProvider;
     @Mock private WindowAndroid mWindowAndroid;
     @Mock private ToolbarManager mToolbarManager;
+    @Mock private StatusBarColorController mStatusBarColorController;
 
     private StripLayoutHelperManager mStripLayoutHelperManager;
     private Context mContext;
@@ -130,6 +136,8 @@
                         ApplicationProvider.getApplicationContext(),
                         R.style.Theme_BrowserUI_DayNight);
         when(mToolbarContainerView.getContext()).thenReturn(mContext);
+        when(mToolbarManager.getStatusBarColorController()).thenReturn(mStatusBarColorController);
+
         TabStripSceneLayer.setTestFlag(true);
         ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.setForTesting(true);
 
@@ -608,6 +616,16 @@
                         anyInt(),
                         eq(mToolbarPrimaryColor),
                         /* scrimOpacity= */ eq(0f));
+
+        // Verify StatusBarColorController method invocations.
+        InOrder inOrder = Mockito.inOrder(mStatusBarColorController);
+        // Invocation during the transition.
+        inOrder.verify(mStatusBarColorController)
+                .setTabStripColorOverlay(mToolbarPrimaryColor, expectedOpacity);
+        // Invocation after the transition finished.
+        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);
+        inOrder.verify(mStatusBarColorController)
+                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
     }
 
     @Test
@@ -656,11 +674,14 @@
         mStripLayoutHelperManager.setIsTabStripHidden(true);
         mStripLayoutHelperManager.getVirtualViews(views);
         assertTrue("Views are empty when tab strip hidden.", views.isEmpty());
+        verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);
 
         mStripLayoutHelperManager.setIsTabStripHidden(false);
         mStripLayoutHelperManager.onHeightChanged(40);
         mStripLayoutHelperManager.getVirtualViews(views);
         assertTrue("Views are empty during tab strip transition.", views.isEmpty());
+        // Invoked once by #setIsTabStripHidden(), once by #onHeightChanged().
+        verify(mStatusBarColorController, times(2)).setTabStripHiddenOnTablet(false);
 
         mStripLayoutHelperManager.onTransitionFinished();
         mStripLayoutHelperManager.getVirtualViews(views);
@@ -728,5 +749,19 @@
                         anyInt(),
                         eq(scrimColor),
                         /* scrimOpacity= */ eq(0f));
+
+        // Verify StatusBarColorController method invocations.
+        InOrder inOrder = Mockito.inOrder(mStatusBarColorController);
+        // Invocations before the transition started.
+        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);
+        inOrder.verify(mStatusBarColorController)
+                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
+        // Invocations during the transition.
+        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(false);
+        inOrder.verify(mStatusBarColorController)
+                .setTabStripColorOverlay(scrimColor, expectedOpacity);
+        // Invocation after the transition finished.
+        inOrder.verify(mStatusBarColorController)
+                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
     }
 }
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 0337c31..e379d74a 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -7072,20 +7072,26 @@
     Can't upload files while on a metered connection.
   </message>
   <message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED" desc="Office Fallback dialog instructions for the choices (and buttons) the user has available to open the office file when the system is on a metered network (network where bandwidth usage incurs cost to the user).">
-    Check your internet connection and choose "Try again", or choose "Open in offline editor" to use limited view and editing options.
+    Change your internet connection and choose "Try again", or choose "Open in offline editor" to use limited view and editing options.
   </message>
   <message name="IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE" desc="Title for a dialog that appears when an office file is unable to be opened due to Google Drive not being available.">
-    Can't open <ph name="FILE_NAMES">$1<ex>file.docx</ex></ph> when Drive is not available
+    Can't open <ph name="FILE_NAMES">$1<ex>file.docx</ex></ph> when Google Drive is not available
   </message>
   <message name="IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE" desc="Explanation for a dialog that appears when an office file is unable to be opened due to Google Drive not being available.">
-    The application <ph name="APPLICATION_NAME">$1<ex>Google Docs</ex></ph> requires Drive to be available.
+    The application <ph name="APPLICATION_NAME">$1<ex>Google Docs</ex></ph> requires Google Drive to be available.
+  </message>
+  <message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE" desc="Office Fallback dialog instructions for the choices (buttons) the user has available when Google Drive is disabled in settings.">
+    Try enabling Google Drive in Settings and choose "Try again", or choose "Open in offline editor" to use limited view and editing options.
+  </message>
+  <message name="IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT" desc="Explanation for a dialog that appears when Google Drive is not available for the account type.">
+    Google Drive is disabled for this account type.
+  </message>
+  <message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT" desc="Office Fallback dialog instructions for the choices (buttons) the user has available when Google Drive is not available for the account type.">
+    Choose "Open in offline editor" to use limited view and editing options.
   </message>
   <message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS" desc="Office Fallback dialog instructions for the choices (buttons) the user has available to open the office file.">
     Choose "Try again", or choose "Open in offline editor" to use limited view and editing options.
   </message>
-  <message name="IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_UNAVAILABLE" desc="Office Fallback dialog instructions for when Google Drive is not available in Files app and the user should try to enable it." translateable="false">
-    Try enabling Google Drive in Settings.
-  </message>
 
   <!-- Office file handler selection dialog -->
   <message name="IDS_OFFICE_FILE_HANDLER_TITLE" desc="Title for office file handler selection dialog, which allows the user to choose an app to open office files.">
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE.png.sha1
new file mode 100644
index 0000000..cb34f16
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE.png.sha1
@@ -0,0 +1 @@
+a58b3567aaca00dece5d3212807e1b4c3f482c4a
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1
new file mode 100644
index 0000000..8bc62c7
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+35ba6a1fde6a00f620752f91bc508c2cf3f1fbcc
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED.png.sha1
index 32db01e..fa61c0fe 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED.png.sha1
@@ -1 +1 @@
-3bc34a3a5fd0a52711481b8c2cfa33f144a4d74d
\ No newline at end of file
+8878e19bbb684dab2ee4b26528c5b67a78e200df
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1
new file mode 100644
index 0000000..6d6109d
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT.png.sha1
@@ -0,0 +1 @@
+52a364dc1133941cd03d1171c3cc6f3675167f80
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE.png.sha1
index 920b5bc2..7143eaff 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE.png.sha1
@@ -1 +1 @@
-a83d60c4feaf2c589b5a889d7181f5b592bc9f2d
\ No newline at end of file
+199a0514431077ab8723a546d4c63dbd917ca611
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE.png.sha1
index 7634320..ef2d015d 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE.png.sha1
@@ -1 +1 @@
-020bf1d2cdbf68cf19bbfc4e504d66aec2fdfe91
\ No newline at end of file
+1bf34ac666d2a8c43a5dcee1fc9c9597275861ca
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 61353e1..6169026 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4342,9 +4342,6 @@
     {"disable-dns-proxy", flag_descriptions::kDisableDnsProxyName,
      flag_descriptions::kDisableDnsProxyDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kDisableDnsProxy)},
-    {"firmware-update-jelly", flag_descriptions::kFirmwareUpdateJellyName,
-     flag_descriptions::kFirmwareUpdateJellyDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kFirmwareUpdateJelly)},
     {"firmware-update-ui-v2", flag_descriptions::kFirmwareUpdateUIV2Name,
      flag_descriptions::kFirmwareUpdateUIV2Description, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kFirmwareUpdateUIV2)},
@@ -5828,6 +5825,11 @@
     {"files-trash-drive", flag_descriptions::kFilesTrashDriveName,
      flag_descriptions::kFilesTrashDriveDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kFilesTrashDrive)},
+    {"file-system-provider-cloud-file-system",
+     flag_descriptions::kFileSystemProviderCloudFileSystemName,
+     flag_descriptions::kFileSystemProviderCloudFileSystemDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(
+         chromeos::features::kFileSystemProviderCloudFileSystem)},
     {"file-system-provider-content-cache",
      flag_descriptions::kFileSystemProviderContentCacheName,
      flag_descriptions::kFileSystemProviderContentCacheDescription, kOsCrOS,
@@ -5901,9 +5903,6 @@
      flag_descriptions::kPhoneHubOnboardingNotifierRevampName,
      flag_descriptions::kPhoneHubOnboardingNotifierRevampDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kPhoneHubOnboardingNotifierRevamp)},
-    {"print-management-jelly", flag_descriptions::kPrintManagementJellyName,
-     flag_descriptions::kPrintManagementJellyDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kPrintManagementJelly)},
     {"print-management-setup-assistance",
      flag_descriptions::kPrintManagementSetupAssistanceName,
      flag_descriptions::kPrintManagementSetupAssistanceDescription, kOsCrOS,
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index b06b3fa..0f07a311 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1639,6 +1639,10 @@
     "kerberos/kerberos_credentials_manager_factory.h",
     "kerberos/kerberos_ticket_expiry_notification.cc",
     "kerberos/kerberos_ticket_expiry_notification.h",
+    "language_packs/language_pack_font_service.cc",
+    "language_packs/language_pack_font_service.h",
+    "language_packs/language_pack_font_service_factory.cc",
+    "language_packs/language_pack_font_service_factory.h",
     "language_preferences.cc",
     "language_preferences.h",
     "locale_change_guard.cc",
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index 5bd7417..74f72b89 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -2334,7 +2334,7 @@
 
   // TODO(crbug.com/1360638): Remove the conditional here when the Save & Recall
   // flag flip has landed since it will always be true.
-  if (saved_desk_util::IsSavedDesksEnabled()) {
+  if (saved_desk_util::ShouldShowSavedDesksButtons()) {
     sm_.Call([this]() { SendKeyPressWithShift(ui::VKEY_TAB); });
     sm_.ExpectSpeechPattern("Save desk for later");
     sm_.ExpectSpeech("Button");
diff --git a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
index 233526c..83f1e9c8 100644
--- a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/ash/app_list/search/app_discovery_metrics_manager.h"
 
+#include <utility>
+
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "chrome/browser/ash/app_list/search/common/types_util.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chromeos/ash/components/string_matching/fuzzy_tokenized_string_match.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "components/metrics/structured/structured_events.h"
+#include "components/metrics/structured/structured_metrics_client.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/service/sync_service.h"
 #include "components/sync/service/sync_service_utils.h"
@@ -51,16 +54,17 @@
   std::string app_id = IsAppSyncEnabled() ? result->id() : "";
   std::u16string app_title = IsAppSyncEnabled() ? result->title() : u"";
 
-  cros_events::AppDiscovery_AppLauncherResultOpened()
-      .SetAppId(app_id)
-      .SetAppName(std::string(app_title.begin(), app_title.end()))
-      .SetFuzzyStringMatch(string_match_score)
-      .SetResultCategory(result->metrics_type())
-      .Record();
+  metrics::structured::StructuredMetricsClient::Record(
+      std::move(cros_events::AppDiscovery_AppLauncherResultOpened()
+                    .SetAppId(app_id)
+                    .SetAppName(std::string(app_title.begin(), app_title.end()))
+                    .SetFuzzyStringMatch(string_match_score)
+                    .SetResultCategory(result->metrics_type())));
 }
 
 void AppDiscoveryMetricsManager::OnLauncherOpen() {
-  cros_events::AppDiscovery_LauncherOpen().Record();
+  metrics::structured::StructuredMetricsClient::Record(
+      std::move(cros_events::AppDiscovery_LauncherOpen()));
 }
 
 bool AppDiscoveryMetricsManager::IsAppSyncEnabled() {
diff --git a/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc b/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
index d0f2b3bb..811f7f23 100644
--- a/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
@@ -6,13 +6,10 @@
 
 #include <memory>
 
-#include "ash/constants/ash_features.h"
 #include "ash/webui/help_app_ui/search/search_handler.h"
 #include "ash/webui/help_app_ui/search/search_tag_registry.h"
 #include "ash/webui/help_app_ui/url_constants.h"
-#include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/app_list/app_list_test_util.h"
@@ -84,11 +81,7 @@
                 /*for_testing=*/true)),
         search_tag_registry_(local_search_service_proxy_.get()),
         mock_handler_(&search_tag_registry_,
-                      local_search_service_proxy_.get()) {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{ash::features::kReleaseNotesSuggestionChip},
-        /*disabled_features=*/{});
-  }
+                      local_search_service_proxy_.get()) {}
   ~HelpAppProviderTest() override = default;
 
   void SetUp() override {
@@ -126,7 +119,6 @@
   ash::help_app::SearchTagRegistry search_tag_registry_;
   raw_ptr<HelpAppProvider> provider_ = nullptr;
   raw_ptr<apps::AppServiceProxy> proxy_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   MockSearchHandler mock_handler_;
 };
 
diff --git a/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc b/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc
index f2a0ba6..0042f02c 100644
--- a/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc
@@ -7,11 +7,8 @@
 #include <memory>
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/app_list/app_list_notifier_impl.h"
 #include "chrome/browser/ash/app_list/app_list_test_util.h"
 #include "chrome/browser/ash/app_list/search/chrome_search_result.h"
@@ -43,11 +40,7 @@
 
 class HelpAppZeroStateProviderTest : public AppListTestBase {
  public:
-  HelpAppZeroStateProviderTest() {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{ash::features::kReleaseNotesSuggestionChip},
-        /*disabled_features=*/{});
-  }
+  HelpAppZeroStateProviderTest() = default;
   ~HelpAppZeroStateProviderTest() override = default;
 
   void SetUp() override {
@@ -81,7 +74,6 @@
   std::unique_ptr<ash::AppListNotifier> app_list_notifier_;
   TestSearchController search_controller_;
   raw_ptr<HelpAppZeroStateProvider> provider_ = nullptr;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Test for empty query.
diff --git a/chrome/browser/ash/app_list/search/search_metrics_manager.cc b/chrome/browser/ash/app_list/search/search_metrics_manager.cc
index 7c0f5b7b..68f9908 100644
--- a/chrome/browser/ash/app_list/search/search_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_manager.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/drive/drive_pref_names.h"
 #include "components/metrics/structured/structured_events.h"
+#include "components/metrics/structured/structured_metrics_client.h"
 #include "components/prefs/pref_service.h"
 
 namespace app_list {
@@ -214,15 +215,15 @@
   base::Time::Exploded now_exploded;
   now.LocalExplode(&now_exploded);
 
-  metrics::structured::events::v2::launcher_usage::LauncherUsage()
-      .SetTarget(NormalizeId(launch_data.id))
-      .SetApp(last_launched_app_id_)
-      .SetSearchQuery(query)
-      .SetSearchQueryLength(query.size())
-      .SetProviderType(static_cast<int>(launch_data.result_type))
-      .SetHour(now_exploded.hour)
-      .SetScore(launch_data.score)
-      .Record();
+  metrics::structured::StructuredMetricsClient::Record(
+      std::move(metrics::structured::events::v2::launcher_usage::LauncherUsage()
+                    .SetTarget(NormalizeId(launch_data.id))
+                    .SetApp(last_launched_app_id_)
+                    .SetSearchQuery(query)
+                    .SetSearchQueryLength(query.size())
+                    .SetProviderType(static_cast<int>(launch_data.result_type))
+                    .SetHour(now_exploded.hour)
+                    .SetScore(launch_data.score)));
 
   // Only record the last launched app if the hashed logging feature flag is
   // enabled, because it is only used by hashed logging.
diff --git a/chrome/browser/ash/arc/intent_helper/arc_settings_service_unittest.cc b/chrome/browser/ash/arc/intent_helper/arc_settings_service_unittest.cc
index f8eda90..9e9bcbf 100644
--- a/chrome/browser/ash/arc/intent_helper/arc_settings_service_unittest.cc
+++ b/chrome/browser/ash/arc/intent_helper/arc_settings_service_unittest.cc
@@ -26,7 +26,6 @@
 #include "chrome/browser/ash/arc/session/arc_provisioning_result.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
 #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h"
 #include "chrome/common/pref_names.h"
@@ -40,9 +39,8 @@
 #include "components/language/core/browser/pref_names.h"
 #include "components/live_caption/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
 #include "components/prefs/testing_pref_store.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace arc {
@@ -63,8 +61,7 @@
 
 class ArcSettingsServiceTest : public BrowserWithTestWindowTest {
  public:
-  ArcSettingsServiceTest()
-      : fake_user_manager_(std::make_unique<ash::FakeChromeUserManager>()) {}
+  ArcSettingsServiceTest() = default;
   ArcSettingsServiceTest(const ArcSettingsServiceTest&) = delete;
   ArcSettingsServiceTest& operator=(const ArcSettingsServiceTest&) = delete;
   ~ArcSettingsServiceTest() override = default;
@@ -101,11 +98,6 @@
                   base_name);
             }));
 
-    const AccountId account_id(AccountId::FromUserEmailGaiaId(
-        profile()->GetProfileUserName(), "1234567890"));
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-
     arc_session_manager()->SetProfile(profile());
     arc_session_manager()->Initialize();
 
@@ -177,8 +169,6 @@
   std::unique_ptr<ash::network_config::CrosNetworkConfigTestHelper>
       network_config_helper_;
   TestingPrefServiceSimple local_state_;
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_;
   std::unique_ptr<FakeIntentHelperHost> intent_helper_host_;
   std::unique_ptr<ArcSessionManager> arc_session_manager_;
   std::unique_ptr<ArcServiceManager> arc_service_manager_;
diff --git a/chrome/browser/ash/arc/notification/arc_provision_notification_service_unittest.cc b/chrome/browser/ash/arc/notification/arc_provision_notification_service_unittest.cc
index 0565d433..e1e9cac 100644
--- a/chrome/browser/ash/arc/notification/arc_provision_notification_service_unittest.cc
+++ b/chrome/browser/ash/arc/notification/arc_provision_notification_service_unittest.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
 #include "chrome/browser/ash/login/ui/fake_login_display_host.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
@@ -31,8 +30,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
 #include "ui/message_center/public/cpp/notification.h"
 
 namespace arc {
@@ -43,9 +40,7 @@
 
 class ArcProvisionNotificationServiceTest : public BrowserWithTestWindowTest {
  protected:
-  ArcProvisionNotificationServiceTest()
-      : fake_user_manager_(std::make_unique<ash::FakeChromeUserManager>()) {}
-
+  ArcProvisionNotificationServiceTest() = default;
   ArcProvisionNotificationServiceTest(
       const ArcProvisionNotificationServiceTest&) = delete;
   ArcProvisionNotificationServiceTest& operator=(
@@ -84,11 +79,6 @@
 
     arc::prefs::RegisterLocalStatePrefs(local_state_.registry());
     arc::StabilityMetricsManager::Initialize(&local_state_);
-
-    const AccountId account_id(AccountId::FromUserEmailGaiaId(
-        profile()->GetProfileUserName(), "1234567890"));
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
   }
 
   void TearDown() override {
@@ -112,8 +102,6 @@
   raw_ptr<session_manager::SessionManager> session_manager_;
 
  private:
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_;
   TestingPrefServiceSimple local_state_;
 };
 
diff --git a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
index de9fe48..b8c0bb4 100644
--- a/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
+++ b/chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator_unittest.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/ash/arc/arc_support_host.h"
 #include "chrome/browser/ash/arc/extensions/fake_arc_support.h"
 #include "chrome/browser/ash/arc/optin/arc_optin_preference_handler.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/settings/device_settings_test_helper.h"
@@ -44,7 +43,6 @@
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -164,7 +162,6 @@
     fake_arc_support_.reset();
     support_host_->SetErrorDelegate(nullptr);
     support_host_.reset();
-    fake_user_manager_.Reset();
     owner_key_util_->Clear();
 
     test_metrics_service_.reset();
@@ -191,10 +188,6 @@
     return test_metrics_service_client_.get();
   }
 
-  ash::FakeChromeUserManager* user_manager() {
-    return fake_user_manager_.Get();
-  }
-
   consent_auditor::FakeConsentAuditor* consent_auditor() {
     return static_cast<consent_auditor::FakeConsentAuditor*>(
         ConsentAuditorFactory::GetForProfile(profile()));
@@ -230,8 +223,6 @@
       test_enabled_state_provider_;
   std::unique_ptr<metrics::MetricsService> test_metrics_service_;
 
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_{std::make_unique<ash::FakeChromeUserManager>()};
   std::unique_ptr<ArcSupportHost> support_host_;
   std::unique_ptr<FakeArcSupport> fake_arc_support_;
   std::unique_ptr<ArcTermsOfServiceDefaultNegotiator> negotiator_;
@@ -253,14 +244,6 @@
 
     ArcTermsOfServiceDefaultNegotiatorTest::SetUp();
   }
-
-  // BrowserWithTestWindowTest:
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindow.
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    user_manager()->AddUser(account_id);
-    user_manager()->LoginUser(account_id);
-  }
 };
 
 namespace {
diff --git a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
index 7b8dbcc..06390d5 100644
--- a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
+++ b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
@@ -144,17 +144,6 @@
     BrowserWithTestWindowTest::TearDown();
   }
 
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-    AccountId account_id = AccountId::FromUserEmail(email);
-    user_manager()->AddUser(account_id);
-    user_manager()->UserLoggedIn(
-        account_id,
-        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
-        /*browser_restart=*/false,
-        /*is_child=*/false);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto* profile = BrowserWithTestWindowTest::CreateProfile(profile_name);
     auto* user = user_manager()->FindUserAndModify(
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index 9ce62c1..21085f2 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -401,6 +401,7 @@
     "//components/session_manager/core",
     "//components/ukm:ukm",
     "//components/user_prefs:user_prefs",
+    "//components/variations/service",
     "//components/version_info:channel",
     "//content/public/common",
     "//extensions/browser/api",
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index 017b894..9f87160 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -169,6 +169,7 @@
 #include "components/ukm/ukm_service.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
+#include "components/variations/service/limited_entropy_synthetic_trial.h"
 #include "components/version_info/version_info.h"
 #include "content/public/common/content_switches.h"
 #include "device/bluetooth/floss/floss_features.h"
@@ -560,6 +561,15 @@
 
   params->device_properties = GetDeviceProperties();
   params->device_settings = GetDeviceSettings();
+
+  // Syncing the randomization seed ensures that the group membership of the
+  // limited entropy synthetic trial will be the same between Ash Chrome and
+  // Lacros.
+  // TODO(crbug.com/1508150): Remove after completing the trial.
+  params->limited_entropy_synthetic_trial_seed =
+      variations::LimitedEntropySyntheticTrial::GetRandomizationSeed(
+          local_state);
+
   // |metrics_service| could be nullptr in tests.
   if (auto* metrics_service = g_browser_process->metrics_service()) {
     // Send metrics service client id to Lacros if it's present.
diff --git a/chrome/browser/ash/file_manager/office_file_tasks.cc b/chrome/browser/ash/file_manager/office_file_tasks.cc
index fe393ab..dccbf8b 100644
--- a/chrome/browser/ash/file_manager/office_file_tasks.cc
+++ b/chrome/browser/ash/file_manager/office_file_tasks.cc
@@ -76,60 +76,6 @@
   return OfficeOpenExtensions::kOther;
 }
 
-void LogOneDriveMetricsAfterFallback(
-    ash::office_fallback::FallbackReason fallback_reason,
-    ash::cloud_upload::OfficeTaskResult task_result,
-    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
-  switch (fallback_reason) {
-    case ash::office_fallback::FallbackReason::kOffline:
-      cloud_open_metrics->LogOneDriveOpenError(
-          ash::cloud_upload::OfficeOneDriveOpenErrors::kOffline);
-      break;
-    case ash::office_fallback::FallbackReason::kDriveDisabled:
-    case ash::office_fallback::FallbackReason::kNoDriveService:
-    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
-    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
-    case ash::office_fallback::FallbackReason::kMeteredConnection:
-      NOTREACHED();
-      break;
-  }
-  cloud_open_metrics->LogTaskResult(task_result);
-}
-
-void LogGoogleDriveMetricsAfterFallback(
-    ash::office_fallback::FallbackReason fallback_reason,
-    ash::cloud_upload::OfficeTaskResult task_result,
-    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
-  switch (fallback_reason) {
-    case ash::office_fallback::FallbackReason::kOffline:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::kOffline);
-      break;
-    case ash::office_fallback::FallbackReason::kDriveDisabled:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::kDriveDisabled);
-      break;
-    case ash::office_fallback::FallbackReason::kNoDriveService:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::kNoDriveService);
-      break;
-    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::
-              kDriveAuthenticationNotReady);
-      break;
-    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::kDriveFsInterface);
-      break;
-    case ash::office_fallback::FallbackReason::kMeteredConnection:
-      cloud_open_metrics->LogGoogleDriveOpenError(
-          ash::cloud_upload::OfficeDriveOpenErrors::kMeteredConnection);
-      break;
-  }
-  cloud_open_metrics->LogTaskResult(task_result);
-}
-
 std::optional<ash::office_fallback::FallbackReason>
 DriveConnectionStatusToFallbackReason(
     drive::util::ConnectionStatus drive_connection_status) {
@@ -269,6 +215,71 @@
   return;
 }
 
+void LogOneDriveMetricsAfterFallback(
+    ash::office_fallback::FallbackReason fallback_reason,
+    ash::cloud_upload::OfficeTaskResult task_result,
+    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
+  switch (fallback_reason) {
+    case ash::office_fallback::FallbackReason::kOffline:
+      cloud_open_metrics->LogOneDriveOpenError(
+          ash::cloud_upload::OfficeOneDriveOpenErrors::kOffline);
+      break;
+    case ash::office_fallback::FallbackReason::kDriveDisabled:
+    case ash::office_fallback::FallbackReason::kNoDriveService:
+    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
+    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
+    case ash::office_fallback::FallbackReason::kMeteredConnection:
+    case ash::office_fallback::FallbackReason::kDisableDrivePreferenceSet:
+    case ash::office_fallback::FallbackReason::kDriveDisabledForAccountType:
+      NOTREACHED();
+      break;
+  }
+  cloud_open_metrics->LogTaskResult(task_result);
+}
+
+void LogGoogleDriveMetricsAfterFallback(
+    ash::office_fallback::FallbackReason fallback_reason,
+    ash::cloud_upload::OfficeTaskResult task_result,
+    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
+  switch (fallback_reason) {
+    case ash::office_fallback::FallbackReason::kOffline:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kOffline);
+      break;
+    case ash::office_fallback::FallbackReason::kDriveDisabled:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kDriveDisabled);
+      break;
+    case ash::office_fallback::FallbackReason::kNoDriveService:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kNoDriveService);
+      break;
+    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::
+              kDriveAuthenticationNotReady);
+      break;
+    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kDriveFsInterface);
+      break;
+    case ash::office_fallback::FallbackReason::kMeteredConnection:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kMeteredConnection);
+      break;
+    case ash::office_fallback::FallbackReason::kDisableDrivePreferenceSet:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::kDisableDrivePreferenceSet);
+      break;
+    case ash::office_fallback::FallbackReason::kDriveDisabledForAccountType:
+      cloud_open_metrics->LogGoogleDriveOpenError(
+          ash::cloud_upload::OfficeDriveOpenErrors::
+              kDriveDisabledForAccountType);
+      break;
+  }
+  cloud_open_metrics->LogTaskResult(task_result);
+}
+
 void OnDialogChoiceReceived(
     Profile* profile,
     const TaskDescriptor& task,
diff --git a/chrome/browser/ash/file_manager/office_file_tasks.h b/chrome/browser/ash/file_manager/office_file_tasks.h
index b5f1db1..e3445c2 100644
--- a/chrome/browser/ash/file_manager/office_file_tasks.h
+++ b/chrome/browser/ash/file_manager/office_file_tasks.h
@@ -11,6 +11,7 @@
 
 #include "base/files/file_path.h"
 #include "base/time/time.h"
+#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
 #include "chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h"
 
 class Profile;
@@ -121,6 +122,16 @@
 void LaunchQuickOffice(Profile* profile,
                        const std::vector<storage::FileSystemURL>& file_urls);
 
+void LogOneDriveMetricsAfterFallback(
+    ash::office_fallback::FallbackReason fallback_reason,
+    ash::cloud_upload::OfficeTaskResult task_result,
+    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics);
+
+void LogGoogleDriveMetricsAfterFallback(
+    ash::office_fallback::FallbackReason fallback_reason,
+    ash::cloud_upload::OfficeTaskResult task_result,
+    std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics);
+
 // Executes appropriate task to open the selected `file_urls`.
 // If user's `choice` is `kDialogChoiceQuickOffice`, launch QuickOffice.
 // If user's `choice` is `kDialogChoiceTryAgain`, execute the `task`.
diff --git a/chrome/browser/ash/file_manager/office_file_tasks_unittest.cc b/chrome/browser/ash/file_manager/office_file_tasks_unittest.cc
index 0fbb91a0..5dd7af33 100644
--- a/chrome/browser/ash/file_manager/office_file_tasks_unittest.cc
+++ b/chrome/browser/ash/file_manager/office_file_tasks_unittest.cc
@@ -7,10 +7,13 @@
 #include <memory>
 #include <string>
 
+#include "base/test/metrics/histogram_tester.h"
 #include "base/values.h"
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/file_manager/file_tasks.h"
 #include "chrome/browser/ash/file_manager/office_file_tasks.h"
+#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.h"
+#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -44,6 +47,19 @@
     profile()->GetTestingPrefService()->ClearPref(prefs::kDefaultTasksBySuffix);
   }
 
+ protected:
+  std::unique_ptr<ash::cloud_upload::CloudOpenMetrics>
+      cloud_open_metrics_for_drive_ =
+          std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
+              ash::cloud_upload::CloudProvider::kGoogleDrive,
+              /*file_count=*/1);
+  std::unique_ptr<ash::cloud_upload::CloudOpenMetrics>
+      cloud_open_metrics_for_one_drive_ =
+          std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
+              ash::cloud_upload::CloudProvider::kOneDrive,
+              /*file_count=*/1);
+  base::HistogramTester histogram_;
+
  private:
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> profile_;
@@ -269,4 +285,127 @@
       *profile()->GetPrefs(), pptx_mime, ".pptx"));
 }
 
+/**
+ * Check Log*MetricsAfterFallback() maps the FallbackReason to the correct
+ * OpenError and logs the TaskResult.
+ */
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogOneDriveMetricsAfterFallback_kOffline) {
+  LogOneDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kOffline,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_one_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kOneDriveErrorMetricName,
+      ash::cloud_upload::OfficeOneDriveOpenErrors::kOffline, 1);
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kOneDriveTaskResultMetricName,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kOffline) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kOffline,
+      ash::cloud_upload::OfficeTaskResult::kFallbackQuickOffice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kOffline, 1);
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kGoogleDriveTaskResultMetricName,
+      ash::cloud_upload::OfficeTaskResult::kFallbackQuickOffice, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kDriveDisabled) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kDriveDisabled,
+      ash::cloud_upload::OfficeTaskResult::kCancelledAtFallback,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kDriveDisabled, 1);
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kGoogleDriveTaskResultMetricName,
+      ash::cloud_upload::OfficeTaskResult::kCancelledAtFallback, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kNoDriveService) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kNoDriveService,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kNoDriveService, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kDriveAuthenticationNotReady) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kDriveAuthenticationNotReady,
+      1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kDriveFsInterfaceError) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kDriveFsInterfaceError,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kDriveFsInterface, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kMeteredConnection) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kMeteredConnection,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kMeteredConnection, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kDisableDrivePreferenceSet) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kDisableDrivePreferenceSet,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kDisableDrivePreferenceSet, 1);
+}
+
+TEST_F(FileManagerOfficeFileTasksTest,
+       LogGoogleDriveMetricsAfterFallback_kDriveDisabledForAccountType) {
+  LogGoogleDriveMetricsAfterFallback(
+      ash::office_fallback::FallbackReason::kDriveDisabledForAccountType,
+      ash::cloud_upload::OfficeTaskResult::kCannotGetFallbackChoice,
+      std::move(cloud_open_metrics_for_drive_));
+
+  histogram_.ExpectUniqueSample(
+      ash::cloud_upload::kDriveErrorMetricName,
+      ash::cloud_upload::OfficeDriveOpenErrors::kDriveDisabledForAccountType,
+      1);
+}
+
 }  // namespace file_manager::file_tasks
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_system.cc b/chrome/browser/ash/file_system_provider/cloud_file_system.cc
index 622fcf6..87d7a03 100644
--- a/chrome/browser/ash/file_system_provider/cloud_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/cloud_file_system.cc
@@ -77,6 +77,10 @@
 }  // namespace
 
 CloudFileSystem::CloudFileSystem(
+    std::unique_ptr<ProvidedFileSystemInterface> file_system)
+    : CloudFileSystem(std::move(file_system), nullptr) {}
+
+CloudFileSystem::CloudFileSystem(
     std::unique_ptr<ProvidedFileSystemInterface> file_system,
     ContentCache* content_cache)
     : file_system_(std::move(file_system)), content_cache_(content_cache) {}
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_system.h b/chrome/browser/ash/file_system_provider/cloud_file_system.h
index d280ce9..b09eb51d 100644
--- a/chrome/browser/ash/file_system_provider/cloud_file_system.h
+++ b/chrome/browser/ash/file_system_provider/cloud_file_system.h
@@ -33,10 +33,13 @@
 namespace ash::file_system_provider {
 
 // A simple wrapper over a `ProvidedFileSystem` that adds additional logging,
-// currently this is hidden behind the `FileSystemProviderContentCache` feature
-// flag.
+// currently this is hidden behind the `FileSystemProviderCloudFileSystem`
+// feature flag.
 class CloudFileSystem : public ProvidedFileSystemInterface {
  public:
+  explicit CloudFileSystem(
+      std::unique_ptr<ProvidedFileSystemInterface> file_system);
+
   CloudFileSystem(std::unique_ptr<ProvidedFileSystemInterface> file_system,
                    ContentCache* content_cache);
 
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache.h b/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
index f588e8d..e6b44cc 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
@@ -9,8 +9,9 @@
 
 // A singleton that is the hub for all FileSystemProvider extensions that are
 // enabled with a content cache. Currently this is just an experiment hidden
-// behind the FileSystemProviderContentCache flag and only enabled on ODFS when
-// the flag is toggled on.
+// behind both the `FileSystemProviderCloudFileSystem` and
+// `FileSystemProviderContentCache` flags and only enabled on ODFS when the
+// flags are toggled on.
 class ContentCache {
  public:
   ContentCache();
diff --git a/chrome/browser/ash/file_system_provider/extension_provider.cc b/chrome/browser/ash/file_system_provider/extension_provider.cc
index b228b53e..93ff8e3b 100644
--- a/chrome/browser/ash/file_system_provider/extension_provider.cc
+++ b/chrome/browser/ash/file_system_provider/extension_provider.cc
@@ -99,16 +99,27 @@
     const ProvidedFileSystemInfo& file_system_info,
     ContentCache* content_cache) {
   DCHECK(profile);
-  // Cache type is only set when `FileSystemProviderContentCache` feature flag
-  // is enabled and the provider is ODFS.
+  if (!chromeos::features::IsFileSystemProviderCloudFileSystemEnabled()) {
+    return std::make_unique<ThrottledFileSystem>(
+        std::make_unique<ProvidedFileSystem>(profile, file_system_info));
+  }
+  // TODO(b/317137739): Check the file system has a CLOUD source before
+  // creating a CloudFileSystem.
+  // Cache type is only set when the
+  // `FileSystemProviderCloudFileSystemEnabled` and
+  // `FileSystemProviderContentCache` feature flags are enabled and the
+  // provider is ODFS.
   if (file_system_info.cache_type() != CacheType::NONE) {
+    // CloudFileSystem with cache.
     return std::make_unique<ThrottledFileSystem>(
         std::make_unique<CloudFileSystem>(
             std::make_unique<ProvidedFileSystem>(profile, file_system_info),
             content_cache));
   }
+  // CloudFileSystem without cache.
   return std::make_unique<ThrottledFileSystem>(
-      std::make_unique<ProvidedFileSystem>(profile, file_system_info));
+      std::make_unique<CloudFileSystem>(
+          std::make_unique<ProvidedFileSystem>(profile, file_system_info)));
 }
 
 const Capabilities& ExtensionProvider::GetCapabilities() const {
diff --git a/chrome/browser/ash/file_system_provider/service.cc b/chrome/browser/ash/file_system_provider/service.cc
index 9b430a45..cf8a5f1 100644
--- a/chrome/browser/ash/file_system_provider/service.cc
+++ b/chrome/browser/ash/file_system_provider/service.cc
@@ -124,8 +124,8 @@
       util::GetMountPath(profile_, provider_id, options.file_system_id);
   const std::string mount_point_name = mount_path.BaseName().AsUTF8Unsafe();
 
-  // The content cache is an experimentation on ODFS behind a feature flag, only
-  // pass it through if those conditions are met.
+  // The content cache is an experimentation on ODFS behind two feature flags,
+  // only pass it through if those conditions are met.
   // TODO(b/317137739): This logic should be moved to a capability in the
   // manifest.json.
   const bool is_content_cache_enabled_and_odfs =
diff --git a/chrome/browser/ash/fileapi/file_change_service_unittest.cc b/chrome/browser/ash/fileapi/file_change_service_unittest.cc
index cd1d4c9..031fc24 100644
--- a/chrome/browser/ash/fileapi/file_change_service_unittest.cc
+++ b/chrome/browser/ash/fileapi/file_change_service_unittest.cc
@@ -15,13 +15,11 @@
 #include "chrome/browser/ash/fileapi/file_change_service_factory.h"
 #include "chrome/browser/ash/fileapi/file_change_service_observer.h"
 #include "chrome/browser/ash/fileapi/file_system_backend.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
 #include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/data_pipe_producer.h"
 #include "mojo/public/cpp/system/string_data_source.h"
@@ -233,9 +231,7 @@
 
 class FileChangeServiceTest : public BrowserWithTestWindowTest {
  public:
-  FileChangeServiceTest()
-      : fake_user_manager_(new FakeChromeUserManager),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {}
+  FileChangeServiceTest() = default;
 
   FileChangeServiceTest(const FileChangeServiceTest& other) = delete;
   FileChangeServiceTest& operator=(const FileChangeServiceTest& other) = delete;
@@ -252,15 +248,6 @@
   std::string GetDefaultProfileName() override {
     return "promary_profile@test";
   }
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-  }
-
-  raw_ptr<FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
 };
 
 }  // namespace
diff --git a/chrome/browser/ash/hats/hats_notification_controller_unittest.cc b/chrome/browser/ash/hats/hats_notification_controller_unittest.cc
index ee4e7cb8..97d4864 100644
--- a/chrome/browser/ash/hats/hats_notification_controller_unittest.cc
+++ b/chrome/browser/ash/hats/hats_notification_controller_unittest.cc
@@ -99,9 +99,11 @@
     std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs(
         factory.CreateSyncable(registry.get()));
     RegisterUserProfilePrefs(registry.get());
-    return profile_manager()->CreateTestingProfile(
+    auto* profile = profile_manager()->CreateTestingProfile(
         profile_name, std::move(prefs), std::u16string(), 0,
         TestingProfile::TestingFactories());
+    OnUserProfileCreated(profile_name, profile);
+    return profile;
   }
 
   void TearDown() override {
diff --git a/chrome/browser/ash/input_method/editor_metrics_enums.h b/chrome/browser/ash/input_method/editor_metrics_enums.h
index 81c213b4..eb77650 100644
--- a/chrome/browser/ash/input_method/editor_metrics_enums.h
+++ b/chrome/browser/ash/input_method/editor_metrics_enums.h
@@ -126,7 +126,11 @@
   kPromoCardExplicitDismissal = 40,
   // Increase by 1 when the webui consent screen is shown.
   kConsentScreenImpression = 41,
-  kMaxValue = kConsentScreenImpression,
+  // Increase by 1 when a text insertion has been requested..
+  kTextInsertionRequested = 42,
+  // Increase by 1 when text has been queued for insertion.
+  kTextQueuedForInsertion = 43,
+  kMaxValue = kTextQueuedForInsertion,
 };
 
 }  // namespace ash::input_method
diff --git a/chrome/browser/ash/input_method/editor_metrics_recorder_unittest.cc b/chrome/browser/ash/input_method/editor_metrics_recorder_unittest.cc
index c241f1b..269ea1e 100644
--- a/chrome/browser/ash/input_method/editor_metrics_recorder_unittest.cc
+++ b/chrome/browser/ash/input_method/editor_metrics_recorder_unittest.cc
@@ -79,7 +79,6 @@
         {"NativeRequestFreeformRewrite", EditorOpportunityMode::kRewrite,
          EditorTone::kFreeformRewrite, EditorStates::kNativeRequest,
          /*histogram_name=*/"InputMethod.Manta.Orca.States.FreeformRewrite"},
-
     }),
     [](const testing::TestParamInfo<StateCase> info) {
       return info.param.test_name;
diff --git a/chrome/browser/ash/input_method/editor_system_actuator.cc b/chrome/browser/ash/input_method/editor_system_actuator.cc
index 18c7d9c..bff0ce28 100644
--- a/chrome/browser/ash/input_method/editor_system_actuator.cc
+++ b/chrome/browser/ash/input_method/editor_system_actuator.cc
@@ -42,10 +42,7 @@
 
 void EditorSystemActuator::InsertText(const std::string& text) {
   EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
-  logger->LogEditorState(EditorStates::kInsert);
-  logger->LogNumberOfCharactersInserted(text.length());
-  logger->LogNumberOfCharactersSelectedForInsert(
-      system_->GetSelectedTextLength());
+  logger->LogEditorState(EditorStates::kTextInsertionRequested);
   // After making an announcement there needs to be a small delay to ensure any
   // other announcements triggered from a text insertion do not collide with the
   // original announcement.
@@ -90,9 +87,22 @@
 }
 
 void EditorSystemActuator::OnFocus(int context_id) {
-  if (queued_text_insertion_ && (queued_text_insertion_->HasTimedOut() ||
-                                 queued_text_insertion_->Commit())) {
+  if (queued_text_insertion_ == nullptr) {
+    return;
+  }
+  if (queued_text_insertion_->HasTimedOut()) {
     queued_text_insertion_ = nullptr;
+    return;
+  }
+  if (queued_text_insertion_->Commit()) {
+    EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
+    logger->LogEditorState(EditorStates::kInsert);
+    logger->LogNumberOfCharactersInserted(
+        queued_text_insertion_->GetTextLength());
+    logger->LogNumberOfCharactersSelectedForInsert(
+        system_->GetSelectedTextLength());
+    queued_text_insertion_ = nullptr;
+    return;
   }
 }
 
@@ -102,6 +112,8 @@
   // return to the original text input.
   queued_text_insertion_ =
       std::make_unique<EditorTextInsertion>(std::move(pending_text));
+  EditorMetricsRecorder* logger = system_->GetMetricsRecorder();
+  logger->LogEditorState(EditorStates::kTextQueuedForInsertion);
   system_->CloseUI();
 }
 
diff --git a/chrome/browser/ash/input_method/editor_text_insertion.cc b/chrome/browser/ash/input_method/editor_text_insertion.cc
index be291fe..4e9c9e1 100644
--- a/chrome/browser/ash/input_method/editor_text_insertion.cc
+++ b/chrome/browser/ash/input_method/editor_text_insertion.cc
@@ -46,6 +46,10 @@
   return true;
 }
 
+int EditorTextInsertion::GetTextLength() {
+  return pending_text_.size();
+}
+
 void EditorTextInsertion::CancelTextInsertion() {
   state_ = State::kTimedOut;
 }
diff --git a/chrome/browser/ash/input_method/editor_text_insertion.h b/chrome/browser/ash/input_method/editor_text_insertion.h
index b59940f..9e7b1f5 100644
--- a/chrome/browser/ash/input_method/editor_text_insertion.h
+++ b/chrome/browser/ash/input_method/editor_text_insertion.h
@@ -16,7 +16,7 @@
 
 class EditorTextInsertion {
  public:
-  EditorTextInsertion(const std::string& text);
+  explicit EditorTextInsertion(const std::string& text);
   ~EditorTextInsertion();
 
   // To help ensure that we do not insert the pending text in text fields other
@@ -28,6 +28,9 @@
   // Attempts to commit the pending text into the currently focused text input.
   bool Commit();
 
+  // Returns the length of the text to be inserted with this operation.
+  int GetTextLength();
+
  private:
   enum State {
     kPending,
diff --git a/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc b/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
index 0a6c410..850abde 100644
--- a/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
+++ b/chrome/browser/ash/input_method/input_method_manager_impl_unittest.cc
@@ -31,6 +31,7 @@
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/components/kiosk/kiosk_test_utils.h"
 #include "components/account_id/account_id.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1530,19 +1531,16 @@
                                    ImeIdFromEngineId(kNaclMozcJpId)));
 }
 
-TEST_F(InputMethodManagerImplTest, EnableAllowedInputMethodsInKiosk) {
-  // Login as a kiosk app user.
-  const std::string user_id = "kiosk@account.user";
-  const std::string user_email = user_id;
-  const AccountId account_id =
-      AccountId::FromUserEmailGaiaId(user_email, user_id);
+class InputMethodManagerImplKioskTest : public InputMethodManagerImplTest {
+ public:
+  void LogIn(const std::string& email) override {
+    chromeos::SetUpFakeKioskSession(email);
+    ash_test_helper()->test_session_controller_client()->AddUserSession(
+        email, user_manager::UserType::kKioskApp);
+  }
+};
 
-  ash::FakeChromeUserManager* fake_user_manager =
-      static_cast<ash::FakeChromeUserManager*>(
-          user_manager::UserManager::Get());
-  fake_user_manager->AddKioskAppUser(account_id);
-  fake_user_manager->LoginUser(account_id);
-
+TEST_F(InputMethodManagerImplKioskTest, EnableAllowedInputMethods) {
   // First, setup xkb:fr::fra input method
   std::string original_input_method(ImeIdFromEngineId("xkb:fr::fra"));
   ASSERT_TRUE(
@@ -1567,8 +1565,6 @@
   EXPECT_THAT(manager_->GetActiveIMEState()->GetAllowedInputMethodIds(),
               testing::ElementsAre(ImeIdFromEngineId("xkb:us::eng"),
                                    ImeIdFromEngineId("xkb:de::ger")));
-  // Logout kiosk app user.
-  fake_user_manager->RemoveUserFromList(account_id);
 }
 
 TEST_F(InputMethodManagerImplTest, SetLoginDefaultWithAllowedInputMethods) {
diff --git a/chrome/browser/ash/language_packs/OWNERS b/chrome/browser/ash/language_packs/OWNERS
new file mode 100644
index 0000000..1e4bc2b
--- /dev/null
+++ b/chrome/browser/ash/language_packs/OWNERS
@@ -0,0 +1 @@
+file://chromeos/ash/components/language_packs/OWNERS
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service.cc b/chrome/browser/ash/language_packs/language_pack_font_service.cc
new file mode 100644
index 0000000..974e33c2
--- /dev/null
+++ b/chrome/browser/ash/language_packs/language_pack_font_service.cc
@@ -0,0 +1,14 @@
+// Copyright 2024 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/language_packs/language_pack_font_service.h"
+
+class PrefService;
+
+namespace ash::language_packs {
+
+LanguagePackFontService::LanguagePackFontService(PrefService* prefs)
+    : prefs_(*prefs) {}
+
+}  // namespace ash::language_packs
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service.h b/chrome/browser/ash/language_packs/language_pack_font_service.h
new file mode 100644
index 0000000..caeb9b3
--- /dev/null
+++ b/chrome/browser/ash/language_packs/language_pack_font_service.h
@@ -0,0 +1,26 @@
+// Copyright 2024 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_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_H_
+#define CHROME_BROWSER_ASH_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_H_
+
+#include "base/memory/raw_ref.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class PrefService;
+
+namespace ash::language_packs {
+
+class LanguagePackFontService : public KeyedService {
+ public:
+  explicit LanguagePackFontService(PrefService* prefs);
+
+ private:
+  // Not owned by this class
+  const raw_ref<PrefService> prefs_;
+};
+
+}  // namespace ash::language_packs
+
+#endif  // CHROME_BROWSER_ASH_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_H_
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc b/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc
new file mode 100644
index 0000000..bcefc59
--- /dev/null
+++ b/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc
@@ -0,0 +1,52 @@
+// Copyright 2024 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/language_packs/language_pack_font_service_factory.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/feature_list.h"
+#include "base/no_destructor.h"
+#include "chrome/browser/ash/language_packs/language_pack_font_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_selections.h"
+
+class KeyedService;
+
+namespace ash::language_packs {
+
+LanguagePackFontServiceFactory* LanguagePackFontServiceFactory::GetInstance() {
+  static base::NoDestructor<LanguagePackFontServiceFactory> instance;
+  return instance.get();
+}
+
+LanguagePackFontServiceFactory::LanguagePackFontServiceFactory()
+    : ProfileKeyedServiceFactory(
+          "FontManagerFactory",
+          ProfileSelections::Builder()
+              // OTR renderers in Ash will inherit fontconfig from the original.
+              .WithRegular(ProfileSelection::kRedirectedToOriginal)
+              // No DLC fonts if there is no user.
+              .WithGuest(ProfileSelection::kNone)
+              .WithSystem(ProfileSelection::kNone)
+              .WithAshInternals(ProfileSelection::kNone)
+              .Build()) {}
+
+LanguagePackFontServiceFactory::~LanguagePackFontServiceFactory() = default;
+
+std::unique_ptr<KeyedService>
+LanguagePackFontServiceFactory::BuildServiceInstanceForBrowserContext(
+    content::BrowserContext* context) const {
+  if (!base::FeatureList::IsEnabled(features::kLanguagePacksFonts)) {
+    return nullptr;
+  }
+  return std::make_unique<LanguagePackFontService>(
+      Profile::FromBrowserContext(context)->GetPrefs());
+}
+
+bool LanguagePackFontServiceFactory::ServiceIsCreatedWithBrowserContext()
+    const {
+  return true;
+}
+
+}  // namespace ash::language_packs
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service_factory.h b/chrome/browser/ash/language_packs/language_pack_font_service_factory.h
new file mode 100644
index 0000000..30e96e3
--- /dev/null
+++ b/chrome/browser/ash/language_packs/language_pack_font_service_factory.h
@@ -0,0 +1,46 @@
+// Copyright 2024 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_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_ASH_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+
+class KeyedService;
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace ash::language_packs {
+
+// Factory for `LanguagePackFontService`.
+// This service is "internal" - other code should not access it, so
+// `GetForProfile()` is omitted on purpose.
+class LanguagePackFontServiceFactory : public ProfileKeyedServiceFactory {
+ public:
+  static LanguagePackFontServiceFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<LanguagePackFontServiceFactory>;
+
+  LanguagePackFontServiceFactory();
+  ~LanguagePackFontServiceFactory() override;
+
+  // BrowserContextKeyedServiceFactory overrides
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+      content::BrowserContext* context) const override;
+  bool ServiceIsCreatedWithBrowserContext() const override;
+};
+
+}  // namespace ash::language_packs
+
+#endif  // CHROME_BROWSER_ASH_LANGUAGE_PACKS_LANGUAGE_PACK_FONT_SERVICE_FACTORY_H_
diff --git a/chrome/browser/ash/lock_screen_apps/state_controller_unittest.cc b/chrome/browser/ash/lock_screen_apps/state_controller_unittest.cc
index 8bfd080..edf34c0 100644
--- a/chrome/browser/ash/lock_screen_apps/state_controller_unittest.cc
+++ b/chrome/browser/ash/lock_screen_apps/state_controller_unittest.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/ash/lock_screen_apps/first_app_run_toast_manager.h"
 #include "chrome/browser/ash/lock_screen_apps/focus_cycler_delegate.h"
 #include "chrome/browser/ash/lock_screen_apps/state_observer.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/note_taking_helper.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/test_extension_system.h"
@@ -45,7 +44,6 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/power_manager/suspend.pb.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_task_environment.h"
@@ -379,13 +377,9 @@
 
 class LockScreenAppStateTest : public BrowserWithTestWindowTest {
  public:
-  LockScreenAppStateTest()
-      : fake_user_manager_(new ash::FakeChromeUserManager),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {}
-
+  LockScreenAppStateTest() = default;
   LockScreenAppStateTest(const LockScreenAppStateTest&) = delete;
   LockScreenAppStateTest& operator=(const LockScreenAppStateTest&) = delete;
-
   ~LockScreenAppStateTest() override = default;
 
   void SetUp() override {
@@ -460,20 +454,6 @@
     ash::ConciergeClient::Shutdown();
   }
 
-  // BrowserWithTestWindow:
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindow.
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    AddTestUser(account_id);
-    fake_user_manager()->LoginUser(account_id);
-  }
-
-  // Adds test user for the primary profile - virtual so test fixture can
-  // override the test user type.
-  virtual void AddTestUser(const AccountId& account_id) {
-    fake_user_manager()->AddUser(account_id);
-  }
-
   // Exposed so test fixtures can override default (empty) command line.
   virtual void SetUpCommandLine(base::CommandLine* command_line) {}
 
@@ -629,8 +609,6 @@
     lock_screen_profile_creator_->CreateProfile();
   }
 
-  ash::FakeChromeUserManager* fake_user_manager() { return fake_user_manager_; }
-
   Profile* LockScreenProfile() {
     return lock_screen_profile_creator_->lock_screen_profile();
   }
@@ -671,9 +649,6 @@
 
   std::unique_ptr<base::test::ScopedCommandLine> command_line_;
 
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-
   // Run loop used to throttle test until async state controller initialization
   // is fully complete. The quit closure for this run loop will be passed to
   // |state_controller_| as the callback to be run when the state controller is
@@ -717,8 +692,16 @@
 
   ~LockScreenAppStateKioskUserTest() override {}
 
-  void AddTestUser(const AccountId& account_id) override {
-    fake_user_manager()->AddKioskAppUser(account_id);
+  // BrowserWithTestWindow:
+  void LogIn(const std::string& email) override {
+    const AccountId account_id = AccountId::FromUserEmail(email);
+    // Log in as a kiosk user.
+    user_manager()->AddKioskAppUser(account_id);
+    user_manager()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
   }
 };
 
diff --git a/chrome/browser/ash/login/existing_user_controller.cc b/chrome/browser/ash/login/existing_user_controller.cc
index f7e840eb..4755261 100644
--- a/chrome/browser/ash/login/existing_user_controller.cc
+++ b/chrome/browser/ash/login/existing_user_controller.cc
@@ -61,6 +61,7 @@
 #include "chrome/browser/ash/login/ui/user_adding_screen.h"
 #include "chrome/browser/ash/login/ui/webui_login_view.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
+#include "chrome/browser/ash/login/users/chrome_user_manager_util.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
@@ -440,7 +441,7 @@
   }
 
   if (LoginDisplayHostMojo::Get()) {
-    auto login_users = ExtractLoginUsers(users);
+    auto login_users = chrome_user_manager_util::FindLoginAllowedUsers(users);
     LoginDisplayHostMojo::Get()->SetUsers(login_users);
   }
 }
@@ -1098,34 +1099,6 @@
   return password_changed_;
 }
 
-// static
-user_manager::UserList ExistingUserController::ExtractLoginUsers(
-    const user_manager::UserList& users) {
-  bool show_users_on_signin;
-  CrosSettings::Get()->GetBoolean(kAccountsPrefShowUserNamesOnSignIn,
-                                  &show_users_on_signin);
-  user_manager::UserList filtered_users;
-  for (user_manager::User* user : users) {
-    // Skip kiosk apps for login screen user list. Kiosk apps as pods (aka new
-    // kiosk UI) is currently disabled and it gets the apps directly from
-    // KioskChromeAppManager, ArcKioskAppManager and WebKioskAppManager.
-    if (user->IsKioskType()) {
-      continue;
-    }
-    const bool meets_allowlist_requirements =
-        !user->HasGaiaAccount() ||
-        user_manager::UserManager::Get()->IsGaiaUserAllowed(*user);
-    // Public session accounts are always shown on login screen.
-    const bool meets_show_users_requirements =
-        show_users_on_signin ||
-        user->GetType() == user_manager::UserType::kPublicAccount;
-    if (meets_allowlist_requirements && meets_show_users_requirements) {
-      filtered_users.push_back(user);
-    }
-  }
-  return filtered_users;
-}
-
 void ExistingUserController::LoginAuthenticated(
     std::unique_ptr<UserContext> user_context) {
   CHECK(login_performer_);
diff --git a/chrome/browser/ash/login/screens/user_selection_screen.cc b/chrome/browser/ash/login/screens/user_selection_screen.cc
index f1da5bce..9fd6d40 100644
--- a/chrome/browser/ash/login/screens/user_selection_screen.cc
+++ b/chrome/browser/ash/login/screens/user_selection_screen.cc
@@ -187,15 +187,17 @@
   return true;
 }
 
-void GetMultiUserSignInPolicy(const user_manager::User* user,
-                              bool* out_is_allowed,
-                              user_manager::MultiUserSignInPolicy* out_policy) {
+// Returns a pair of 1) whether it is allowed to be part of the current
+// multi user sign-in session, and 2) that policy for the user.
+std::tuple<bool, user_manager::MultiUserSignInPolicy> GetMultiUserSignInPolicy(
+    const user_manager::User* user) {
   const std::string& user_id = user->GetAccountId().GetUserEmail();
-  MultiProfileUserController* multi_profile_user_controller =
+  MultiProfileUserController* controller =
       ChromeUserManager::Get()->GetMultiProfileUserController();
-  *out_is_allowed =
-      multi_profile_user_controller->IsUserAllowedInSession(user_id, nullptr);
-  *out_policy = multi_profile_user_controller->GetCachedValue(user_id);
+  return {
+      controller->IsUserAllowedInSession(user_id),
+      controller->GetCachedValue(user_id),
+  };
 }
 
 // Determines if user auth status requires online sign in.
@@ -845,8 +847,9 @@
     if (!is_signin_to_add) {
       user_info.is_multi_user_sign_in_allowed = true;
     } else {
-      GetMultiUserSignInPolicy(user, &user_info.is_multi_user_sign_in_allowed,
-                               &user_info.multi_user_sign_in_policy);
+      std::tie(user_info.is_multi_user_sign_in_allowed,
+               user_info.multi_user_sign_in_policy) =
+          GetMultiUserSignInPolicy(user);
     }
 
     // Fill public session data.
diff --git a/chrome/browser/ash/login/signin/signin_error_notifier_unittest.cc b/chrome/browser/ash/login/signin/signin_error_notifier_unittest.cc
index 9046ed7..322459e6 100644
--- a/chrome/browser/ash/login/signin/signin_error_notifier_unittest.cc
+++ b/chrome/browser/ash/login/signin/signin_error_notifier_unittest.cc
@@ -12,7 +12,6 @@
 #include "base/memory/ptr_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/login/signin/signin_error_notifier_factory.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
@@ -25,7 +24,6 @@
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/supervised_user/core/browser/supervised_user_service.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -54,8 +52,6 @@
     // Required to initialize TokenHandleUtil.
     ash::UserDataAuthClient::InitializeFake();
 
-    fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
-
     SigninErrorNotifierFactory::GetForProfile(GetProfile());
     display_service_ =
         std::make_unique<NotificationDisplayServiceTester>(profile());
@@ -90,8 +86,6 @@
 
  protected:
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
 };
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
index d04ff9d..a106431 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
@@ -42,7 +42,6 @@
 #include "base/values.h"
 #include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
 #include "chrome/browser/ash/login/enterprise_user_session_metrics.h"
-#include "chrome/browser/ash/login/existing_user_controller.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/ash/login/signin/auth_error_observer.h"
 #include "chrome/browser/ash/login/signin/auth_error_observer_factory.h"
@@ -462,29 +461,25 @@
     return user_manager::UserList();
   }
 
-  user_manager::UserList result;
-  const user_manager::UserList& users = GetUsers();
-  for (user_manager::UserList::const_iterator it = users.begin();
-       it != users.end(); ++it) {
-    if ((*it)->GetType() == user_manager::UserType::kRegular &&
-        !(*it)->is_logged_in()) {
-      MultiProfileUserController::UserAllowedInSessionReason check;
-      multi_profile_user_controller_.IsUserAllowedInSession(
-          (*it)->GetAccountId().GetUserEmail(), &check);
-      if (check ==
-          MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS) {
-        return user_manager::UserList();
-      }
+  // No user is allowed if the primary user policy forbids it.
+  if (multi_profile_user_controller_.GetPrimaryUserPolicy() ==
+      MultiUserSignInPolicy::kNotAllowed) {
+    return {};
+  }
 
+  user_manager::UserList result;
+  for (user_manager::User* user : GetUsers()) {
+    if (user->GetType() == user_manager::UserType::kRegular &&
+        !user->is_logged_in()) {
       // Users with a policy that prevents them being added to a session will be
       // shown in login UI but will be grayed out.
       // Same applies to owner account (see http://crbug.com/385034).
-      result.push_back(*it);
+      result.push_back(user);
     }
   }
 
   // Extract out users that are allowed on login screen.
-  return ExistingUserController::ExtractLoginUsers(result);
+  return chrome_user_manager_util::FindLoginAllowedUsers(result);
 }
 
 user_manager::UserList ChromeUserManagerImpl::GetUnlockUsers() const {
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_util.cc b/chrome/browser/ash/login/users/chrome_user_manager_util.cc
index eb7721c..55ff620 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_util.cc
+++ b/chrome/browser/ash/login/users/chrome_user_manager_util.cc
@@ -78,4 +78,31 @@
          user_manager->IsCurrentUserCryptohomeDataEphemeral();
 }
 
+user_manager::UserList FindLoginAllowedUsers(
+    const user_manager::UserList& users) {
+  bool show_users_on_signin;
+  CrosSettings::Get()->GetBoolean(kAccountsPrefShowUserNamesOnSignIn,
+                                  &show_users_on_signin);
+  user_manager::UserList found_users;
+  for (user_manager::User* user : users) {
+    // Skip kiosk apps for login screen user list. Kiosk apps as pods (aka new
+    // kiosk UI) is currently disabled and it gets the apps directly from
+    // KioskChromeAppManager, ArcKioskAppManager and WebKioskAppManager.
+    if (user->IsKioskType()) {
+      continue;
+    }
+    const bool meets_allowlist_requirements =
+        !user->HasGaiaAccount() ||
+        user_manager::UserManager::Get()->IsGaiaUserAllowed(*user);
+    // Public session accounts are always shown on login screen.
+    const bool meets_show_users_requirements =
+        show_users_on_signin ||
+        user->GetType() == user_manager::UserType::kPublicAccount;
+    if (meets_allowlist_requirements && meets_show_users_requirements) {
+      found_users.push_back(user);
+    }
+  }
+  return found_users;
+}
+
 }  // namespace ash::chrome_user_manager_util
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_util.h b/chrome/browser/ash/login/users/chrome_user_manager_util.h
index 4d2e24f..67d7cc1a 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_util.h
+++ b/chrome/browser/ash/login/users/chrome_user_manager_util.h
@@ -29,6 +29,10 @@
 // user has logged in).
 bool IsManagedGuestSessionOrEphemeralLogin();
 
+// Returns users allowed on login screen.
+user_manager::UserList FindLoginAllowedUsers(
+    const user_manager::UserList& users);
+
 }  // namespace ash::chrome_user_manager_util
 
 #endif  // CHROME_BROWSER_ASH_LOGIN_USERS_CHROME_USER_MANAGER_UTIL_H_
diff --git a/chrome/browser/ash/login/users/multi_profile_user_controller.cc b/chrome/browser/ash/login/users/multi_profile_user_controller.cc
index 849b91ed..2dfc95b 100644
--- a/chrome/browser/ash/login/users/multi_profile_user_controller.cc
+++ b/chrome/browser/ash/login/users/multi_profile_user_controller.cc
@@ -30,19 +30,6 @@
 using user_manager::MultiUserSignInPolicyToPrefValue;
 using user_manager::ParseMultiUserSignInPolicyPref;
 
-namespace {
-
-bool SetUserAllowedReason(
-    MultiProfileUserController::UserAllowedInSessionReason* reason,
-    MultiProfileUserController::UserAllowedInSessionReason value) {
-  if (reason) {
-    *reason = value;
-  }
-  return value == MultiProfileUserController::ALLOWED;
-}
-
-}  // namespace
-
 MultiProfileUserController::MultiProfileUserController(
     PrefService* local_state,
     user_manager::UserManager* user_manager)
@@ -73,31 +60,24 @@
   pref_watchers_.clear();
 }
 
-MultiProfileUserController::UserAllowedInSessionReason
+std::optional<MultiUserSignInPolicy>
 MultiProfileUserController::GetPrimaryUserPolicy() const {
   const user_manager::User* user = user_manager_->GetPrimaryUser();
   if (!user) {
-    return ALLOWED;
+    return std::nullopt;
   }
 
   auto* prefs = user->GetProfilePrefs();
   if (!prefs) {
-    return ALLOWED;
+    return std::nullopt;
   }
 
-  // No user is allowed if the primary user policy forbids it.
-  auto value = ParseMultiUserSignInPolicyPref(
+  return ParseMultiUserSignInPolicyPref(
       prefs->GetString(prefs::kMultiProfileUserBehaviorPref));
-  if (value == MultiUserSignInPolicy::kNotAllowed) {
-    return NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS;
-  }
-
-  return ALLOWED;
 }
 
 bool MultiProfileUserController::IsUserAllowedInSession(
-    const std::string& user_email,
-    MultiProfileUserController::UserAllowedInSessionReason* reason) const {
+    const std::string& user_email) const {
   const user_manager::User* primary_user = user_manager_->GetPrimaryUser();
   std::string primary_user_email;
   if (primary_user) {
@@ -107,20 +87,17 @@
   // Always allow if there is no primary user or user being checked is the
   // primary user.
   if (primary_user_email.empty() || primary_user_email == user_email) {
-    return SetUserAllowedReason(reason, ALLOWED);
+    return true;
   }
 
-  UserAllowedInSessionReason primary_user_policy = GetPrimaryUserPolicy();
-  if (primary_user_policy != ALLOWED) {
-    return SetUserAllowedReason(reason, primary_user_policy);
+  auto primary_user_policy = GetPrimaryUserPolicy();
+  if (primary_user_policy == MultiUserSignInPolicy::kNotAllowed) {
+    return false;
   }
 
   // The user must have 'unrestricted' policy to be a secondary user.
   const auto policy = GetCachedValue(user_email);
-  return SetUserAllowedReason(reason,
-                              policy == MultiUserSignInPolicy::kUnrestricted
-                                  ? ALLOWED
-                                  : NOT_ALLOWED_POLICY_FORBIDS);
+  return policy == MultiUserSignInPolicy::kUnrestricted;
 }
 
 void MultiProfileUserController::StartObserving(user_manager::User* user) {
@@ -171,7 +148,7 @@
 void MultiProfileUserController::CheckSessionUsers() {
   for (const user_manager::User* user : user_manager_->GetLoggedInUsers()) {
     const std::string& user_email = user->GetAccountId().GetUserEmail();
-    if (!IsUserAllowedInSession(user_email, /*reason=*/nullptr)) {
+    if (!IsUserAllowedInSession(user_email)) {
       user_manager_->NotifyUserNotAllowed(user_email);
       return;
     }
diff --git a/chrome/browser/ash/login/users/multi_profile_user_controller.h b/chrome/browser/ash/login/users/multi_profile_user_controller.h
index 2783d33..44a2207 100644
--- a/chrome/browser/ash/login/users/multi_profile_user_controller.h
+++ b/chrome/browser/ash/login/users/multi_profile_user_controller.h
@@ -34,20 +34,6 @@
 // user login and checks if the meaning of the value is respected.
 class MultiProfileUserController {
  public:
-  // Second return value of IsUserAllowedInSession().
-  enum UserAllowedInSessionReason {
-    // User is allowed in multi-profile session.
-    ALLOWED,
-
-    // Not allowed since primary user policy forbids it to be part of
-    // multi-profiles session.
-    NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
-
-    // Not allowed since user policy forbids this user being part of
-    // multi-profiles session. Either 'primary-only' or 'not-allowed'.
-    NOT_ALLOWED_POLICY_FORBIDS
-  };
-
   MultiProfileUserController(PrefService* local_state,
                              user_manager::UserManager* user_manager);
 
@@ -67,15 +53,13 @@
   user_manager::MultiUserSignInPolicy GetCachedValue(
       std::string_view user_email) const;
 
-  // Returns primary user policy (only ALLOW,
-  // NOT_ALLOWED_PRIMARY_POLICY_CERT_TAINTED,
-  // NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS)
-  UserAllowedInSessionReason GetPrimaryUserPolicy() const;
+  // Returns the primary user's policy. If there's no primary user,
+  // returns std::nullopt.
+  std::optional<user_manager::MultiUserSignInPolicy> GetPrimaryUserPolicy()
+      const;
 
-  // Returns true if user allowed to be in the current session. If `reason` not
-  // null stores UserAllowedInSessionReason enum that describes actual reason.
-  bool IsUserAllowedInSession(const std::string& user_email,
-                              UserAllowedInSessionReason* reason) const;
+  // Returns true if user allowed to be in the current session.
+  bool IsUserAllowedInSession(const std::string& user_email) const;
 
   // Starts to observe the multiprofile user behavior pref of the given user.
   void StartObserving(user_manager::User* user);
diff --git a/chrome/browser/ash/login/users/multi_profile_user_controller_unittest.cc b/chrome/browser/ash/login/users/multi_profile_user_controller_unittest.cc
index a10133d..d4cf485 100644
--- a/chrome/browser/ash/login/users/multi_profile_user_controller_unittest.cc
+++ b/chrome/browser/ash/login/users/multi_profile_user_controller_unittest.cc
@@ -57,66 +57,54 @@
 struct BehaviorTestCase {
   MultiUserSignInPolicy primary;
   MultiUserSignInPolicy secondary;
-  MultiProfileUserController::UserAllowedInSessionReason
-      expected_primary_policy;
-  MultiProfileUserController::UserAllowedInSessionReason
-      expected_secondary_allowed;
+  bool expected_secondary_allowed;
 };
 
 constexpr BehaviorTestCase kBehaviorTestCases[] = {
     {
         MultiUserSignInPolicy::kUnrestricted,
         MultiUserSignInPolicy::kUnrestricted,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::ALLOWED,
+        true,
     },
     {
         MultiUserSignInPolicy::kUnrestricted,
         MultiUserSignInPolicy::kPrimaryOnly,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::NOT_ALLOWED_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kUnrestricted,
         MultiUserSignInPolicy::kNotAllowed,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::NOT_ALLOWED_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kPrimaryOnly,
         MultiUserSignInPolicy::kUnrestricted,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::ALLOWED,
+        true,
     },
     {
         MultiUserSignInPolicy::kPrimaryOnly,
         MultiUserSignInPolicy::kPrimaryOnly,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::NOT_ALLOWED_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kPrimaryOnly,
         MultiUserSignInPolicy::kNotAllowed,
-        MultiProfileUserController::ALLOWED,
-        MultiProfileUserController::NOT_ALLOWED_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kNotAllowed,
         MultiUserSignInPolicy::kUnrestricted,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kNotAllowed,
         MultiUserSignInPolicy::kPrimaryOnly,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
+        false,
     },
     {
         MultiUserSignInPolicy::kNotAllowed,
         MultiUserSignInPolicy::kNotAllowed,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
-        MultiProfileUserController::NOT_ALLOWED_PRIMARY_USER_POLICY_FORBIDS,
+        false,
     },
 };
 
@@ -238,15 +226,11 @@
       MultiUserSignInPolicy::kNotAllowed,
   };
   for (size_t i = 0; i < std::size(kTestCases); ++i) {
+    SCOPED_TRACE(i);
     SetCachedBehavior(0, kTestCases[i]);
-    MultiProfileUserController::UserAllowedInSessionReason reason;
-    EXPECT_TRUE(controller()->IsUserAllowedInSession(
-        test_users_[0].GetUserEmail(), &reason))
-        << "Case " << i;
-    EXPECT_EQ(MultiProfileUserController::ALLOWED, reason) << "Case " << i;
-    EXPECT_EQ(MultiProfileUserController::ALLOWED,
-              controller()->GetPrimaryUserPolicy())
-        << "Case " << i;
+    EXPECT_TRUE(
+        controller()->IsUserAllowedInSession(test_users_[0].GetUserEmail()));
+    EXPECT_EQ(std::nullopt, controller()->GetPrimaryUserPolicy());
   }
 }
 
@@ -312,16 +296,14 @@
   LoginUser(0);
 
   for (size_t i = 0; i < std::size(kBehaviorTestCases); ++i) {
+    SCOPED_TRACE(i);
     SetPrefBehavior(0, kBehaviorTestCases[i].primary);
     SetCachedBehavior(1, kBehaviorTestCases[i].secondary);
-    EXPECT_EQ(kBehaviorTestCases[i].expected_primary_policy,
-              controller()->GetPrimaryUserPolicy())
-        << "Case " << i;
-    MultiProfileUserController::UserAllowedInSessionReason reason;
-    controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail(),
-                                         &reason);
-    EXPECT_EQ(kBehaviorTestCases[i].expected_secondary_allowed, reason)
-        << "Case " << i;
+    EXPECT_EQ(kBehaviorTestCases[i].primary,
+              controller()->GetPrimaryUserPolicy());
+    EXPECT_EQ(
+        kBehaviorTestCases[i].expected_secondary_allowed,
+        controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
   }
 }
 
@@ -339,6 +321,7 @@
   testing::Mock::VerifyAndClearExpectations(&mock_observer);
 
   for (size_t i = 0; i < std::size(kBehaviorTestCases); ++i) {
+    SCOPED_TRACE(i);
     EXPECT_CALL(mock_observer, OnUserNotAllowed(testing::_))
         .Times(testing::AnyNumber());
     SetPrefBehavior(0, MultiUserSignInPolicy::kUnrestricted);
@@ -346,8 +329,7 @@
     testing::Mock::VerifyAndClearExpectations(&mock_observer);
 
     EXPECT_CALL(mock_observer, OnUserNotAllowed(testing::_))
-        .Times(kBehaviorTestCases[i].expected_secondary_allowed ==
-                       MultiProfileUserController::ALLOWED
+        .Times(kBehaviorTestCases[i].expected_secondary_allowed
                    ? testing::Exactly(0)
                    : testing::AtLeast(1));
     SetPrefBehavior(0, kBehaviorTestCases[i].primary);
@@ -366,15 +348,10 @@
   policy::PolicyCertServiceFactory::GetForProfile(profile(0))
       ->SetUsedPolicyCertificates();
 
-  MultiProfileUserController::UserAllowedInSessionReason reason;
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[0].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[1].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_EQ(MultiProfileUserController::ALLOWED,
-            controller()->GetPrimaryUserPolicy());
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[0].GetUserEmail()));
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
 }
 
 TEST_F(MultiProfileUserControllerTest,
@@ -387,10 +364,8 @@
   // changed back to enabled.
   SetPrefBehavior(1, MultiUserSignInPolicy::kUnrestricted);
 
-  MultiProfileUserController::UserAllowedInSessionReason reason;
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[0].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[0].GetUserEmail()));
 
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
@@ -398,9 +373,8 @@
   policy::PolicyCertServiceFactory::GetForProfile(profile(0))
       ->SetUsedPolicyCertificates();
 
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[0].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[0].GetUserEmail()));
 }
 
 TEST_F(MultiProfileUserControllerTest,
@@ -414,12 +388,8 @@
       ->SetUsedPolicyCertificates();
   LoginUser(0);
 
-  MultiProfileUserController::UserAllowedInSessionReason reason;
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[1].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_EQ(MultiProfileUserController::ALLOWED,
-            controller()->GetPrimaryUserPolicy());
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
 
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
@@ -427,11 +397,8 @@
   policy::PolicyCertServiceFactory::GetForProfile(profile(1))
       ->SetUsedPolicyCertificates();
 
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[1].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_EQ(MultiProfileUserController::ALLOWED,
-            controller()->GetPrimaryUserPolicy());
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
 
   // Flush tasks posted to IO.
   base::RunLoop().RunUntilIdle();
@@ -455,23 +422,16 @@
   ASSERT_TRUE(service);
 
   EXPECT_FALSE(service->has_policy_certificates());
-  MultiProfileUserController::UserAllowedInSessionReason reason;
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[1].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_EQ(MultiProfileUserController::ALLOWED,
-            controller()->GetPrimaryUserPolicy());
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
 
   net::CertificateList certificates;
   certificates.push_back(
       net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"));
   service->SetPolicyTrustAnchorsForTesting(/*trust_anchors=*/certificates);
   EXPECT_TRUE(service->has_policy_certificates());
-  EXPECT_TRUE(controller()->IsUserAllowedInSession(
-      test_users_[1].GetUserEmail(), &reason));
-  EXPECT_EQ(MultiProfileUserController::ALLOWED, reason);
-  EXPECT_EQ(MultiProfileUserController::ALLOWED,
-            controller()->GetPrimaryUserPolicy());
+  EXPECT_TRUE(
+      controller()->IsUserAllowedInSession(test_users_[1].GetUserEmail()));
 
   // Flush tasks posted to IO.
   base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/ash/note_taking_helper_unittest.cc b/chrome/browser/ash/note_taking_helper_unittest.cc
index 469e080..93835ad 100644
--- a/chrome/browser/ash/note_taking_helper_unittest.cc
+++ b/chrome/browser/ash/note_taking_helper_unittest.cc
@@ -411,8 +411,7 @@
     auto* profile = profile_manager()->CreateTestingProfile(
         profile_name, std::move(prefs), u"Test profile", 1 /*avatar_id*/,
         TestingProfile::TestingFactories());
-    user_manager()->OnUserProfileCreated(AccountId::FromUserEmail(profile_name),
-                                         nullptr /*TODO*/);
+    OnUserProfileCreated(profile_name, profile);
     return profile;
   }
 
@@ -425,8 +424,7 @@
     TestingProfile* profile = profile_manager()->CreateTestingProfile(
         kSecondProfileName, std::move(prefs), u"second-profile-username",
         /*avatar_id=*/1, TestingProfile::TestingFactories());
-    user_manager()->OnUserProfileCreated(
-        AccountId::FromUserEmail(kSecondProfileName), nullptr /*TODO*/);
+    OnUserProfileCreated(kSecondProfileName, profile);
 
     InitExtensionService(profile);
     InitWebAppProvider(profile);
diff --git a/chrome/browser/ash/notifications/low_disk_notification_unittest.cc b/chrome/browser/ash/notifications/low_disk_notification_unittest.cc
index 3ff59c3..4657e5eb 100644
--- a/chrome/browser/ash/notifications/low_disk_notification_unittest.cc
+++ b/chrome/browser/ash/notifications/low_disk_notification_unittest.cc
@@ -22,7 +22,6 @@
 #include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
 #include "chromeos/ash/components/settings/cros_settings_names.h"
 #include "components/user_manager/fake_user_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -50,11 +49,6 @@
     GetCrosSettingsHelper()->SetBoolean(kDeviceShowLowDiskSpaceNotification,
                                         true);
 
-    auto user_manager = std::make_unique<user_manager::FakeUserManager>();
-    user_manager_ = user_manager.get();
-    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(user_manager));
-
     TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
         std::make_unique<SystemNotificationHelper>());
     tester_ = std::make_unique<NotificationDisplayServiceTester>(
@@ -86,9 +80,6 @@
   void OnNotificationAdded() { notification_count_++; }
 
  protected:
-  raw_ptr<user_manager::FakeUserManager, DanglingUntriaged> user_manager_ =
-      nullptr;
-  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   std::unique_ptr<NotificationDisplayServiceTester> tester_;
   std::unique_ptr<LowDiskNotification> low_disk_notification_;
   int notification_count_;
@@ -142,9 +133,9 @@
 }
 
 TEST_F(LowDiskNotificationTest, ShowForMultipleUsersWhenEnrolled) {
-  user_manager_->AddUser(
+  user_manager()->AddUser(
       AccountId::FromUserEmailGaiaId("test_user1@example.com", "1234567891"));
-  user_manager_->AddUser(
+  user_manager()->AddUser(
       AccountId::FromUserEmailGaiaId("test_user2@example.com", "1234567892"));
 
   SetNotificationThrottlingInterval(-1);
@@ -153,9 +144,9 @@
 }
 
 TEST_F(LowDiskNotificationTest, SupressedForMultipleUsersWhenEnrolled) {
-  user_manager_->AddUser(
+  user_manager()->AddUser(
       AccountId::FromUserEmailGaiaId("test_user1@example.com", "1234567891"));
-  user_manager_->AddUser(
+  user_manager()->AddUser(
       AccountId::FromUserEmailGaiaId("test_user2@example.com", "1234567892"));
 
   GetCrosSettingsHelper()->SetBoolean(kDeviceShowLowDiskSpaceNotification,
diff --git a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
index cf1d744..d740e5ac 100644
--- a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
@@ -128,6 +128,16 @@
     ash::ChunneldClient::Shutdown();
   }
 
+  // BrowserWithTestWindowTest:
+  // Override to do nothing to inject this test's specific behavior.
+  // TODO(b/40286020): Consider migrating into BrowserWithTestWindowTest
+  // in better way. Current test implementation is different from
+  // what we're seeing in production.
+  void LogIn(const std::string& email) override {}
+  void OnUserProfileCreated(const std::string& email,
+                            Profile* profile) override {}
+  void SwitchActiveUser(const std::string& email) override {}
+
   void LogInUser(bool is_affiliated) {
     base::RunLoop run_loop;
     const AccountId account_id(AccountId::FromUserEmailGaiaId(
diff --git a/chrome/browser/ash/release_notes/release_notes_storage.cc b/chrome/browser/ash/release_notes/release_notes_storage.cc
index 9a633f2..0e72e19 100644
--- a/chrome/browser/ash/release_notes/release_notes_storage.cc
+++ b/chrome/browser/ash/release_notes/release_notes_storage.cc
@@ -117,10 +117,6 @@
 }
 
 bool ReleaseNotesStorage::ShouldShowSuggestionChip() {
-  if (!base::FeatureList::IsEnabled(features::kReleaseNotesSuggestionChip)) {
-    return false;
-  }
-
   const int times_left_to_show = profile_->GetPrefs()->GetInteger(
       prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
   return times_left_to_show > 0;
diff --git a/chrome/browser/ash/release_notes/release_notes_storage_unittest.cc b/chrome/browser/ash/release_notes/release_notes_storage_unittest.cc
index 1789fff..ee44aca 100644
--- a/chrome/browser/ash/release_notes/release_notes_storage_unittest.cc
+++ b/chrome/browser/ash/release_notes/release_notes_storage_unittest.cc
@@ -75,10 +75,8 @@
   }
 
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kReleaseNotesNotificationAllChannels,
-                              features::kReleaseNotesSuggestionChip},
-        /*disabled_features=*/{});
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kReleaseNotesNotificationAllChannels);
   }
 
   raw_ptr<FakeChromeUserManager, DanglingUntriaged> user_manager_;
diff --git a/chrome/browser/ash/sync/sync_error_notifier_unittest.cc b/chrome/browser/ash/sync/sync_error_notifier_unittest.cc
index eed5f41..94f29bd 100644
--- a/chrome/browser/ash/sync/sync_error_notifier_unittest.cc
+++ b/chrome/browser/ash/sync/sync_error_notifier_unittest.cc
@@ -9,13 +9,11 @@
 #include "ash/constants/ash_features.h"
 #include "base/functional/bind.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/sync/test/test_sync_service.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/message_center/public/cpp/notification.h"
 
@@ -93,8 +91,6 @@
   syncer::TestSyncService service_;
   FakeLoginUI login_ui_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
-  user_manager::ScopedUserManager scoped_user_manager_{
-      std::make_unique<ash::FakeChromeUserManager>()};
 };
 
 TEST_F(SyncErrorNotifierTest, NoNotificationWhenNoPassphrase) {
diff --git a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller_unittest.cc b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller_unittest.cc
index fd2e04a..e381df0d 100644
--- a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/version.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/system_web_apps/apps/help_app/help_app_discover_tab_notification.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
@@ -26,10 +25,7 @@
 #include "components/account_id/account_id.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/pref_service.h"
-#include "components/prefs/pref_service_factory.h"
 #include "components/prefs/testing_pref_store.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "components/version_info/version_info.h"
 
 namespace {
@@ -42,43 +38,27 @@
 
 class HelpAppNotificationControllerTest : public BrowserWithTestWindowTest {
  public:
-  HelpAppNotificationControllerTest()
-      : user_manager_(new FakeChromeUserManager()),
-        scoped_user_manager_(
-            std::unique_ptr<FakeChromeUserManager>(user_manager_)) {}
+  HelpAppNotificationControllerTest() = default;
   ~HelpAppNotificationControllerTest() override = default;
 
-  std::unique_ptr<TestingProfile> CreateRegularProfile() {
-    AccountId account_id_ =
-        AccountId::FromUserEmailGaiaId("user@gmail.com", "12345");
-    user_manager_->AddUser(account_id_);
-    TestingProfile::Builder builder;
-    builder.SetProfileName("user@gmail.com");
-    std::unique_ptr<TestingProfile> profile = builder.Build();
+  TestingProfile* CreateRegularProfile() {
+    constexpr char kEmail[] = "user@gmail.com";
+    LogIn(kEmail);
+    auto* profile = CreateProfile(kEmail);
     // Set profile creation version, otherwise it defaults to 1.0.0.0.
     ChromeVersionService::SetVersion(
         profile->GetPrefs(), std::string(version_info::GetVersionNumber()));
     return profile;
   }
 
-  std::unique_ptr<TestingProfile> CreateChildProfile() {
-    std::unique_ptr<TestingProfile> profile = CreateRegularProfile();
+  TestingProfile* CreateChildProfile() {
+    TestingProfile* profile = CreateRegularProfile();
     ChromeVersionService::SetVersion(
         profile->GetPrefs(), std::string(version_info::GetVersionNumber()));
     profile->SetIsSupervisedProfile();
     return profile;
   }
 
-  // Creates a PrefService and registers Autofill prefs.
-  std::unique_ptr<PrefService> CreatePrefServiceAndRegisterPrefs() {
-    scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
-        new user_prefs::PrefRegistrySyncable());
-    HelpAppNotificationController::RegisterProfilePrefs(registry.get());
-    PrefServiceFactory factory;
-    factory.set_user_prefs(base::MakeRefCounted<TestingPrefStore>());
-    return factory.Create(registry);
-  }
-
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     help_app_notification_controller_ =
@@ -95,7 +75,6 @@
         {features::kHelpAppDiscoverTabNotificationAllChannels,
          features::kReleaseNotesNotificationAllChannels},
         /*disabled_features=*/{});
-    pref_service_ = CreatePrefServiceAndRegisterPrefs();
   }
 
   void TearDown() override {
@@ -131,24 +110,19 @@
         .value();
   }
 
-  PrefService* pref_service() { return pref_service_.get(); }
-
-  raw_ptr<FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager scoped_user_manager_;
   int notification_count_ = 0;
   std::unique_ptr<HelpAppNotificationController>
       help_app_notification_controller_;
   std::unique_ptr<NotificationDisplayServiceTester> notification_tester_;
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<PrefService> pref_service_;
 };
 
 // Tests for regular profiles.
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowAnyNotificationIfNewRegularProfile) {
-  std::unique_ptr<Profile> profile = CreateRegularProfile();
+  Profile* profile = CreateRegularProfile();
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowReleaseNotesNotification();
 
@@ -163,11 +137,11 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        ShowsReleaseNotesNotificationIfShownInOlderMilestone) {
-  std::unique_ptr<Profile> profile = CreateRegularProfile();
+  Profile* profile = CreateRegularProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   20);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowReleaseNotesNotification();
 
@@ -180,11 +154,11 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowReleaseNotificationIfAlreadyShownInCurrentMilestone) {
-  std::unique_ptr<Profile> profile = CreateRegularProfile();
+  Profile* profile = CreateRegularProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   CurrentMilestone());
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowReleaseNotesNotification();
 
@@ -194,9 +168,9 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowDiscoverNotificationIfNotChildProfile) {
-  std::unique_ptr<Profile> profile = CreateRegularProfile();
+  Profile* profile = CreateRegularProfile();
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   20);
 
@@ -209,9 +183,9 @@
 // Tests for Child profile.
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowAnyNotificationIfNewChildProfile) {
-  std::unique_ptr<Profile> profile = CreateChildProfile();
+  Profile* profile = CreateChildProfile();
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowReleaseNotesNotification();
 
@@ -227,12 +201,12 @@
 // TODO(b/187774783): Remove this when discover tab is supported in all locales.
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowDiscoverNotificationIfSystemLanguageNotEnglish) {
-  std::unique_ptr<Profile> profile = CreateChildProfile();
+  Profile* profile = CreateChildProfile();
   g_browser_process->SetApplicationLocale("fr");
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   20);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowDiscoverNotification();
 
@@ -242,11 +216,11 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        ShowsDiscoverNotificationIfShownInPreviousMilestone) {
-  std::unique_ptr<Profile> profile = CreateChildProfile();
+  Profile* profile = CreateChildProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   91);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowDiscoverNotification();
 
@@ -259,11 +233,11 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        DoesNotShowMoreThanOneNotificationPerMilestone) {
-  std::unique_ptr<Profile> profile = CreateChildProfile();
+  Profile* profile = CreateChildProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   91);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   controller->MaybeShowDiscoverNotification();
 
@@ -279,11 +253,11 @@
 // Tests for suggestion chips.
 TEST_F(HelpAppNotificationControllerTest,
        UpdatesReleaseNotesChipPrefWhenReleaseNotesNotificationShown) {
-  std::unique_ptr<Profile> profile = CreateRegularProfile();
+  Profile* profile = CreateRegularProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   20);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   EXPECT_EQ(0, profile->GetPrefs()->GetInteger(
                    prefs::kReleaseNotesSuggestionChipTimesLeftToShow));
@@ -296,11 +270,11 @@
 
 TEST_F(HelpAppNotificationControllerTest,
        UpdatesDiscoverTabChipPrefWhenDiscoverTabNotificationShown) {
-  std::unique_ptr<Profile> profile = CreateChildProfile();
+  Profile* profile = CreateChildProfile();
   profile->GetPrefs()->SetInteger(prefs::kHelpAppNotificationLastShownMilestone,
                                   20);
   std::unique_ptr<HelpAppNotificationController> controller =
-      std::make_unique<HelpAppNotificationController>(profile.get());
+      std::make_unique<HelpAppNotificationController>(profile);
 
   EXPECT_EQ(0, profile->GetPrefs()->GetInteger(
                    prefs::kReleaseNotesSuggestionChipTimesLeftToShow));
diff --git a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_untrusted_ui_config.cc b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_untrusted_ui_config.cc
index eb937eed..212b68e 100644
--- a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_untrusted_ui_config.cc
+++ b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_untrusted_ui_config.cc
@@ -110,6 +110,8 @@
     source->AddBoolean("HelpAppAutoTriggerInstallDialog",
                        base::FeatureList::IsEnabled(
                            features::kHelpAppAutoTriggerInstallDialog));
+    source->AddBoolean("AppInstallServiceUri",
+                       chromeos::features::IsAppInstallServiceUriEnabled());
   }
 
   Profile* profile = Profile::FromWebUI(web_ui);
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/common/api_guard_delegate_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/api/common/api_guard_delegate_unittest.cc
index 40243db..84e3319 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/common/api_guard_delegate_unittest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/common/api_guard_delegate_unittest.cc
@@ -136,13 +136,6 @@
     // Make sure device manufacturer is allowlisted.
     SetDeviceManufacturer(manufacturer());
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    auto user_manager = std::make_unique<ash::FakeChromeUserManager>();
-    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(user_manager));
-    AddUserAndLogIn();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
     auto params = crosapi::mojom::BrowserInitParams::New();
     params->is_current_user_device_owner = true;
@@ -153,22 +146,10 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   }
 
-  void TearDown() override {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    // Explicitly removing the user is required; otherwise ProfileHelper keeps a
-    // dangling pointer to the User.
-    // TODO(b/208629291): Consider removing all users from ProfileHelper in the
-    // destructor of ash::FakeChromeUserManager.
-    if (GetFakeUserManager().GetActiveUser()) {
-      GetFakeUserManager().RemoveUserFromList(
-          GetFakeUserManager().GetActiveUser()->GetAccountId());
-    }
-    scoped_user_manager_.reset();
+  std::string GetDefaultProfileName() override { return kUserEmail; }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-    BrowserWithTestWindowTest::TearDown();
-  }
-
  protected:
   extensions::ExtensionId extension_id() const {
     return GetParam().extension_id;
@@ -183,25 +164,10 @@
   const extensions::Extension* extension() { return extension_.get(); }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  ash::FakeChromeUserManager& GetFakeUserManager() {
-    return CHECK_DEREF(static_cast<ash::FakeChromeUserManager*>(
-        user_manager::UserManager::Get()));
-  }
-
-  virtual void AddUserAndLogIn() {
-    auto& user_manager = GetFakeUserManager();
-    // Make sure the current user is affiliated.
-    const AccountId account_id = AccountId::FromUserEmail(kUserEmail);
-    user_manager.AddUser(account_id);
-    user_manager.LoginUser(account_id);
-    user_manager.SwitchActiveUser(account_id);
-  }
-
   void SetUserAsOwner() {
-    auto& user_manager = GetFakeUserManager();
     // Make sure the current user is affiliated.
     const AccountId account_id = AccountId::FromUserEmail(kUserEmail);
-    user_manager.SetOwnerId(account_id);
+    user_manager()->SetOwnerId(account_id);
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -248,18 +214,13 @@
   scoped_refptr<const extensions::Extension> extension_;
   std::unique_ptr<HardwareInfoDelegate::Factory>
       hardware_info_delegate_factory_;
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 };
 
 TEST_P(ApiGuardDelegateTest, CurrentUserNotOwner) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  auto& user_manager = GetFakeUserManager();
   // Make sure the current user is not the device owner.
   const AccountId regular_user = AccountId::FromUserEmail("regular@gmail.com");
-  user_manager.SetOwnerId(regular_user);
+  user_manager()->SetOwnerId(regular_user);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -430,13 +391,15 @@
 
  protected:
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  void AddUserAndLogIn() override {
-    auto& user_manager = GetFakeUserManager();
+  void LogIn(const std::string& email) override {
     // Make sure the current user is affiliated.
-    const AccountId account_id = AccountId::FromUserEmail("user@example.com");
-    user_manager.AddUserWithAffiliation(account_id, /*is_affiliated=*/true);
-    user_manager.LoginUser(account_id);
-    user_manager.SwitchActiveUser(account_id);
+    const AccountId account_id = AccountId::FromUserEmail(email);
+    user_manager()->AddUserWithAffiliation(account_id, /*is_affiliated=*/true);
+    user_manager()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 };
@@ -643,10 +606,11 @@
     return ash::kShimlessRmaAppBrowserContextBaseName;
   }
 
-  // ApiGuardDelegateTest overrides.
-  void AddUserAndLogIn() override {
-    // No user is logged in during Shimless RMA.
-  }
+  // Do nothing for special profile for shimless RMA App.
+  void LogIn(const std::string& email) override {}
+  void SwitchActiveUser(const std::string& email) override {}
+  void OnUserProfileCreated(const std::string& email,
+                            Profile* profile) override {}
 
  private:
   base::test::ScopedFeatureList feature_list_;
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index 1637b74da..efc22ad 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -2340,7 +2340,8 @@
   if (UseOopif()) {
     content::NavigateIframeToURL(web_contents,
                                  /*iframe_id=*/"test", subframe_url);
-    GetTestPdfViewerStreamManager()->WaitUntilPdfLoadedInFirstChild();
+    ASSERT_TRUE(
+        GetTestPdfViewerStreamManager()->WaitUntilPdfLoadedInFirstChild());
 
     content::RenderFrameHost* extension_host =
         pdf_extension_test_util::GetOnlyPdfExtensionHost(web_contents);
@@ -2525,7 +2526,8 @@
   if (UseOopif()) {
     content::NavigateIframeToURL(web_contents,
                                  /*iframe_id=*/"test", subframe_url);
-    GetTestPdfViewerStreamManager()->WaitUntilPdfLoadedInFirstChild();
+    ASSERT_TRUE(
+        GetTestPdfViewerStreamManager()->WaitUntilPdfLoadedInFirstChild());
 
     target_frame = pdf_extension_test_util::GetOnlyPdfPluginFrame(web_contents);
     ASSERT_TRUE(target_frame);
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
index 58c4507..e10427e 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "chrome/browser/ash/attestation/mock_tpm_challenge_key.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/platform_keys/key_permissions/fake_user_private_token_kpm_service.h"
 #include "chrome/browser/ash/platform_keys/key_permissions/mock_key_permissions_manager.h"
 #include "chrome/browser/ash/platform_keys/key_permissions/user_private_token_kpm_service_factory.h"
@@ -29,7 +28,6 @@
 #include "chromeos/ash/components/dbus/constants/attestation_constants.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "extensions/browser/api_test_utils.h"
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/common/extension_builder.h"
@@ -61,10 +59,7 @@
 
 class EPKChallengeKeyTestBase : public BrowserWithTestWindowTest {
  protected:
-  EPKChallengeKeyTestBase()
-      : extension_(ExtensionBuilder("Test").Build()),
-        fake_user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {
+  EPKChallengeKeyTestBase() : extension_(ExtensionBuilder("Test").Build()) {
     stub_install_attributes_.SetCloudManaged("google.com", "device_id");
   }
 
@@ -114,9 +109,9 @@
 
   void LogIn(const std::string& email) override {
     const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUserWithAffiliation(account_id,
-                                               /*is_affiliated=*/true);
-    fake_user_manager_->UserLoggedIn(
+    user_manager()->AddUserWithAffiliation(account_id,
+                                           /*is_affiliated=*/true);
+    user_manager()->UserLoggedIn(
         account_id,
         user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
         /*browser_restart=*/false,
@@ -178,10 +173,6 @@
 
   scoped_refptr<const extensions::Extension> extension_;
   ash::StubInstallAttributes stub_install_attributes_;
-  // fake_user_manager_ is owned by user_manager_enabler_.
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_ =
-      nullptr;
-  user_manager::ScopedUserManager user_manager_enabler_;
   ash::platform_keys::MockKeyPermissionsManager key_permissions_manager_;
   raw_ptr<PrefService, DanglingUntriaged> prefs_ = nullptr;
   raw_ptr<ash::attestation::MockTpmChallengeKey, DanglingUntriaged>
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
index 4d03cd64..88de4db 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
@@ -10,14 +10,12 @@
 #include "base/memory/raw_ptr.h"
 #include "base/values.h"
 #include "chrome/browser/ash/attestation/mock_tpm_challenge_key.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "extensions/browser/api_test_utils.h"
 #include "extensions/common/extension_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -35,11 +33,7 @@
 
 class EPKPChallengeKeyTestBase : public BrowserWithTestWindowTest {
  protected:
-  EPKPChallengeKeyTestBase()
-      : fake_user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {
-    extension_ = ExtensionBuilder("Test").Build();
-  }
+  EPKPChallengeKeyTestBase() : extension_(ExtensionBuilder("Test").Build()) {}
 
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
@@ -58,9 +52,9 @@
   // This will be called by BrowserWithTestWindowTest::SetUp();
   void LogIn(const std::string& email) override {
     const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUserWithAffiliation(account_id,
-                                               /*is_affiliated=*/true);
-    fake_user_manager_->UserLoggedIn(
+    user_manager()->AddUserWithAffiliation(account_id,
+                                           /*is_affiliated=*/true);
+    user_manager()->UserLoggedIn(
         account_id,
         user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
         /*browser_restart=*/false,
@@ -77,11 +71,6 @@
   }
 
   scoped_refptr<const Extension> extension_;
-  // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-  // fake_user_manager_ is owned by user_manager_enabler_.
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_ =
-      nullptr;
-  user_manager::ScopedUserManager user_manager_enabler_;
   raw_ptr<PrefService, DanglingUntriaged> prefs_ = nullptr;
 };
 
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
index d70d0e1..be1c228b 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/ash/login/smart_lock/smart_lock_service.h"
 #include "chrome/browser/ash/login/smart_lock/smart_lock_service_factory.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/extension_api_unittest.h"
@@ -54,7 +53,6 @@
 #include "components/prefs/testing_pref_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/user_manager/known_user.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api_test_utils.h"
 #include "extensions/browser/extension_function_dispatcher.h"
@@ -83,8 +81,6 @@
 }
 
 constexpr char kTestUserEmail[] = "testuser@gmail.com";
-constexpr char kTestUserGaiaId[] = "9876543210";
-constexpr char kTestUserEmailHash[] = "testuser@gmail.com-hash";
 constexpr char kInvalidToken[] = "invalid";
 constexpr char kValidPassword[] = "valid";
 constexpr char kInvalidPassword[] = "invalid";
@@ -185,7 +181,7 @@
 
     const cryptohome::AccountIdentifier account_id =
         cryptohome::CreateAccountIdentifierFromAccountId(
-            AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId));
+            AccountId::FromUserEmail(kTestUserEmail));
 
     ash::Key key{kValidPassword};
     key.Transform(ash::Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
@@ -201,10 +197,6 @@
 
     ash::SystemSaltGetter::Initialize();
 
-    auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>();
-    fake_user_manager_ = fake_user_manager.get();
-    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(fake_user_manager));
     auth_parts_ = ash::AuthPartsImpl::CreateTestInstance();
     auth_parts_->SetAuthSessionStorage(
         std::make_unique<ash::AuthSessionStorageImpl>(
@@ -231,13 +223,6 @@
 
   std::string GetDefaultProfileName() override { return kTestUserEmail; }
 
-  void LogIn(const std::string& email) override {
-    auto test_account = AccountId::FromUserEmailGaiaId(email, kTestUserGaiaId);
-    fake_user_manager_->AddUser(test_account);
-    fake_user_manager_->UserLoggedIn(test_account, kTestUserEmailHash, false,
-                                     false);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto pref_service =
         std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
@@ -247,28 +232,26 @@
     TestingProfile* profile = profile_manager()->CreateTestingProfile(
         profile_name, std::move(pref_service), u"Test profile",
         1 /* avatar_id */, GetTestingFactories());
+    OnUserProfileCreated(profile_name, profile);
 
     // Setup a primary user.
-    auto test_account =
-        AccountId::FromUserEmailGaiaId(profile_name, kTestUserGaiaId);
-    fake_user_manager_->SimulateUserProfileLoad(test_account);
     ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
-        fake_user_manager_->GetPrimaryUser(), profile);
+        user_manager()->GetPrimaryUser(), profile);
 
     // Generate an auth token.
-    auth_token_user_context_.SetAccountId(test_account);
-    auth_token_user_context_.SetUserIDHash(kTestUserEmailHash);
+    AccountId account_id = AccountId::FromUserEmail(profile_name);
+    auth_token_user_context_.SetAccountId(account_id);
+    auth_token_user_context_.SetUserIDHash(
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id));
     auth_token_user_context_.SetSessionLifetime(
         base::Time::Now() + ash::quick_unlock::AuthToken::kTokenExpiration);
     if (std::get<0>(GetParam()) == TestType::kCryptohome) {
       auto* fake_userdataauth_client_testapi =
           ash::FakeUserDataAuthClient::TestApi::Get();
 
-      const cryptohome::AccountIdentifier account_id =
-          cryptohome::CreateAccountIdentifierFromAccountId(
-              AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId));
       auto session_ids = fake_userdataauth_client_testapi->AddSession(
-          account_id, /*authenticated=*/true);
+          cryptohome::CreateAccountIdentifierFromAccountId(account_id),
+          /*authenticated=*/true);
       auth_token_user_context_.SetAuthSessionIds(session_ids.first,
                                                  session_ids.second);
       // Technically configuration should contain password as factor, but
@@ -290,8 +273,6 @@
 
     ExtensionApiUnittest::TearDown();
 
-    fake_user_manager_ = nullptr;
-    scoped_user_manager_.reset();
     test_api_.reset();
 
     ash::SystemSaltGetter::Shutdown();
@@ -523,8 +504,7 @@
 
   // Returns if the pin is set in the backend.
   bool IsPinSetInBackend() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
 
     base::test::TestFuture<bool> is_pin_set_future;
     ash::quick_unlock::PinBackend::GetInstance()->IsSet(
@@ -546,43 +526,37 @@
   }
 
   int GetExposedPinLength() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     return user_manager::KnownUser(g_browser_process->local_state())
         .GetUserPinLength(account_id);
   }
 
   void ClearExposedPinLength() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     user_manager::KnownUser(g_browser_process->local_state())
         .SetUserPinLength(account_id, 0);
   }
 
   bool IsBackfillNeeded() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     return user_manager::KnownUser(g_browser_process->local_state())
         .PinAutosubmitIsBackfillNeeded(account_id);
   }
 
   void SetBackfillNotNeeded() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     user_manager::KnownUser(g_browser_process->local_state())
         .PinAutosubmitSetBackfillNotNeeded(account_id);
   }
 
   void SetBackfillNeededForTests() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     user_manager::KnownUser(g_browser_process->local_state())
         .PinAutosubmitSetBackfillNeededForTests(account_id);
   }
 
   void OnUpdateUserPods() {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     ash::quick_unlock::PinBackend::GetInstance()->GetExposedPinLength(
         account_id);
   }
@@ -612,8 +586,7 @@
 
   // Run an authentication attempt with the plain-text |password|.
   bool TryAuthenticate(const std::string& password) {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     auto user_context = std::make_unique<ash::UserContext>(
         user_manager::UserType::kRegular, account_id);
     user_context->SetIsUsingPin(true);
@@ -629,8 +602,7 @@
   }
 
   bool SetPinAutosubmitEnabled(const std::string& pin, const bool enabled) {
-    const AccountId account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
+    const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
     base::test::TestFuture<bool> set_pin_future;
     ash::quick_unlock::PinBackend::GetInstance()->SetPinAutoSubmitEnabled(
         account_id, pin, enabled, set_pin_future.GetCallback());
@@ -685,8 +657,6 @@
   }
 
   std::unique_ptr<ash::AuthPartsImpl> auth_parts_;
-  raw_ptr<ash::FakeChromeUserManager> fake_user_manager_ = nullptr;
-  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   QuickUnlockPrivateSetModesFunction::ModesChangedEventHandler
       modes_changed_handler_;
   bool expect_modes_changed_ = false;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index c3c0971..48865a0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4320,6 +4320,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "file-system-provider-cloud-file-system",
+    "owners": [ "benreich@chromium.org", "cassycc@chromium.org", "simmonsjosh@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "file-system-provider-content-cache",
     "owners": [ "benreich@chromium.org", "cassycc@chromium.org", "simmonsjosh@google.com" ],
     "expiry_milestone": 130
@@ -4380,14 +4385,6 @@
     "expiry_milestone": 125
   },
   {
-    "name": "firmware-update-jelly",
-    "owners": [
-      "//ash/webui/firmware_update_ui/OWNERS",
-      "cros-peripherals@google.com"
-    ],
-    "expiry_milestone": 130
-  },
-  {
     "name": "firmware-update-ui-v2",
     "owners": [
       "//ash/webui/firmware_update_ui/OWNERS",
@@ -6556,14 +6553,6 @@
     "expiry_milestone": 122
   },
   {
-    "name": "print-management-jelly",
-    "owners": [
-      "//ash/webui/print_management/OWNERS",
-      "cros-peripherals@google.com"
-    ],
-    "expiry_milestone": 130
-  },
-  {
     "name": "print-management-setup-assistance",
     "owners": [
       "//ash/webui/print_management/OWNERS"
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 3c42d23..0bf2fefb 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -6534,17 +6534,17 @@
 const char kFilesTrashDriveDescription[] =
     "Enable trash for Drive volume in Files App.";
 
+const char kFileSystemProviderCloudFileSystemName[] =
+    "Enable CloudFileSystem for FileSystemProvider extensions.";
+const char kFileSystemProviderCloudFileSystemDescription[] =
+    "Enable the ability for individual FileSystemProvider extensions to "
+    "be serviced by a CloudFileSystem.";
+
 const char kFileSystemProviderContentCacheName[] =
     "Enable content caching for FileSystemProvider extensions.";
 const char kFileSystemProviderContentCacheDescription[] =
-    "Enable the ability for individual FileSystemProvider extensions to "
-    "leverage a content cache.";
-
-const char kFirmwareUpdateJellyName[] =
-    "Enable jelly colors for the Firmware Update App";
-const char kFirmwareUpdateJellyDescription[] =
-    "Enable jelly colors for the Firmware Update App. Requires "
-    "jelly-colors flag to be enabled.";
+    "Enable the ability for individual FileSystemProvider extensions being "
+    "serviced by CloudFileSystem to leverage a content cache.";
 
 const char kFirmwareUpdateUIV2Name[] =
     "Enables the v2 version of the Firmware Updates app";
@@ -7000,12 +7000,6 @@
     "The channel from which PPD index "
     "is loaded when matching PPD files during printer setup.";
 
-const char kPrintManagementJellyName[] =
-    "Enable jelly colors for the Print Management App";
-const char kPrintManagementJellyDescription[] =
-    "Enable jelly colors for the Print Management App. Requires "
-    "jelly-colors flag to be enabled.";
-
 const char kPrintManagementSetupAssistanceName[] =
     "Enable improved printer setup experience for the Print Management App";
 const char kPrintManagementSetupAssistanceDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2012d55..6380119 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3765,12 +3765,12 @@
 extern const char kFilesTrashDriveName[];
 extern const char kFilesTrashDriveDescription[];
 
+extern const char kFileSystemProviderCloudFileSystemName[];
+extern const char kFileSystemProviderCloudFileSystemDescription[];
+
 extern const char kFileSystemProviderContentCacheName[];
 extern const char kFileSystemProviderContentCacheDescription[];
 
-extern const char kFirmwareUpdateJellyName[];
-extern const char kFirmwareUpdateJellyDescription[];
-
 extern const char kFirmwareUpdateUIV2Name[];
 extern const char kFirmwareUpdateUIV2Description[];
 
@@ -4023,9 +4023,6 @@
 extern const char kPrintingPpdChannelName[];
 extern const char kPrintingPpdChannelDescription[];
 
-extern const char kPrintManagementJellyName[];
-extern const char kPrintManagementJellyDescription[];
-
 extern const char kPrintManagementSetupAssistanceName[];
 extern const char kPrintManagementSetupAssistanceDescription[];
 
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index da057dc..60103a4d 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -338,10 +338,14 @@
       "providers/dial/dial_media_route_provider_unittest.cc",
       "providers/wired_display/wired_display_media_route_provider_unittest.cc",
     ]
+    if (is_win) {
+      sources += [ "discovery/discovery_network_list_win_unittest.cc" ]
+    }
     deps += [
       ":logger_list",
       ":test_support",
       "//chrome/browser/media/router/discovery",
+      "//chrome/browser/media/router/discovery:test_support",
       "//chrome/browser/media/router/discovery/access_code:access_code_cast_feature",
       "//chrome/browser/media/router/discovery/access_code:access_code_sink_service",
       "//chrome/browser/media/router/discovery/access_code:discovery_resources_proto",
@@ -374,7 +378,7 @@
 }
 
 # TODO(crbug.com/1290541): Fails to link on Fuchsia builds.
-# TODO(https://issuetracker.google.com/236160471): CDDL compiler doesn't build
+# TODO(issuetracker.google.com/236160471): CDDL compiler doesn't build
 # on Windows.
 if (!is_fuchsia && !is_win) {
   test("openscreen_unittests") {
@@ -383,8 +387,12 @@
       "//components/media_router/common/providers/cast/certificate:openscreen_certificate_verifier",
       "//components/openscreen_platform",
       "//testing/gmock",
-      "//third_party/openscreen/src:openscreen_unittests_all",
     ]
+
+    # TODO(issues.chromium.org/41496044): Re-enable for MSAN once fixed.
+    if (!is_msan) {
+      deps += [ "//third_party/openscreen/src:openscreen_unittests_all" ]
+    }
   }
 }
 
diff --git a/chrome/browser/media/router/discovery/BUILD.gn b/chrome/browser/media/router/discovery/BUILD.gn
index e2440c0..2f3c1aa3 100644
--- a/chrome/browser/media/router/discovery/BUILD.gn
+++ b/chrome/browser/media/router/discovery/BUILD.gn
@@ -103,7 +103,46 @@
   }
 
   if (is_win) {
-    sources += [ "discovery_network_list_win.cc" ]
+    sources += [
+      "discovery_network_list_win.cc",
+      "discovery_network_list_win.h",
+    ]
     libs = [ "iphlpapi.lib" ]
   }
 }
+
+source_set("test_support") {
+  testonly = true
+
+  cflags = []
+
+  sources = []
+
+  if (is_win) {
+    # Disable warnings about deprecated declarations in Windows SDK headers.
+    # FakeConnectionProfile implements the WinRT interface IConnectionProfile,
+    # which contains a few deprecated functions that are not used by Chromium.
+    cflags += [ "-Wno-deprecated-declarations" ]
+
+    sources += [
+      "test_support/win/fake_connection_profile.cc",
+      "test_support/win/fake_connection_profile.h",
+      "test_support/win/fake_ip_adapter_addresses.cc",
+      "test_support/win/fake_ip_adapter_addresses.h",
+      "test_support/win/fake_ip_helper.cc",
+      "test_support/win/fake_ip_helper.h",
+      "test_support/win/fake_mib_table.cc",
+      "test_support/win/fake_mib_table.h",
+      "test_support/win/fake_network_adapter.cc",
+      "test_support/win/fake_network_adapter.h",
+      "test_support/win/fake_network_information_statics.cc",
+      "test_support/win/fake_network_information_statics.h",
+      "test_support/win/fake_vector_view.h",
+      "test_support/win/fake_winrt_network_environment.cc",
+      "test_support/win/fake_winrt_network_environment.h",
+      "test_support/win/fake_wlan_connection_profile_details.cc",
+      "test_support/win/fake_wlan_connection_profile_details.h",
+    ]
+  }
+  deps = [ "//base" ]
+}
diff --git a/chrome/browser/media/router/discovery/discovery_network_list_win.cc b/chrome/browser/media/router/discovery/discovery_network_list_win.cc
index 312fd938..aa955ce 100644
--- a/chrome/browser/media/router/discovery/discovery_network_list_win.cc
+++ b/chrome/browser/media/router/discovery/discovery_network_list_win.cc
@@ -2,41 +2,58 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/media/router/discovery/discovery_network_list.h"
+#include "chrome/browser/media/router/discovery/discovery_network_list_win.h"
 
 #include <winsock2.h>
-#include <ws2tcpip.h>
+#include <wrl/client.h>
 
-#include <iphlpapi.h>  // NOLINT
 #include <windot11.h>  // NOLINT
 #include <wlanapi.h>   // NOLINT
 
 #include <algorithm>
 #include <cstring>
-#include <map>
+
 #include <utility>
 #include <vector>
 
 #include "base/check.h"
-#include "base/containers/small_map.h"
+
 #include "base/memory/ptr_util.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/win/hstring_reference.h"
+#include "base/win/scoped_hstring.h"
+#include "base/win/windows_version.h"
+#include "chrome/browser/media/router/discovery/discovery_network_list.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+namespace WinrtCollections = ABI::Windows::Foundation::Collections;
+
+using Microsoft::WRL::ComPtr;
 
 namespace media_router {
 namespace {
 
-struct GuidOperatorLess {
-  bool operator()(const GUID& guid1, const GUID& guid2) const {
-    return memcmp(&guid1, &guid2, sizeof(GUID)) < 0;
-  }
-};
+bool g_requires_network_location_permission_for_testing = false;
+
+WindowsOsApi& GetWindowsOsApi() {
+  static base::NoDestructor<WindowsOsApi> windows_os_api;
+  return *windows_os_api;
+}
 
 void IfTable2Deleter(PMIB_IF_TABLE2 interface_table) {
   if (interface_table) {
-    FreeMibTable(interface_table);
+    GetWindowsOsApi().ip_helper_api.free_mib_table_callback.Run(
+        interface_table);
   }
 }
 
+}  // namespace
+
+bool GuidOperatorLess::operator()(const GUID& guid1, const GUID& guid2) const {
+  return memcmp(&guid1, &guid2, sizeof(GUID)) < 0;
+}
+
 typedef DWORD(WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
                                               PVOID pReserved,
                                               PDWORD pdwNegotiatedVersion,
@@ -132,7 +149,8 @@
 base::small_map<std::map<GUID, std::string, GuidOperatorLess>>
 GetInterfaceGuidMacMap() {
   PMIB_IF_TABLE2 interface_table_raw = nullptr;
-  auto result = GetIfTable2(&interface_table_raw);
+  auto result = GetWindowsOsApi().ip_helper_api.get_if_table2_callback.Run(
+      &interface_table_raw);
   if (result != ERROR_SUCCESS) {
     return {};
   }
@@ -228,7 +246,176 @@
   return mac_ssid_map;
 }
 
-}  // namespace
+// Returns true when running on a Windows version that prompts for network
+// location permission.  At the time of this writing, the permission prompt
+// exists in Win11 24H2 only, which does not have a final build yet, but is
+// shipping publicly to Windows Insiders.
+//
+// See the following documentation for more detail:
+//
+// Changes to API behavior for Wi-Fi access and location
+// https://learn.microsoft.com/en-us/windows/win32/nativewifi/wi-fi-access-location-changes
+//
+// Announcing Windows 11 Insider Preview Build 25977 (Canary Channel)
+// https://blogs.windows.com/windows-insider/2023/10/18/announcing-windows-11-insider-preview-build-25977-canary-channel/
+[[nodiscard]] bool RequiresNetworkLocationPermission() {
+  const base::win::OSInfo::VersionNumber& os_version =
+      base::win::OSInfo::GetInstance()->version_number();
+
+  // Win11 uses 10 for its major version number.
+  const bool is_24h2_or_greater =
+      (os_version.major > 10) ||
+      (os_version.major == 10 && os_version.build >= 25977);
+  return is_24h2_or_greater ||
+         g_requires_network_location_permission_for_testing;
+}
+
+// Returns true when `connection_profile` is connected to a local network or the
+// internet.
+bool IsProfileConnectedToNetwork(
+    WinrtConnectivity::IConnectionProfile* connection_profile) {
+  WinrtConnectivity::NetworkConnectivityLevel connectivity;
+  HRESULT hr = connection_profile->GetNetworkConnectivityLevel(&connectivity);
+  if (hr != S_OK) {
+    return false;
+  }
+  return connectivity != WinrtConnectivity::NetworkConnectivityLevel::
+                             NetworkConnectivityLevel_None;
+}
+
+// Returns the GUID for the network interface adapter used by
+// `connection_profile`.
+HRESULT GetProfileNetworkAdapterId(
+    WinrtConnectivity::IConnectionProfile* connection_profile,
+    GUID* network_adapter_id) {
+  ComPtr<WinrtConnectivity::INetworkAdapter> network_adapter;
+  HRESULT hr = connection_profile->get_NetworkAdapter(&network_adapter);
+  if (hr != S_OK) {
+    return hr;
+  }
+  return network_adapter->get_NetworkAdapterId(network_adapter_id);
+}
+
+// Returns the WiFi SSID used by the`connection_profile` for network
+// connectivity. Returns an error when `connection_profile` does not use a WiFi
+// network adapter.
+HRESULT GetProfileWifiSSID(
+    WinrtConnectivity::IConnectionProfile* connection_profile,
+    HSTRING* out_ssid) {
+  ComPtr<WinrtConnectivity::IConnectionProfile2> connection_profile2;
+  HRESULT hr =
+      connection_profile->QueryInterface(IID_PPV_ARGS(&connection_profile2));
+  if (hr != S_OK) {
+    return hr;
+  }
+
+  ComPtr<WinrtConnectivity::IWlanConnectionProfileDetails>
+      wlan_connection_details;
+  hr = connection_profile2->get_WlanConnectionProfileDetails(
+      &wlan_connection_details);
+  if (hr != S_OK) {
+    return hr;
+  }
+
+  if (wlan_connection_details == nullptr) {
+    // `connection_profile` is not using WiFi.
+    return kWifiNotSupported;
+  }
+  return wlan_connection_details->GetConnectedSsid(out_ssid);
+}
+
+HRESULT GetAllConnectionProfiles(
+    ComPtr<WinrtCollections::IVectorView<
+        WinrtConnectivity::ConnectionProfile*>>* out_connection_profiles,
+    uint32_t* out_connection_profiles_size) {
+  ComPtr<WinrtConnectivity::INetworkInformationStatics>
+      network_information_statics;
+  HRESULT hr =
+      GetWindowsOsApi().winrt_api.ro_get_activation_factory_callback.Run(
+          base::win::HStringReference(
+              RuntimeClass_Windows_Networking_Connectivity_NetworkInformation)
+              .Get(),
+          IID_PPV_ARGS(&network_information_statics));
+  if (hr != S_OK) {
+    return hr;
+  }
+
+  ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+      connection_profiles;
+  hr = network_information_statics->GetConnectionProfiles(&connection_profiles);
+  if (hr != S_OK) {
+    return hr;
+  }
+
+  uint32_t connection_profiles_size;
+  hr = connection_profiles->get_Size(&connection_profiles_size);
+  if (hr != S_OK) {
+    return hr;
+  }
+
+  *out_connection_profiles = connection_profiles;
+  *out_connection_profiles_size = connection_profiles_size;
+  return S_OK;
+}
+
+// Returns a map from a network adapter's MAC address to its currently
+// associated WiFi SSID using WinRT Network APIs instead of Win32 WLAN APIS.
+// In particular, uses IWlanConnectionProfileDetails::GetConnectedSsid() to get
+// the SSID without prompting the user for network location permission.  The
+// Win32 version uses WlanQueryInterface(), which prompts for permission in
+// Win11 24H2.
+base::small_map<std::map<std::string, std::string>> GetMacSsidMapUsingWinrt() {
+  ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+      connection_profiles;
+  uint32_t connection_profiles_size = 0u;
+  HRESULT hr =
+      GetAllConnectionProfiles(&connection_profiles, &connection_profiles_size);
+  if (hr != S_OK) {
+    return {};
+  }
+
+  auto guid_mac_map = GetInterfaceGuidMacMap();
+  base::small_map<std::map<std::string, std::string>> mac_ssid_map;
+
+  // This loop finds each connected wireless interface, mapping its MAC address
+  // to its SSID.
+  for (uint32_t i = 0u; i < connection_profiles_size; ++i) {
+    ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile;
+    hr = connection_profiles->GetAt(i, &connection_profile);
+    if (hr != S_OK) {
+      continue;
+    }
+
+    if (!IsProfileConnectedToNetwork(connection_profile.Get())) {
+      // Skip disconnected profiles.
+      continue;
+    }
+
+    HSTRING ssid_hstring;
+    hr = GetProfileWifiSSID(connection_profile.Get(), &ssid_hstring);
+    if (hr != S_OK) {
+      // Skip ethernet and cellular profiles.
+      continue;
+    }
+    base::win::ScopedHString ssid(ssid_hstring);
+
+    GUID network_adapter_id;
+    hr = GetProfileNetworkAdapterId(connection_profile.Get(),
+                                    &network_adapter_id);
+    if (hr != S_OK) {
+      continue;
+    }
+
+    const auto mac_entry = guid_mac_map.find(network_adapter_id);
+    if (mac_entry == guid_mac_map.end()) {
+      continue;
+    }
+
+    mac_ssid_map.emplace(/*wifi_network_adapter_mac_address=*/mac_entry->second,
+                         ssid.GetAsUTF8());
+  }
+  return mac_ssid_map;
+}
 
 std::vector<DiscoveryNetworkInfo> GetDiscoveryNetworkInfoList() {
   // Max number of times to retry GetAdaptersAddresses due to
@@ -236,10 +423,6 @@
   // due to an unforeseen reason, we don't want to be stuck in an endless loop.
   constexpr int kMaxGetAdaptersAddressTries = 10;
 
-  // Use an initial buffer size of 15KB, as recommended by MSDN. See:
-  // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
-  constexpr int kInitialAddressBufferSize = 15000;
-
   constexpr ULONG kAddressFlags =
       GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
       GAA_FLAG_SKIP_DNS_SERVER;
@@ -251,7 +434,7 @@
   // the required size.  Although it's very unlikely that two successive calls
   // will both require increasing the buffer size, there's no guarantee that
   // this won't happen; this is what the maximum retry count guards against.
-  ULONG addresses_buffer_size = kInitialAddressBufferSize;
+  ULONG addresses_buffer_size = kGetAdaptersAddressesInitialBufferSize;
   std::unique_ptr<char[]> addresses_buffer;
   PIP_ADAPTER_ADDRESSES adapter_addresses = nullptr;
   ULONG result = ERROR_BUFFER_OVERFLOW;
@@ -261,8 +444,10 @@
     addresses_buffer.reset(new char[addresses_buffer_size]);
     adapter_addresses =
         reinterpret_cast<PIP_ADAPTER_ADDRESSES>(addresses_buffer.get());
-    result = GetAdaptersAddresses(AF_UNSPEC, kAddressFlags, nullptr,
-                                  adapter_addresses, &addresses_buffer_size);
+    result =
+        GetWindowsOsApi().ip_helper_api.get_adapters_addresses_callback.Run(
+            AF_UNSPEC, kAddressFlags, nullptr, adapter_addresses,
+            &addresses_buffer_size);
   }
 
   if (result != NO_ERROR) {
@@ -270,7 +455,12 @@
   }
 
   std::vector<DiscoveryNetworkInfo> network_ids;
-  auto mac_ssid_map = GetMacSsidMap();
+  base::small_map<std::map<std::string, std::string>> mac_ssid_map;
+  if (RequiresNetworkLocationPermission()) {
+    mac_ssid_map = GetMacSsidMapUsingWinrt();
+  } else {
+    mac_ssid_map = GetMacSsidMap();
+  }
   for (const IP_ADAPTER_ADDRESSES* current_adapter = adapter_addresses;
        current_adapter != nullptr; current_adapter = current_adapter->Next) {
     // We only want adapters which are up and either Ethernet or wireless, so we
@@ -315,4 +505,25 @@
   return network_ids;
 }
 
+WindowsOsApi::WindowsOsApi() = default;
+WindowsOsApi::WindowsOsApi(const WindowsOsApi& source) = default;
+WindowsOsApi::~WindowsOsApi() = default;
+
+WindowsOsApi::IpHelperApi::IpHelperApi() = default;
+WindowsOsApi::IpHelperApi::IpHelperApi(const IpHelperApi& source) = default;
+WindowsOsApi::IpHelperApi::~IpHelperApi() = default;
+
+WindowsOsApi::WinrtApi::WinrtApi() = default;
+WindowsOsApi::WinrtApi::WinrtApi(const WinrtApi& source) = default;
+WindowsOsApi::WinrtApi::~WinrtApi() = default;
+
+void OverrideWindowsOsApiForTesting(WindowsOsApi overridden_api) {
+  GetWindowsOsApi() = overridden_api;
+}
+
+void OverrideRequiresNetworkLocationPermissionForTesting(  // IN-TEST
+    bool requires_permission) {
+  g_requires_network_location_permission_for_testing = requires_permission;
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/discovery_network_list_win.h b/chrome/browser/media/router/discovery/discovery_network_list_win.h
new file mode 100644
index 0000000..d58a80c
--- /dev/null
+++ b/chrome/browser/media/router/discovery/discovery_network_list_win.h
@@ -0,0 +1,129 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_DISCOVERY_NETWORK_LIST_WIN_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DISCOVERY_NETWORK_LIST_WIN_H_
+
+#include <roapi.h>
+#include <windows.networking.connectivity.h>
+#include <ws2tcpip.h>
+
+// iphlpapi.h must be included after ws2tcpip.h to use MIB_IF_TABLE2.
+#include <iphlpapi.h>  // NOLINT
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/containers/small_map.h"
+#include "base/functional/callback.h"
+#include "base/win/core_winrt_util.h"
+
+namespace media_router {
+// Declares helper functions and constants used to implement
+// GetDiscoveryNetworkInfoList() on Windows.  Exposes these helpers for unit
+// testing.
+
+// To receive the output from GetAdaptersAddresses(), use an initial buffer size
+// of 15KB, as recommended by MSDN. See:
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+inline constexpr int kGetAdaptersAddressesInitialBufferSize = 15000;
+
+// Returned by GetProfileWifiSSID() for network profiles that do not use WiFi.
+inline constexpr HRESULT kWifiNotSupported =
+    HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+
+struct GuidOperatorLess {
+  bool operator()(const GUID& guid1, const GUID& guid2) const;
+};
+
+base::small_map<std::map<GUID, std::string, GuidOperatorLess>>
+GetInterfaceGuidMacMap();
+
+[[nodiscard]] bool IsProfileConnectedToNetwork(
+    ABI::Windows::Networking::Connectivity::IConnectionProfile*
+        connection_profile);
+
+[[nodiscard]] HRESULT GetProfileNetworkAdapterId(
+    ABI::Windows::Networking::Connectivity::IConnectionProfile*
+        connection_profile,
+    GUID* network_adapter_id);
+
+[[nodiscard]] HRESULT GetProfileWifiSSID(
+    ABI::Windows::Networking::Connectivity::IConnectionProfile*
+        connection_profile,
+    HSTRING* out_ssid);
+
+[[nodiscard]] HRESULT GetAllConnectionProfiles(
+    Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVectorView<
+        ABI::Windows::Networking::Connectivity::ConnectionProfile*>>*
+        out_connection_profiles,
+    uint32_t* out_connection_profiles_size);
+
+base::small_map<std::map<std::string, std::string>> GetMacSsidMapUsingWinrt();
+
+// Enable tests to override Windows OS APIs to simulate different networking
+// environments.  Contains callbacks for each OS API function used by
+// GetDiscoveryNetworkInfoList().  By default, the callbacks bind to the actual
+// OS API function.  Tests may bind these callbacks to fake implementations of
+// the OS APIs and then call OverrideWindowsOsApiForTesting().
+struct WindowsOsApi {
+  WindowsOsApi();
+  WindowsOsApi(const WindowsOsApi& source);
+  ~WindowsOsApi();
+
+  // Override Win32 functions declared in iphlpapi.h.
+  struct IpHelperApi {
+    IpHelperApi();
+    IpHelperApi(const IpHelperApi& source);
+    ~IpHelperApi();
+
+    // Callbacks for GetAdaptersAddresses, GetIfTable2, and FreeMibTable.
+    // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
+    using GetAdaptersAddressesCallback =
+        base::RepeatingCallback<ULONG(ULONG family,
+                                      ULONG flags,
+                                      void* reserved,
+                                      IP_ADAPTER_ADDRESSES* adapter_addresses,
+                                      ULONG* size_pointer)>;
+    GetAdaptersAddressesCallback get_adapters_addresses_callback =
+        base::BindRepeating(&GetAdaptersAddresses);
+
+    // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getiftable2
+    using GetIfTable2Callback =
+        base::RepeatingCallback<DWORD(MIB_IF_TABLE2** out_table)>;
+    GetIfTable2Callback get_if_table2_callback =
+        base::BindRepeating(&GetIfTable2);
+
+    // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-freemibtable
+    using FreeMibTableCallback = base::RepeatingCallback<void(void* table)>;
+    FreeMibTableCallback free_mib_table_callback =
+        base::BindRepeating(&FreeMibTable);
+  };
+  IpHelperApi ip_helper_api;
+
+  // Override RoGetActivationFactory() to return fake WinRT objects.
+  struct WinrtApi {
+    WinrtApi();
+    WinrtApi(const WinrtApi& source);
+    ~WinrtApi();
+
+    // https://learn.microsoft.com/en-us/windows/win32/api/roapi/nf-roapi-rogetactivationfactory
+    using RoGetActivationFactoryCallback =
+        base::RepeatingCallback<HRESULT(HSTRING, const IID&, void**)>;
+    RoGetActivationFactoryCallback ro_get_activation_factory_callback =
+        base::BindRepeating(&base::win::RoGetActivationFactory);
+  };
+  WinrtApi winrt_api;
+};
+void OverrideWindowsOsApiForTesting(WindowsOsApi overridden_api);
+
+// Set to true to always use the WinRT implementation of
+// GetDiscoveryNetworkInfoList().
+void OverrideRequiresNetworkLocationPermissionForTesting(
+    bool requires_permission);
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DISCOVERY_NETWORK_LIST_WIN_H_
diff --git a/chrome/browser/media/router/discovery/discovery_network_list_win_unittest.cc b/chrome/browser/media/router/discovery/discovery_network_list_win_unittest.cc
new file mode 100644
index 0000000..95b234f
--- /dev/null
+++ b/chrome/browser/media/router/discovery/discovery_network_list_win_unittest.cc
@@ -0,0 +1,594 @@
+// Copyright 2024 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/media/router/discovery/discovery_network_list_win.h"
+
+#include <wrl/client.h>
+
+#include <optional>
+
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/win/scoped_hstring.h"
+#include "chrome/browser/media/router/discovery/discovery_network_list.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+namespace WinrtCollections = ABI::Windows::Foundation::Collections;
+
+using Microsoft::WRL::ComPtr;
+
+namespace media_router {
+
+namespace {
+
+constexpr const char kWiredAdapterName[] = "fake_wired_adapter";
+constexpr GUID kWiredNetworkAdapterId = {
+    0xf8eb6b9e,
+    0xa77e,
+    0x44c8,
+    {0xa2, 0xf0, 0x4, 0x44, 0x84, 0xc6, 0xb6, 0xcd}};
+constexpr uint8_t kWiredMacAddress[] = {0x00, 0x21, 0x02, 0x43, 0x14, 0x05};
+
+constexpr const char kWifiAdapterName[] = "fake_wifi_adapter";
+constexpr GUID kWifiNetworkAdapterId = {
+    0xf494958d,
+    0x661a,
+    0x45f1,
+    {0x9e, 0x91, 0xb6, 0x58, 0x38, 0x77, 0x7f, 0xe1}};
+constexpr uint8_t kWifiMacAddress[] = {0x08, 0xf2, 0x06, 0xc4, 0xa1, 0x07};
+constexpr const char kWifiSsid[] = "fake_wifi_ssid";
+
+}  // namespace
+
+class DiscoveryNetworkListWinTest : public testing::Test {
+ public:
+  DiscoveryNetworkListWinTest() = default;
+  ~DiscoveryNetworkListWinTest() override = default;
+
+  void SetUp() override;
+  void TearDown() override;
+
+  // Add network adapters and connection profiles to the fake Windows OS API
+  // implementations.  AddWiredConnectionProfile() can be called once to setup a
+  // network adapter using the kWired* constants defined above.
+  // AddWifiConnectionProfile() can be called once to setup a network adapter
+  // using the kWifi* constants defined above.  If a test requires more
+  // adapters, it can call AddConnectionProfile() with test defined values.
+  ComPtr<WinrtConnectivity::IConnectionProfile> AddWiredConnectionProfile(
+      WinrtConnectivity::NetworkConnectivityLevel connectivity);
+  ComPtr<WinrtConnectivity::IConnectionProfile> AddWifiConnectionProfile(
+      WinrtConnectivity::NetworkConnectivityLevel connectivity);
+  ComPtr<WinrtConnectivity::IConnectionProfile> AddConnectionProfile(
+      const std::string& adapter_name,
+      const GUID& network_adapter_id,
+      const std::vector<uint8_t>& mac_address,
+      std::optional<std::string> ssid,
+      WinrtConnectivity::NetworkConnectivityLevel connectivity);
+
+  // Returns a random 6-byte MAC address.
+  std::vector<uint8_t> GenerateRandomMacAddress() const;
+
+  DiscoveryNetworkListWinTest(const DiscoveryNetworkListWinTest&) = delete;
+  DiscoveryNetworkListWinTest& operator=(const DiscoveryNetworkListWinTest&) =
+      delete;
+
+ protected:
+  const std::vector<uint8_t> kWiredMacAddressVector{
+      kWiredMacAddress, kWiredMacAddress + std::size(kWiredMacAddress)};
+
+  const std::vector<uint8_t> kWifiMacAddressVector{
+      kWifiMacAddress, kWifiMacAddress + std::size(kWifiMacAddress)};
+
+  // Contains the fake Windows OS API implementations along with the network
+  // adapters and connection profiles setup through Add*ConnectionProfile().
+  FakeIpHelper fake_ip_helper_;
+  FakeWinrtNetworkEnvironment fake_winrt_network_environment_;
+};
+
+void DiscoveryNetworkListWinTest::SetUp() {
+  // Use the fake Windows OS APIs to simulate different networking environments.
+  WindowsOsApi os_api_override;
+
+  // Override the WinRT networking connectivity APIs.
+  os_api_override.winrt_api.ro_get_activation_factory_callback =
+      base::BindRepeating(
+          &FakeWinrtNetworkEnvironment::FakeRoGetActivationFactory,
+          base::Unretained(&fake_winrt_network_environment_));
+
+  // Override the IP Helper Win32 APIs.
+  os_api_override.ip_helper_api.get_adapters_addresses_callback =
+      base::BindRepeating(&FakeIpHelper::GetAdaptersAddresses,
+                          base::Unretained(&fake_ip_helper_));
+  os_api_override.ip_helper_api.get_if_table2_callback = base::BindRepeating(
+      &FakeIpHelper::GetIfTable2, base::Unretained(&fake_ip_helper_));
+  os_api_override.ip_helper_api.free_mib_table_callback = base::BindRepeating(
+      &FakeIpHelper::FreeMibTable, base::Unretained(&fake_ip_helper_));
+
+  OverrideWindowsOsApiForTesting(os_api_override);
+
+  // Force GetDiscoveryNetworkInfoList() to use the WinRT code path that avoids
+  // prompting for network location permission on Windows 24H2.
+  OverrideRequiresNetworkLocationPermissionForTesting(true);
+}
+
+void DiscoveryNetworkListWinTest::TearDown() {
+  // Remove the overrides to start using the actual OS APIs again.
+  OverrideWindowsOsApiForTesting({});
+  OverrideRequiresNetworkLocationPermissionForTesting(false);
+}
+
+ComPtr<WinrtConnectivity::IConnectionProfile>
+DiscoveryNetworkListWinTest::AddWiredConnectionProfile(
+    WinrtConnectivity::NetworkConnectivityLevel connectivity) {
+  return AddConnectionProfile(kWiredAdapterName, kWiredNetworkAdapterId,
+                              kWiredMacAddressVector,
+                              /*ssid=*/std::nullopt, connectivity);
+}
+
+ComPtr<WinrtConnectivity::IConnectionProfile>
+DiscoveryNetworkListWinTest::AddWifiConnectionProfile(
+    WinrtConnectivity::NetworkConnectivityLevel connectivity) {
+  return AddConnectionProfile(kWifiAdapterName, kWifiNetworkAdapterId,
+                              kWifiMacAddressVector, kWifiSsid, connectivity);
+}
+
+ComPtr<WinrtConnectivity::IConnectionProfile>
+DiscoveryNetworkListWinTest::AddConnectionProfile(
+    const std::string& adapter_name,
+    const GUID& network_adapter_id,
+    const std::vector<uint8_t>& mac_address,
+    std::optional<std::string> ssid,
+    WinrtConnectivity::NetworkConnectivityLevel connectivity) {
+  IFTYPE adapter_type;
+  if (ssid) {
+    adapter_type = IF_TYPE_IEEE80211;
+  } else {
+    adapter_type = IF_TYPE_ETHERNET_CSMACD;
+  }
+  fake_ip_helper_.AddNetworkInterface(adapter_name, network_adapter_id,
+                                      mac_address, adapter_type,
+                                      IfOperStatusUp);
+
+  return fake_winrt_network_environment_.AddConnectionProfile(
+      network_adapter_id, connectivity, ssid);
+}
+
+std::vector<uint8_t> DiscoveryNetworkListWinTest::GenerateRandomMacAddress()
+    const {
+  std::vector<uint8_t> mac_address(6);
+  base::RandBytes(mac_address.data(), mac_address.size());
+  return mac_address;
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetInterfaceGuidMacMapEmpty) {
+  auto interface_guid_mac_map = GetInterfaceGuidMacMap();
+  EXPECT_EQ(interface_guid_mac_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetInterfaceGuidMacMap) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+
+  auto interface_guid_mac_map = GetInterfaceGuidMacMap();
+  ASSERT_EQ(interface_guid_mac_map.size(), 2u);
+
+  auto it = interface_guid_mac_map.find(kWiredNetworkAdapterId);
+  ASSERT_NE(it, interface_guid_mac_map.end());
+
+  const std::string kExpectedWiredMacAddress(kWiredMacAddressVector.begin(),
+                                             kWiredMacAddressVector.end());
+  EXPECT_EQ(it->second, kExpectedWiredMacAddress);
+
+  it = interface_guid_mac_map.find(kWifiNetworkAdapterId);
+  ASSERT_NE(it, interface_guid_mac_map.end());
+
+  const std::string kExpectedWifiMacAddress(kWifiMacAddressVector.begin(),
+                                            kWifiMacAddressVector.end());
+  EXPECT_EQ(it->second, kExpectedWifiMacAddress);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetInterfaceGuidMacMapError) {
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+
+  fake_ip_helper_.SimulateError(FakeIpHelperStatus::kErrorGetIfTable2Failed);
+
+  auto interface_guid_mac_map = GetInterfaceGuidMacMap();
+  EXPECT_EQ(interface_guid_mac_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, IsProfileConnectedToNetworkWhenOnline) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_InternetAccess);
+
+  EXPECT_TRUE(IsProfileConnectedToNetwork(connection_profile.Get()));
+}
+
+TEST_F(DiscoveryNetworkListWinTest, IsProfileConnectedToNetworkWhenOffline) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_None);
+
+  EXPECT_FALSE(IsProfileConnectedToNetwork(connection_profile.Get()));
+}
+
+TEST_F(DiscoveryNetworkListWinTest, IsProfileConnectedNetworkError) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_InternetAccess);
+
+  fake_winrt_network_environment_.SimulateError(
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetNetworkConnectivityLevelFailed);
+
+  // `IsProfileConnectedToNetwork()` must return false when an error occurs.
+  EXPECT_FALSE(IsProfileConnectedToNetwork(connection_profile.Get()));
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetProfileNetworkAdapterId) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_InternetAccess);
+
+  GUID network_adapter_id;
+  HRESULT hr =
+      GetProfileNetworkAdapterId(connection_profile.Get(), &network_adapter_id);
+
+  EXPECT_EQ(hr, S_OK);
+  EXPECT_EQ(network_adapter_id, kWiredNetworkAdapterId);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetProfileNetworkAdapterIdErrors) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_InternetAccess);
+
+  constexpr FakeWinrtNetworkStatus kErrorStatusList[] = {
+      FakeWinrtNetworkStatus::kErrorConnectionProfileGetNetworkAdapterFailed,
+      FakeWinrtNetworkStatus::kErrorGetNetworkAdapterIdFailed,
+  };
+  for (const FakeWinrtNetworkStatus error_status : kErrorStatusList) {
+    fake_winrt_network_environment_.SimulateError(error_status);
+
+    GUID network_adapter_id = {0};
+    HRESULT hr = GetProfileNetworkAdapterId(connection_profile.Get(),
+                                            &network_adapter_id);
+
+    EXPECT_EQ(hr, fake_winrt_network_environment_.MakeHresult(error_status))
+        << " for error_status: " << static_cast<int>(error_status);
+    EXPECT_EQ(network_adapter_id, GUID_NULL)
+        << " for error_status: " << static_cast<int>(error_status);
+  }
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetProfileWifiSSID) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                   NetworkConnectivityLevel_InternetAccess);
+
+  HSTRING ssid_hstring;
+  HRESULT hr = GetProfileWifiSSID(connection_profile.Get(), &ssid_hstring);
+  ASSERT_EQ(hr, S_OK);
+
+  base::win::ScopedHString ssid(ssid_hstring);
+  EXPECT_EQ(ssid.GetAsUTF8(), kWifiSsid);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetProfileWifiSSIDWhenNotWifi) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                    NetworkConnectivityLevel_InternetAccess);
+
+  HSTRING ssid_hstring = nullptr;
+  HRESULT hr = GetProfileWifiSSID(connection_profile.Get(), &ssid_hstring);
+  EXPECT_EQ(hr, kWifiNotSupported);
+  EXPECT_EQ(ssid_hstring, nullptr);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetProfileWifiSsidErrors) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> connection_profile =
+      AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                   NetworkConnectivityLevel_InternetAccess);
+
+  constexpr FakeWinrtNetworkStatus kErrorStatusList[] = {
+      FakeWinrtNetworkStatus::kErrorConnectionProfileQueryInterfaceFailed,
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetWlanConnectionProfileDetailsFailed,
+      FakeWinrtNetworkStatus::
+          kErrorWlanConnectionProfileDetailsGetConnectedSsidFailed,
+  };
+  for (const FakeWinrtNetworkStatus error_status : kErrorStatusList) {
+    fake_winrt_network_environment_.SimulateError(error_status);
+
+    HSTRING ssid_hstring = nullptr;
+    HRESULT hr = GetProfileWifiSSID(connection_profile.Get(), &ssid_hstring);
+
+    EXPECT_EQ(hr, fake_winrt_network_environment_.MakeHresult(error_status))
+        << " for error_status: " << static_cast<int>(error_status);
+    EXPECT_EQ(ssid_hstring, nullptr)
+        << " for error_status: " << static_cast<int>(error_status);
+  }
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetAllConnectionProfilesEmpty) {
+  ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+      connection_profiles;
+  uint32_t connection_profiles_size = 0u;
+
+  HRESULT hr =
+      GetAllConnectionProfiles(&connection_profiles, &connection_profiles_size);
+
+  EXPECT_EQ(hr, S_OK);
+  EXPECT_NE(connection_profiles, nullptr);
+  EXPECT_EQ(connection_profiles_size, 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetAllConnectionProfiles) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_LocalAccess);
+
+  ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+      connection_profiles;
+  uint32_t connection_profiles_size = 0u;
+
+  HRESULT hr =
+      GetAllConnectionProfiles(&connection_profiles, &connection_profiles_size);
+
+  EXPECT_EQ(hr, S_OK);
+  EXPECT_NE(connection_profiles, nullptr);
+  EXPECT_EQ(connection_profiles_size, 2u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetAllConnectionProfilesError) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  constexpr FakeWinrtNetworkStatus kErrorStatusList[] = {
+      FakeWinrtNetworkStatus::kErrorRoGetActivationFactoryFailed,
+      FakeWinrtNetworkStatus::
+          kErrorNetworkInformationStaticsGetConnectionProfilesFailed,
+      FakeWinrtNetworkStatus::kErrorVectorViewGetSizeFailed,
+  };
+
+  for (const FakeWinrtNetworkStatus error_status : kErrorStatusList) {
+    fake_winrt_network_environment_.SimulateError(error_status);
+
+    ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+        connection_profiles;
+    uint32_t connection_profiles_size = 0u;
+
+    HRESULT hr = GetAllConnectionProfiles(&connection_profiles,
+                                          &connection_profiles_size);
+
+    EXPECT_EQ(hr, fake_winrt_network_environment_.MakeHresult(error_status))
+        << " for error_status: " << static_cast<int>(error_status);
+    EXPECT_EQ(connection_profiles, nullptr)
+        << " for error_status: " << static_cast<int>(error_status);
+    EXPECT_EQ(connection_profiles_size, 0u)
+        << " for error_status: " << static_cast<int>(error_status);
+  }
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       DiscoveryNetworkListWinTest_GetMacSsidMapUsingWinrtWhenEmpty) {
+  auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+  EXPECT_EQ(mac_ssid_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetMacSsidMapUsingWinrt) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+
+  auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+  EXPECT_EQ(mac_ssid_map.size(), 1u);
+
+  const std::string kExpectedWifiMacAddress(kWifiMacAddressVector.begin(),
+                                            kWifiMacAddressVector.end());
+
+  auto it = mac_ssid_map.find(kExpectedWifiMacAddress);
+  ASSERT_NE(it, mac_ssid_map.end());
+  EXPECT_EQ(it->second, kWifiSsid);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetMacSsidMapUsingWinrtWhenDisconnected) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_None);
+
+  auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+  EXPECT_EQ(mac_ssid_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetMacSsidMapUsingWinrtErrors) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  constexpr FakeWinrtNetworkStatus kErrorStatusList[] = {
+      FakeWinrtNetworkStatus::
+          kErrorNetworkInformationStaticsGetConnectionProfilesFailed,
+      FakeWinrtNetworkStatus::kErrorVectorViewGetAtFailed,
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetNetworkConnectivityLevelFailed,
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetWlanConnectionProfileDetailsFailed,
+      FakeWinrtNetworkStatus::kErrorGetNetworkAdapterIdFailed,
+  };
+  for (const FakeWinrtNetworkStatus error_status : kErrorStatusList) {
+    fake_winrt_network_environment_.SimulateError(error_status);
+
+    auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+    EXPECT_EQ(mac_ssid_map.size(), 0u)
+        << " for error_status: " << static_cast<int>(error_status);
+  }
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetMacSsidMapUsingWinrtWithIpHelperErrors) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+  fake_ip_helper_.SimulateError(FakeIpHelperStatus::kErrorGetIfTable2Failed);
+
+  auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+  EXPECT_EQ(mac_ssid_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetMacSsidMapUsingWinrtWithMissingMacAddress) {
+  fake_winrt_network_environment_.AddConnectionProfile(
+      kWifiNetworkAdapterId,
+      WinrtConnectivity::NetworkConnectivityLevel::
+          NetworkConnectivityLevel_InternetAccess,
+      kWifiSsid);
+
+  auto mac_ssid_map = GetMacSsidMapUsingWinrt();
+  EXPECT_EQ(mac_ssid_map.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetDiscoveryNetworkInfoListEmpty) {
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  EXPECT_EQ(network_info_list.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest, GetDiscoveryNetworkInfoList) {
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  ASSERT_EQ(network_info_list.size(), 2u);
+
+  EXPECT_EQ(network_info_list[0].name, kWiredAdapterName);
+  EXPECT_EQ(network_info_list[0].network_id,
+            base::HexEncode(kWiredMacAddress, std::size(kWiredMacAddress)));
+
+  EXPECT_EQ(network_info_list[1].name, kWifiAdapterName);
+  EXPECT_EQ(network_info_list[1].network_id, kWifiSsid);
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetDiscoveryNetworkInfoListWithBufferOverflow) {
+  // Calculate the number of network adapters that we need to add to the fake
+  // environment to cause the first call to GetAdaptersAddresses() to fail with
+  // ERROR_BUFFER_OVERFLOW..
+  const size_t kMaxAdapterCount =
+      (kGetAdaptersAddressesInitialBufferSize / sizeof(IP_ADAPTER_ADDRESSES));
+
+  const size_t kOverflowAdapterCount = (kMaxAdapterCount + 1);
+  std::vector<DiscoveryNetworkInfo> expected_network_info_list;
+
+  // Setup the fake environment with the required number of network adapters.
+  for (size_t i = 0u; i < kOverflowAdapterCount; ++i) {
+    const std::string kAdapterName =
+        ("fake_adapter_name_" + base::NumberToString(i));
+
+    GUID network_adapter_id;
+    ASSERT_HRESULT_SUCCEEDED(CoCreateGuid(&network_adapter_id));
+
+    const std::vector<uint8_t> kMacAddress = GenerateRandomMacAddress();
+
+    const std::string kSsid = ("fake_ssid_" + base::NumberToString(i));
+
+    AddConnectionProfile(kAdapterName, network_adapter_id, kMacAddress, kSsid,
+                         WinrtConnectivity::NetworkConnectivityLevel::
+                             NetworkConnectivityLevel_InternetAccess);
+
+    expected_network_info_list.emplace_back(kAdapterName, kSsid);
+  }
+  StableSortDiscoveryNetworkInfo(expected_network_info_list.begin(),
+                                 expected_network_info_list.end());
+
+  std::vector<DiscoveryNetworkInfo> actual_network_info_list =
+      GetDiscoveryNetworkInfoList();
+  ASSERT_EQ(actual_network_info_list, expected_network_info_list);
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetDiscoveryNetworkInfoListWithBufferOverflowRepeated) {
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+
+  fake_ip_helper_.SimulateError(
+      FakeIpHelperStatus::kErrorGetAdaptersAddressesBufferOverflow);
+
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  EXPECT_EQ(network_info_list.size(), 0u);
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetDiscoveryNetworkInfoListWithMissingSsid) {
+  AddWiredConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                                NetworkConnectivityLevel_InternetAccess);
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  // Force GetAdaptersAddresses() to always fail with ERROR_BUFFER_OVERFLOW.
+  fake_winrt_network_environment_.SimulateError(
+      FakeWinrtNetworkStatus::
+          kErrorNetworkInformationStaticsGetConnectionProfilesFailed);
+
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  ASSERT_EQ(network_info_list.size(), 2u);
+
+  EXPECT_EQ(network_info_list[0].name, kWiredAdapterName);
+  EXPECT_EQ(network_info_list[0].network_id,
+            base::HexEncode(kWiredMacAddress, std::size(kWiredMacAddress)));
+
+  EXPECT_EQ(network_info_list[1].name, kWifiAdapterName);
+  EXPECT_EQ(network_info_list[1].network_id,
+            base::HexEncode(kWifiMacAddress, std::size(kWifiMacAddress)));
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetDiscoveryNetworkInfoListWithAdapterStatusDown) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  // Adapters with the IfOperStatusDown status must be ignored.
+  fake_ip_helper_.AddNetworkInterface(
+      kWiredAdapterName, kWiredNetworkAdapterId, kWiredMacAddressVector,
+      IF_TYPE_ETHERNET_CSMACD, IfOperStatusDown);
+
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  ASSERT_EQ(network_info_list.size(), 1u);
+
+  EXPECT_EQ(network_info_list[0].name, kWifiAdapterName);
+  EXPECT_EQ(network_info_list[0].network_id, kWifiSsid);
+}
+
+TEST_F(DiscoveryNetworkListWinTest,
+       GetDiscoveryNetworkInfoListWithUnwantedAdapterType) {
+  AddWifiConnectionProfile(WinrtConnectivity::NetworkConnectivityLevel::
+                               NetworkConnectivityLevel_InternetAccess);
+
+  // Adapters that are not ethernet or wifi must be ignored.
+  fake_ip_helper_.AddNetworkInterface(kWiredAdapterName, kWiredNetworkAdapterId,
+                                      kWiredMacAddressVector, IF_TYPE_TUNNEL,
+                                      IfOperStatusUp);
+
+  std::vector<DiscoveryNetworkInfo> network_info_list =
+      GetDiscoveryNetworkInfoList();
+  ASSERT_EQ(network_info_list.size(), 1u);
+
+  EXPECT_EQ(network_info_list[0].name, kWifiAdapterName);
+  EXPECT_EQ(network_info_list[0].network_id, kWifiSsid);
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.cc b/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.cc
new file mode 100644
index 0000000..ee64c6b
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.cc
@@ -0,0 +1,190 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_connection_profile.h"
+
+#include "base/notimplemented.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+namespace WinrtFoundation = ABI::Windows::Foundation;
+
+using Microsoft::WRL::Make;
+
+namespace media_router {
+
+FakeConnectionProfile::FakeConnectionProfile(
+    base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+    const GUID& network_adapter_id,
+    WinrtConnectivity::NetworkConnectivityLevel connectivity_level,
+    const std::optional<std::string>& ssid)
+    : fake_network_environment_(fake_network_environment),
+      network_adapter_(Make<FakeNetworkAdapter>(fake_network_environment,
+                                                network_adapter_id)),
+      connectivity_level_(connectivity_level) {
+  if (ssid) {
+    wlan_connection_profile_details_ =
+        Make<FakeWlanConnectionProfileDetails>(fake_network_environment, *ssid);
+  }
+}
+
+HRESULT FakeConnectionProfile::QueryInterface(const IID& interface_id,
+                                              void** result) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::kErrorConnectionProfileQueryInterfaceFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::kErrorConnectionProfileQueryInterfaceFailed);
+  }
+
+  // Call the base class implementation.
+  return Microsoft::WRL::RuntimeClass<
+      Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+      WinrtConnectivity::IConnectionProfile,
+      WinrtConnectivity::IConnectionProfile2>::QueryInterface(interface_id,
+                                                              result);
+}
+
+HRESULT FakeConnectionProfile::GetNetworkConnectivityLevel(
+    WinrtConnectivity::NetworkConnectivityLevel* value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetNetworkConnectivityLevelFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::
+            kErrorConnectionProfileGetNetworkConnectivityLevelFailed);
+  }
+
+  *value = connectivity_level_;
+  return S_OK;
+}
+
+HRESULT FakeConnectionProfile::get_ProfileName(HSTRING* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_NetworkAdapter(
+    WinrtConnectivity::INetworkAdapter** value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::kErrorConnectionProfileGetNetworkAdapterFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::kErrorConnectionProfileGetNetworkAdapterFailed);
+  }
+
+  return network_adapter_.CopyTo(value);
+}
+
+HRESULT FakeConnectionProfile::get_WlanConnectionProfileDetails(
+    WinrtConnectivity::IWlanConnectionProfileDetails** value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::
+          kErrorConnectionProfileGetWlanConnectionProfileDetailsFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::
+            kErrorConnectionProfileGetWlanConnectionProfileDetailsFailed);
+  }
+  return wlan_connection_profile_details_.CopyTo(value);
+}
+
+HRESULT FakeConnectionProfile::GetNetworkNames(
+    WinrtFoundation::Collections::IVectorView<HSTRING>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetConnectionCost(
+    WinrtConnectivity::IConnectionCost** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetDataPlanStatus(
+    WinrtConnectivity::IDataPlanStatus** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetLocalUsage(
+    WinrtFoundation::DateTime StartTime,
+    WinrtFoundation::DateTime EndTime,
+    WinrtConnectivity::IDataUsage** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetLocalUsagePerRoamingStates(
+    WinrtFoundation::DateTime StartTime,
+    WinrtFoundation::DateTime EndTime,
+    WinrtConnectivity::RoamingStates States,
+    WinrtConnectivity::IDataUsage** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_NetworkSecuritySettings(
+    WinrtConnectivity::INetworkSecuritySettings** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_IsWwanConnectionProfile(boolean* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_IsWlanConnectionProfile(boolean* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_WwanConnectionProfileDetails(
+    WinrtConnectivity::IWwanConnectionProfileDetails** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::get_ServiceProviderGuid(
+    WinrtFoundation::IReference<GUID>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetSignalBars(
+    WinrtFoundation::IReference<byte>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetDomainConnectivityLevel(
+    WinrtConnectivity::DomainConnectivityLevel* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetNetworkUsageAsync(
+    WinrtFoundation::DateTime startTime,
+    WinrtFoundation::DateTime endTime,
+    WinrtConnectivity::DataUsageGranularity granularity,
+    WinrtConnectivity::NetworkUsageStates states,
+    WinrtFoundation::IAsyncOperation<WinrtFoundation::Collections::IVectorView<
+        WinrtConnectivity::NetworkUsage*>*>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeConnectionProfile::GetConnectivityIntervalsAsync(
+    WinrtFoundation::DateTime startTime,
+    WinrtFoundation::DateTime endTime,
+    WinrtConnectivity::NetworkUsageStates states,
+    WinrtFoundation::IAsyncOperation<WinrtFoundation::Collections::IVectorView<
+        WinrtConnectivity::ConnectivityInterval*>*>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+FakeConnectionProfile::~FakeConnectionProfile() = default;
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.h b/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.h
new file mode 100644
index 0000000..bd4a9b709
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.h
@@ -0,0 +1,118 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_CONNECTION_PROFILE_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_CONNECTION_PROFILE_H_
+
+#include <windows.networking.connectivity.h>
+#include <wrl.h>
+
+#include <optional>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+
+namespace media_router {
+class FakeWinrtNetworkEnvironment;
+
+class FakeConnectionProfile final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+          ABI::Windows::Networking::Connectivity::IConnectionProfile,
+          ABI::Windows::Networking::Connectivity::IConnectionProfile2> {
+ public:
+  FakeConnectionProfile(
+      base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+      const GUID& network_adapter_id,
+      ABI::Windows::Networking::Connectivity::NetworkConnectivityLevel
+          connectivity_level,
+      const std::optional<std::string>& ssid);
+
+  // Override the IUnknown interface to simulate failures.
+  HRESULT __stdcall QueryInterface(const IID& interface_id,
+                                   void** result) final;
+
+  // Implement the IConnectionProfile & IConnectionProfile2 interfaces.
+  HRESULT __stdcall GetNetworkConnectivityLevel(
+      ABI::Windows::Networking::Connectivity::NetworkConnectivityLevel* value)
+      final;
+  HRESULT __stdcall get_ProfileName(HSTRING* value) final;
+  HRESULT __stdcall get_NetworkAdapter(
+      ABI::Windows::Networking::Connectivity::INetworkAdapter** value) final;
+  HRESULT __stdcall get_IsWlanConnectionProfile(boolean* value) final;
+  // The following are not implemented because they are not used:
+  HRESULT __stdcall GetNetworkNames(
+      ABI::Windows::Foundation::Collections::IVectorView<HSTRING>** value)
+      final;
+  HRESULT __stdcall GetConnectionCost(
+      ABI::Windows::Networking::Connectivity::IConnectionCost** value) final;
+  HRESULT __stdcall GetDataPlanStatus(
+      ABI::Windows::Networking::Connectivity::IDataPlanStatus** value) final;
+  HRESULT __stdcall GetLocalUsage(
+      ABI::Windows::Foundation::DateTime StartTime,
+      ABI::Windows::Foundation::DateTime EndTime,
+      ABI::Windows::Networking::Connectivity::IDataUsage** value) final;
+  HRESULT __stdcall GetLocalUsagePerRoamingStates(
+      ABI::Windows::Foundation::DateTime StartTime,
+      ABI::Windows::Foundation::DateTime EndTime,
+      ABI::Windows::Networking::Connectivity::RoamingStates States,
+      ABI::Windows::Networking::Connectivity::IDataUsage** value) final;
+  HRESULT __stdcall get_NetworkSecuritySettings(
+      ABI::Windows::Networking::Connectivity::INetworkSecuritySettings** value)
+      final;
+  HRESULT __stdcall get_IsWwanConnectionProfile(boolean* value) final;
+  HRESULT __stdcall get_WwanConnectionProfileDetails(
+      ABI::Windows::Networking::Connectivity::IWwanConnectionProfileDetails**
+          value) final;
+  HRESULT __stdcall get_WlanConnectionProfileDetails(
+      ABI::Windows::Networking::Connectivity::IWlanConnectionProfileDetails**
+          value) final;
+  HRESULT __stdcall get_ServiceProviderGuid(
+      ABI::Windows::Foundation::IReference<GUID>** value) final;
+  HRESULT __stdcall GetSignalBars(
+      ABI::Windows::Foundation::IReference<byte>** value) final;
+  HRESULT __stdcall GetDomainConnectivityLevel(
+      ABI::Windows::Networking::Connectivity::DomainConnectivityLevel* value)
+      final;
+  HRESULT __stdcall GetNetworkUsageAsync(
+      ABI::Windows::Foundation::DateTime startTime,
+      ABI::Windows::Foundation::DateTime endTime,
+      ABI::Windows::Networking::Connectivity::DataUsageGranularity granularity,
+      ABI::Windows::Networking::Connectivity::NetworkUsageStates states,
+      ABI::Windows::Foundation::IAsyncOperation<
+          ABI::Windows::Foundation::Collections::IVectorView<
+              ABI::Windows::Networking::Connectivity::NetworkUsage*>*>** value)
+      final;
+  HRESULT __stdcall GetConnectivityIntervalsAsync(
+      ABI::Windows::Foundation::DateTime startTime,
+      ABI::Windows::Foundation::DateTime endTime,
+      ABI::Windows::Networking::Connectivity::NetworkUsageStates states,
+      ABI::Windows::Foundation::IAsyncOperation<
+          ABI::Windows::Foundation::Collections::IVectorView<
+              ABI::Windows::Networking::Connectivity::ConnectivityInterval*>*>**
+          value) final;
+
+  FakeConnectionProfile(const FakeConnectionProfile&) = delete;
+  FakeConnectionProfile& operator=(const FakeConnectionProfile&) = delete;
+
+ private:
+  ~FakeConnectionProfile() final;
+
+  base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment_;
+
+  Microsoft::WRL::ComPtr<
+      ABI::Windows::Networking::Connectivity::INetworkAdapter>
+      network_adapter_;
+
+  ABI::Windows::Networking::Connectivity::NetworkConnectivityLevel
+      connectivity_level_;
+
+  Microsoft::WRL::ComPtr<
+      ABI::Windows::Networking::Connectivity::IWlanConnectionProfileDetails>
+      wlan_connection_profile_details_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_CONNECTION_PROFILE_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.cc b/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.cc
new file mode 100644
index 0000000..4dbb2263
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.cc
@@ -0,0 +1,45 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_ip_adapter_addresses.h"
+
+#include "base/check_op.h"
+
+namespace media_router {
+
+FakeIpAdapterAddresses::FakeIpAdapterAddresses(
+    const std::string& adapter_name,
+    const std::vector<uint8_t>& physical_address,
+    IFTYPE adapter_type,
+    IF_OPER_STATUS adapter_status)
+    : adapter_name_(adapter_name) {
+  // Only populate struct members that are used by Chromium.
+  value_ = {};
+  value_.Length = sizeof(IP_ADAPTER_ADDRESSES);
+  value_.IfType = adapter_type;
+  value_.OperStatus = adapter_status;
+  value_.AdapterName = const_cast<char*>(adapter_name_.c_str());
+
+  CHECK_LE(physical_address.size(),
+           static_cast<size_t>(MAX_ADAPTER_ADDRESS_LENGTH));
+
+  value_.PhysicalAddressLength = physical_address.size();
+  memcpy(value_.PhysicalAddress, physical_address.data(),
+         physical_address.size());
+}
+
+FakeIpAdapterAddresses::FakeIpAdapterAddresses(
+    const FakeIpAdapterAddresses& source) {
+  adapter_name_ = source.adapter_name_;
+  value_ = source.value_;
+  value_.AdapterName = const_cast<char*>(adapter_name_.c_str());
+}
+
+FakeIpAdapterAddresses::~FakeIpAdapterAddresses() = default;
+
+IP_ADAPTER_ADDRESSES* FakeIpAdapterAddresses::Get() {
+  return &value_;
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.h b/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.h
new file mode 100644
index 0000000..96524e8
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.h
@@ -0,0 +1,41 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_ADAPTER_ADDRESSES_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_ADAPTER_ADDRESSES_H_
+
+#include <ws2tcpip.h>
+
+// iphlpapi.h must be included after ws2tcpip.h to use MIB_IF_TABLE2.
+#include <iphlpapi.h>  // NOLINT
+
+#include <string>
+#include <vector>
+
+namespace media_router {
+
+// Implements the IP_ADAPTER_ADDRESSES structure returned by the fake
+// implementation of GetAdaptersAddresses in FakeIpHelper.
+class FakeIpAdapterAddresses final {
+ public:
+  FakeIpAdapterAddresses(const std::string& adapter_name,
+                         const std::vector<uint8_t>& physical_address,
+                         IFTYPE adapter_type,
+                         IF_OPER_STATUS adapter_status);
+  FakeIpAdapterAddresses(const FakeIpAdapterAddresses& source);
+  ~FakeIpAdapterAddresses();
+
+  IP_ADAPTER_ADDRESSES* Get();
+
+ private:
+  // In `value_`, the struct member, `IP_ADAPTER_ADDRESSES::AdapterName`, points
+  // to this string.
+  std::string adapter_name_;
+
+  IP_ADAPTER_ADDRESSES value_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_ADAPTER_ADDRESSES_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.cc b/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.cc
new file mode 100644
index 0000000..e55a70a
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.cc
@@ -0,0 +1,110 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_ip_helper.h"
+
+#include "base/check_op.h"
+#include "base/notreached.h"
+
+namespace media_router {
+
+FakeIpHelper::FakeIpHelper() = default;
+
+FakeIpHelper::~FakeIpHelper() {
+  CHECK_EQ(mib_tables_.size(), 0u);
+}
+
+void FakeIpHelper::SimulateError(FakeIpHelperStatus error_status) {
+  error_status_ = error_status;
+}
+
+void FakeIpHelper::AddNetworkInterface(
+    const std::string& adapter_name,
+    const GUID& network_interface_guid,
+    const std::vector<uint8_t>& physical_address,
+    IFTYPE adapter_type,
+    IF_OPER_STATUS adapter_status) {
+  AddMibTableRow(network_interface_guid, physical_address);
+  AddIpAdapterAddresses(adapter_name, physical_address, adapter_type,
+                        adapter_status);
+}
+
+ULONG FakeIpHelper::GetAdaptersAddresses(
+    ULONG family,
+    ULONG flags,
+    void* reserved,
+    IP_ADAPTER_ADDRESSES* adapter_addresses,
+    ULONG* size_pointer) {
+  if (error_status_ ==
+      FakeIpHelperStatus::kErrorGetAdaptersAddressesBufferOverflow) {
+    return ERROR_BUFFER_OVERFLOW;
+  }
+
+  if (ip_adapter_addresses_.empty()) {
+    return ERROR_NO_DATA;
+  }
+
+  const ULONG adapter_addresses_byte_count =
+      (ip_adapter_addresses_.size() * sizeof(IP_ADAPTER_ADDRESSES));
+
+  if (*size_pointer < adapter_addresses_byte_count) {
+    *size_pointer = adapter_addresses_byte_count;
+    return ERROR_BUFFER_OVERFLOW;
+  }
+
+  for (size_t i = 0; i < ip_adapter_addresses_.size(); ++i) {
+    adapter_addresses[i] = *ip_adapter_addresses_[i].Get();
+    if (i > 0) {
+      adapter_addresses[i - 1].Next = &adapter_addresses[i];
+    }
+  }
+  return ERROR_SUCCESS;
+}
+
+DWORD FakeIpHelper::GetIfTable2(MIB_IF_TABLE2** out_table) {
+  if (error_status_ == FakeIpHelperStatus::kErrorGetIfTable2Failed) {
+    return ERROR_NOT_FOUND;
+  }
+
+  mib_tables_.emplace_back(mib_table_rows_);
+  *out_table = mib_tables_.back().Get();
+  return ERROR_SUCCESS;
+}
+
+void FakeIpHelper::FreeMibTable(void* table) {
+  for (auto it = mib_tables_.begin(); it != mib_tables_.end(); ++it) {
+    if (it->Get() == table) {
+      mib_tables_.erase(it);
+      return;
+    }
+  }
+  NOTREACHED();
+}
+
+void FakeIpHelper::AddIpAdapterAddresses(
+    const std::string& adapter_name,
+    const std::vector<uint8_t>& physical_address,
+    IFTYPE adapter_type,
+    IF_OPER_STATUS adapter_status) {
+  ip_adapter_addresses_.emplace_back(adapter_name, physical_address,
+                                     adapter_type, adapter_status);
+}
+
+void FakeIpHelper::AddMibTableRow(
+    const GUID& network_interface_guid,
+    const std::vector<uint8_t>& physical_address) {
+  MIB_IF_ROW2 network_interface = {};
+  network_interface.InterfaceGuid = network_interface_guid;
+
+  CHECK_LE(physical_address.size(),
+           static_cast<size_t>(IF_MAX_PHYS_ADDRESS_LENGTH));
+
+  network_interface.PhysicalAddressLength = physical_address.size();
+  memcpy(network_interface.PhysicalAddress, &physical_address[0],
+         physical_address.size());
+
+  mib_table_rows_.push_back(network_interface);
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.h b/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.h
new file mode 100644
index 0000000..7bfba009
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_ip_helper.h
@@ -0,0 +1,85 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_HELPER_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_HELPER_H_
+
+#include <ws2tcpip.h>
+
+// iphlpapi.h must be included after ws2tcpip.h to use MIB_IF_TABLE2.
+#include <iphlpapi.h>  // NOLINT
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/media/router/discovery/test_support/win/fake_ip_adapter_addresses.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_mib_table.h"
+
+namespace media_router {
+
+// Each value represents a different IP Helper Win32 API that can fail during
+// GetDiscoveryNetworkInfoList().  Use with
+// `FakeIpHelper::SimulateError` to simulate Win32 API failures
+// that return Windows system error codes.
+enum class FakeIpHelperStatus {
+  kOk = 0,
+  kErrorGetIfTable2Failed = 1,
+  kErrorGetAdaptersAddressesBufferOverflow = 2,
+};
+
+// Provides a fake implementation of the Win32 APIs used to enumerate network
+// adapters.  Tests should use this class to simulate different
+// network environments with different types of adapters and different statuses.
+class FakeIpHelper final {
+ public:
+  FakeIpHelper();
+  ~FakeIpHelper();
+
+  void SimulateError(FakeIpHelperStatus error_status);
+
+  // Sets up the fake network environment by creating fake network adapters.
+  // Stores fake network adapters in the `mib_table_rows_` and
+  // `ip_adapter_addresses_` members.
+  void AddNetworkInterface(const std::string& adapter_name,
+                           const GUID& network_interface_guid,
+                           const std::vector<uint8_t>& physical_address,
+                           IFTYPE adapter_type,
+                           IF_OPER_STATUS adapter_status);
+
+  // Implements the `GetAdaptersAddresses()` Win32 API.  Copies
+  // `ip_adapter_addresses_` to `adapter_addresses`.
+  ULONG GetAdaptersAddresses(ULONG family,
+                             ULONG flags,
+                             void* reserved,
+                             IP_ADAPTER_ADDRESSES* adapter_addresses,
+                             ULONG* size_pointer);
+
+  // Implements the `GetIfTable2()` Win32 API. Creates a new `FakeMibTable`
+  // stored in `mib_tables_` that is returned through `out_table`.
+  // `FreeMibTable()` removes the `FakeMibTable` from `mib_tables_`.
+  DWORD GetIfTable2(MIB_IF_TABLE2** out_table);
+  void FreeMibTable(void* table);
+
+ private:
+  // Adds a fake network adapter to `ip_adapter_addresses_`.
+  void AddIpAdapterAddresses(const std::string& adapter_name,
+                             const std::vector<uint8_t>& physical_address,
+                             IFTYPE adapter_type,
+                             IF_OPER_STATUS adapter_status);
+
+  // Adds a fake network adapter to `mib_table_rows_`.
+  void AddMibTableRow(const GUID& network_interface_guid,
+                      const std::vector<uint8_t>& physical_address);
+
+  FakeIpHelperStatus error_status_ = FakeIpHelperStatus::kOk;
+
+  std::vector<FakeIpAdapterAddresses> ip_adapter_addresses_;
+
+  std::vector<MIB_IF_ROW2> mib_table_rows_;
+  std::vector<FakeMibTable> mib_tables_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_IP_HELPER_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.cc b/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.cc
new file mode 100644
index 0000000..4ce08481
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.cc
@@ -0,0 +1,35 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_mib_table.h"
+
+namespace media_router {
+
+FakeMibTable::FakeMibTable(
+    const std::vector<MIB_IF_ROW2>& source_network_interfaces) {
+  size_t mib_table_byte_count = sizeof(MIB_IF_TABLE2);
+  if (source_network_interfaces.size() > 1) {
+    // MIB_IF_TABLE2 contains one MIB_IF_ROW2, so we need to add space for the
+    // rest of the array.
+    mib_table_byte_count +=
+        (source_network_interfaces.size() - 1) * sizeof(MIB_IF_ROW2);
+  }
+  mib_table_bytes_.resize(mib_table_byte_count);
+
+  MIB_IF_TABLE2* mib_table = Get();
+  mib_table->NumEntries = source_network_interfaces.size();
+  for (size_t i = 0; i < source_network_interfaces.size(); ++i) {
+    mib_table->Table[i] = source_network_interfaces[i];
+  }
+}
+
+FakeMibTable::FakeMibTable(const FakeMibTable& source) = default;
+
+FakeMibTable::~FakeMibTable() = default;
+
+MIB_IF_TABLE2* FakeMibTable::Get() {
+  return reinterpret_cast<MIB_IF_TABLE2*>(&mib_table_bytes_[0]);
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.h b/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.h
new file mode 100644
index 0000000..ef95352d
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_mib_table.h
@@ -0,0 +1,40 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_MIB_TABLE_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_MIB_TABLE_H_
+
+#include <ws2tcpip.h>
+
+// iphlpapi.h must be included after ws2tcpip.h to use MIB_IF_TABLE2.
+#include <iphlpapi.h>  // NOLINT
+
+#include <vector>
+
+namespace media_router {
+
+// Contains the `MIB_IF_TABLE2` structure returned by the fake implementation of
+// `GetIfTable2()` in `FakeIpHelper`.
+class FakeMibTable final {
+ public:
+  explicit FakeMibTable(
+      const std::vector<MIB_IF_ROW2>& source_network_interfaces);
+  FakeMibTable(const FakeMibTable& source);
+  ~FakeMibTable();
+
+  MIB_IF_TABLE2* Get();
+
+ private:
+  // A blob which makes up the `MIB_IF_TABLE2` structure. The `MIB_IF_TABLE2` is
+  // a length prefixed array of `MIB_IF_ROW2` structures with some additional
+  // padding in between the length and the array.  Here's the format:
+  //   <ULONG> NumEntries
+  //   <Padding>
+  //   <MIB_IF_ROW2>  Array[NumEntries]
+  std::vector<uint8_t> mib_table_bytes_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_MIB_TABLE_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.cc b/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.cc
new file mode 100644
index 0000000..6f603cc
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.cc
@@ -0,0 +1,61 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_network_adapter.h"
+
+#include "base/notreached.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+namespace WinrtFoundation = ABI::Windows::Foundation;
+
+namespace media_router {
+
+FakeNetworkAdapter::FakeNetworkAdapter(
+    base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+    const GUID& network_adapter_id)
+    : fake_network_environment_(fake_network_environment),
+      network_adapter_id_(network_adapter_id) {}
+
+HRESULT FakeNetworkAdapter::get_NetworkAdapterId(GUID* value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::kErrorGetNetworkAdapterIdFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::kErrorGetNetworkAdapterIdFailed);
+  }
+  *value = network_adapter_id_;
+  return S_OK;
+}
+
+HRESULT FakeNetworkAdapter::get_OutboundMaxBitsPerSecond(UINT64* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkAdapter::get_InboundMaxBitsPerSecond(UINT64* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkAdapter::get_IanaInterfaceType(UINT32* value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkAdapter::get_NetworkItem(
+    WinrtConnectivity::INetworkItem** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkAdapter::GetConnectedProfileAsync(
+    WinrtFoundation::IAsyncOperation<WinrtConnectivity::ConnectionProfile*>**
+        value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+FakeNetworkAdapter::~FakeNetworkAdapter() = default;
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.h b/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.h
new file mode 100644
index 0000000..674e475
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_network_adapter.h
@@ -0,0 +1,50 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_ADAPTER_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_ADAPTER_H_
+
+#include <windows.networking.connectivity.h>
+#include <wrl.h>
+
+#include "base/memory/weak_ptr.h"
+
+namespace media_router {
+class FakeWinrtNetworkEnvironment;
+
+class FakeNetworkAdapter final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+          ABI::Windows::Networking::Connectivity::INetworkAdapter> {
+ public:
+  FakeNetworkAdapter(
+      base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+      const GUID& network_adapter_id);
+
+  // Implement the INetworkAdapter interface.
+  HRESULT __stdcall get_NetworkAdapterId(GUID* value) final;
+  // The following are not implemented because they are not used:
+  HRESULT __stdcall get_OutboundMaxBitsPerSecond(UINT64* value) final;
+  HRESULT __stdcall get_InboundMaxBitsPerSecond(UINT64* value) final;
+  HRESULT __stdcall get_IanaInterfaceType(UINT32* value) final;
+  HRESULT __stdcall get_NetworkItem(
+      ABI::Windows::Networking::Connectivity::INetworkItem** value) final;
+  HRESULT __stdcall GetConnectedProfileAsync(
+      ABI::Windows::Foundation::IAsyncOperation<
+          ABI::Windows::Networking::Connectivity::ConnectionProfile*>** value)
+      final;
+
+  FakeNetworkAdapter(const FakeNetworkAdapter&) = delete;
+  FakeNetworkAdapter& operator=(const FakeNetworkAdapter&) = delete;
+
+ private:
+  ~FakeNetworkAdapter() final;
+
+  base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment_;
+  GUID network_adapter_id_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_ADAPTER_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.cc b/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.cc
new file mode 100644
index 0000000..b1073b0
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.cc
@@ -0,0 +1,97 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_network_information_statics.h"
+
+#include "base/notreached.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_vector_view.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+namespace WinrtCollections = ABI::Windows::Foundation::Collections;
+namespace WinrtNetworking = ABI::Windows::Networking;
+namespace WinrtFoundation = ABI::Windows::Foundation;
+
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::Make;
+
+namespace media_router {
+
+FakeNetworkInformationStatics::FakeNetworkInformationStatics(
+    base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment)
+    : fake_network_environment_(fake_network_environment) {}
+
+HRESULT FakeNetworkInformationStatics::GetConnectionProfiles(
+    WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>**
+        value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::
+          kErrorNetworkInformationStaticsGetConnectionProfilesFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::
+            kErrorNetworkInformationStaticsGetConnectionProfilesFailed);
+  }
+
+  ComPtr<WinrtCollections::IVectorView<WinrtConnectivity::ConnectionProfile*>>
+      new_vector_view =
+          Make<FakeVectorView<WinrtConnectivity::IConnectionProfile,
+                              WinrtConnectivity::ConnectionProfile>>(
+              fake_network_environment_);
+
+  *value = new_vector_view.Detach();
+  return S_OK;
+}
+
+HRESULT FakeNetworkInformationStatics::GetInternetConnectionProfile(
+    WinrtConnectivity::IConnectionProfile** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::GetLanIdentifiers(
+    WinrtCollections::IVectorView<WinrtConnectivity::LanIdentifier*>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::GetHostNames(
+    WinrtCollections::IVectorView<WinrtNetworking::HostName*>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::GetProxyConfigurationAsync(
+    WinrtFoundation::IUriRuntimeClass* uri,
+    WinrtFoundation::IAsyncOperation<WinrtConnectivity::ProxyConfiguration*>**
+        value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::GetSortedEndpointPairs(
+    WinrtCollections::IIterable<WinrtNetworking::EndpointPair*>*
+        destination_list,
+    WinrtNetworking::HostNameSortOptions sort_options,
+    WinrtCollections::IVectorView<WinrtNetworking::EndpointPair*>** value) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::add_NetworkStatusChanged(
+    WinrtConnectivity::INetworkStatusChangedEventHandler*
+        network_status_handler,
+    EventRegistrationToken* event_cookie) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+HRESULT FakeNetworkInformationStatics::remove_NetworkStatusChanged(
+    EventRegistrationToken event_cookie) {
+  NOTIMPLEMENTED();
+  return E_NOTIMPL;
+}
+
+FakeNetworkInformationStatics::~FakeNetworkInformationStatics() = default;
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.h b/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.h
new file mode 100644
index 0000000..a3f25938
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.h
@@ -0,0 +1,69 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_INFORMATION_STATICS_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_INFORMATION_STATICS_H_
+
+#include <windows.networking.connectivity.h>
+#include <wrl.h>
+
+#include "base/memory/weak_ptr.h"
+
+namespace media_router {
+class FakeWinrtNetworkEnvironment;
+
+class FakeNetworkInformationStatics final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+          ABI::Windows::Networking::Connectivity::INetworkInformationStatics> {
+ public:
+  FakeNetworkInformationStatics(
+      base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment);
+
+  // Implement the INetworkInformationStatics interface.
+  HRESULT __stdcall GetConnectionProfiles(
+      ABI::Windows::Foundation::Collections::IVectorView<
+          ABI::Windows::Networking::Connectivity::ConnectionProfile*>** value)
+      final;
+  // The following are not implemented because they are not used:
+  HRESULT __stdcall GetInternetConnectionProfile(
+      ABI::Windows::Networking::Connectivity::IConnectionProfile** value) final;
+  HRESULT __stdcall GetLanIdentifiers(
+      ABI::Windows::Foundation::Collections::IVectorView<
+          ABI::Windows::Networking::Connectivity::LanIdentifier*>** value)
+      final;
+  HRESULT __stdcall GetHostNames(
+      ABI::Windows::Foundation::Collections::IVectorView<
+          ABI::Windows::Networking::HostName*>** value) final;
+  HRESULT __stdcall GetProxyConfigurationAsync(
+      ABI::Windows::Foundation::IUriRuntimeClass* uri,
+      ABI::Windows::Foundation::IAsyncOperation<
+          ABI::Windows::Networking::Connectivity::ProxyConfiguration*>** value)
+      final;
+  HRESULT __stdcall GetSortedEndpointPairs(
+      ABI::Windows::Foundation::Collections::IIterable<
+          ABI::Windows::Networking::EndpointPair*>* destination_list,
+      ABI::Windows::Networking::HostNameSortOptions sort_options,
+      ABI::Windows::Foundation::Collections::IVectorView<
+          ABI::Windows::Networking::EndpointPair*>** value) final;
+  HRESULT __stdcall add_NetworkStatusChanged(
+      ABI::Windows::Networking::Connectivity::INetworkStatusChangedEventHandler*
+          network_status_handler,
+      EventRegistrationToken* event_cookie) final;
+  HRESULT __stdcall remove_NetworkStatusChanged(
+      EventRegistrationToken event_cookie) final;
+
+  FakeNetworkInformationStatics(const FakeNetworkInformationStatics&) = delete;
+  FakeNetworkInformationStatics& operator=(
+      const FakeNetworkInformationStatics&) = delete;
+
+ private:
+  ~FakeNetworkInformationStatics() final;
+
+  base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_NETWORK_INFORMATION_STATICS_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_vector_view.h b/chrome/browser/media/router/discovery/test_support/win/fake_vector_view.h
new file mode 100644
index 0000000..62d6f0d
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_vector_view.h
@@ -0,0 +1,86 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_VECTOR_VIEW_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_VECTOR_VIEW_H_
+
+#include <windows.foundation.h>
+#include <wrl.h>
+
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/notreached.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+
+namespace media_router {
+
+template <typename TInterface, typename TClass>
+class FakeVectorView final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+          ABI::Windows::Foundation::Collections::IVectorView<TClass*>> {
+ public:
+  FakeVectorView(
+      base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment)
+      : fake_network_environment_(fake_network_environment),
+        view_(fake_network_environment->GetConnectionProfiles()) {}
+
+  // Implement the IVectorView interface.
+  HRESULT __stdcall GetAt(unsigned index, TInterface** item) final {
+    if (fake_network_environment_->GetErrorStatus() ==
+        FakeWinrtNetworkStatus::kErrorVectorViewGetAtFailed) {
+      return fake_network_environment_->MakeHresult(
+          FakeWinrtNetworkStatus::kErrorVectorViewGetAtFailed);
+    }
+
+    if (index >= view_.size()) {
+      return E_BOUNDS;
+    }
+
+    view_[index].CopyTo(item);
+    return S_OK;
+  }
+
+  HRESULT __stdcall get_Size(unsigned* size) final {
+    if (fake_network_environment_->GetErrorStatus() ==
+        FakeWinrtNetworkStatus::kErrorVectorViewGetSizeFailed) {
+      return fake_network_environment_->MakeHresult(
+          FakeWinrtNetworkStatus::kErrorVectorViewGetSizeFailed);
+    }
+
+    *size = view_.size();
+    return S_OK;
+  }
+
+  // The following are not implemented because they are not used.
+  HRESULT __stdcall IndexOf(TInterface* value,
+                            unsigned* index,
+                            boolean* found) final {
+    NOTIMPLEMENTED();
+    return E_NOTIMPL;
+  }
+
+  HRESULT __stdcall GetMany(unsigned start_index,
+                            unsigned capacity,
+                            TInterface** value,
+                            unsigned* actual) final {
+    NOTIMPLEMENTED();
+    return E_NOTIMPL;
+  }
+
+  FakeVectorView(const FakeVectorView&) = delete;
+  FakeVectorView& operator=(const FakeVectorView&) = delete;
+
+ private:
+  ~FakeVectorView() final = default;
+
+  base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment_;
+
+  std::vector<Microsoft::WRL::ComPtr<TInterface>> view_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_VECTOR_VIEW_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.cc b/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.cc
new file mode 100644
index 0000000..76c88f4
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.cc
@@ -0,0 +1,85 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+
+#include <tuple>
+
+#include "base/notreached.h"
+#include "base/win/scoped_hstring.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_connection_profile.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_network_information_statics.h"
+
+namespace WinrtConnectivity = ABI::Windows::Networking::Connectivity;
+
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::Make;
+
+namespace media_router {
+
+FakeWinrtNetworkEnvironment::FakeWinrtNetworkEnvironment() = default;
+
+FakeWinrtNetworkEnvironment::~FakeWinrtNetworkEnvironment() = default;
+
+FakeWinrtNetworkStatus FakeWinrtNetworkEnvironment::GetErrorStatus() const {
+  return error_status_;
+}
+
+HRESULT FakeWinrtNetworkEnvironment::MakeHresult(
+    FakeWinrtNetworkStatus error_status) const {
+  return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF,
+                      /*base=*/0x800 + static_cast<int>(error_status));
+}
+
+void FakeWinrtNetworkEnvironment::SimulateError(
+    FakeWinrtNetworkStatus error_status) {
+  error_status_ = error_status;
+}
+
+const std::vector<ComPtr<WinrtConnectivity::IConnectionProfile>>&
+FakeWinrtNetworkEnvironment::GetConnectionProfiles() const {
+  return connection_profiles_;
+}
+
+ComPtr<WinrtConnectivity::IConnectionProfile>
+FakeWinrtNetworkEnvironment::AddConnectionProfile(
+    const GUID& network_adapter_id,
+    WinrtConnectivity::NetworkConnectivityLevel connectivity_level,
+    const std::optional<std::string>& ssid) {
+  ComPtr<WinrtConnectivity::IConnectionProfile> new_connection_profile =
+      Make<FakeConnectionProfile>(weak_ptr_factory_.GetWeakPtr(),
+                                  network_adapter_id, connectivity_level, ssid);
+
+  connection_profiles_.push_back(new_connection_profile);
+  return new_connection_profile;
+}
+
+HRESULT FakeWinrtNetworkEnvironment::FakeRoGetActivationFactory(
+    HSTRING class_id,
+    const IID& iid,
+    void** factory) {
+  if (error_status_ ==
+      FakeWinrtNetworkStatus::kErrorRoGetActivationFactoryFailed) {
+    return MakeHresult(
+        FakeWinrtNetworkStatus::kErrorRoGetActivationFactoryFailed);
+  }
+
+  base::win::ScopedHString class_id_hstring(class_id);
+
+  if (class_id_hstring.Get() ==
+      RuntimeClass_Windows_Networking_Connectivity_NetworkInformation) {
+    ComPtr<FakeNetworkInformationStatics> network_info_statics =
+        Make<FakeNetworkInformationStatics>(weak_ptr_factory_.GetWeakPtr());
+    *factory = network_info_statics.Detach();
+  }
+  std::ignore = class_id_hstring.release();
+
+  if (*factory == nullptr) {
+    NOTIMPLEMENTED();
+    return E_NOTIMPL;
+  }
+  return S_OK;
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h b/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h
new file mode 100644
index 0000000..504f346b5
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h
@@ -0,0 +1,91 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WINRT_NETWORK_ENVIRONMENT_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WINRT_NETWORK_ENVIRONMENT_H_
+
+#include <roapi.h>
+#include <windows.networking.connectivity.h>
+#include <wrl/client.h>
+
+#include <optional>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+
+namespace media_router {
+
+// Each value represents a different WinRT API that can fail during
+// GetDiscoveryNetworkInfoList().  Use with
+// `FakeWinrtNetworkEnvironment::SimulateError` to simulate WinRT API failures
+// that return error HRESULTS.
+enum class FakeWinrtNetworkStatus {
+  kOk = 0,
+  kErrorRoGetActivationFactoryFailed = 1,
+  kErrorNetworkInformationStaticsGetConnectionProfilesFailed = 2,
+  kErrorVectorViewGetAtFailed = 3,
+  kErrorVectorViewGetSizeFailed = 4,
+  kErrorConnectionProfileQueryInterfaceFailed = 5,
+  kErrorConnectionProfileGetNetworkConnectivityLevelFailed = 6,
+  kErrorConnectionProfileGetNetworkAdapterFailed = 7,
+  kErrorConnectionProfileGetWlanConnectionProfileDetailsFailed = 8,
+  kErrorWlanConnectionProfileDetailsGetConnectedSsidFailed = 9,
+  kErrorGetNetworkAdapterIdFailed = 10,
+};
+
+// Provides a fake implementation of RoGetActivationFactory() to creates a fake
+// implementation of INetworkInformationStatics, which is used to enumerate fake
+// connection profiles.  Tests should use this class to simulate different
+// network environments with different types of adapters and different levels
+// of connectivity.
+class FakeWinrtNetworkEnvironment final {
+ public:
+  FakeWinrtNetworkEnvironment();
+  ~FakeWinrtNetworkEnvironment();
+
+  FakeWinrtNetworkStatus GetErrorStatus() const;
+  void SimulateError(FakeWinrtNetworkStatus error_status);
+
+  // Gets the fake HRESULT that the fake OS API implementation will return for
+  // `error_status`.
+  HRESULT MakeHresult(FakeWinrtNetworkStatus error_status) const;
+
+  // Sets up the fake network environment by creating fake network adapters. Use
+  // `std::nullopt` for `ssid` to create a wired network adapter.  Stores
+  // fake network adapters in the `connection_profiles_` member.
+  Microsoft::WRL::ComPtr<
+      ABI::Windows::Networking::Connectivity::IConnectionProfile>
+  AddConnectionProfile(
+      const GUID& network_adapter_id,
+      ABI::Windows::Networking::Connectivity::NetworkConnectivityLevel
+          connectivity_level,
+      const std::optional<std::string>& ssid);
+
+  const std::vector<Microsoft::WRL::ComPtr<
+      ABI::Windows::Networking::Connectivity::IConnectionProfile>>&
+  GetConnectionProfiles() const;
+
+  HRESULT FakeRoGetActivationFactory(HSTRING class_id,
+                                     const IID& iid,
+                                     void** factory);
+
+  FakeWinrtNetworkEnvironment(const FakeWinrtNetworkEnvironment&) = delete;
+  FakeWinrtNetworkEnvironment& operator=(const FakeWinrtNetworkEnvironment&) =
+      delete;
+
+ private:
+  // Specifies which error the fake WinRT network environment should simulate.
+  FakeWinrtNetworkStatus error_status_ = FakeWinrtNetworkStatus::kOk;
+
+  // Contains the fake network adapters enumerated by the WinRT API.
+  std::vector<Microsoft::WRL::ComPtr<
+      ABI::Windows::Networking::Connectivity::IConnectionProfile>>
+      connection_profiles_;
+
+  base::WeakPtrFactory<FakeWinrtNetworkEnvironment> weak_ptr_factory_{this};
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WINRT_NETWORK_ENVIRONMENT_H_
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.cc b/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.cc
new file mode 100644
index 0000000..3e4a481
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.cc
@@ -0,0 +1,33 @@
+// Copyright 2024 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/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.h"
+
+#include "base/win/scoped_hstring.h"
+#include "chrome/browser/media/router/discovery/test_support/win/fake_winrt_network_environment.h"
+
+namespace media_router {
+
+FakeWlanConnectionProfileDetails::FakeWlanConnectionProfileDetails(
+    base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+    const std::string& ssid)
+    : fake_network_environment_(fake_network_environment), ssid_(ssid) {}
+
+HRESULT FakeWlanConnectionProfileDetails::GetConnectedSsid(HSTRING* value) {
+  if (fake_network_environment_->GetErrorStatus() ==
+      FakeWinrtNetworkStatus::
+          kErrorWlanConnectionProfileDetailsGetConnectedSsidFailed) {
+    return fake_network_environment_->MakeHresult(
+        FakeWinrtNetworkStatus::
+            kErrorWlanConnectionProfileDetailsGetConnectedSsidFailed);
+  }
+
+  base::win::ScopedHString ssid_copy(base::win::ScopedHString::Create(ssid_));
+  *value = ssid_copy.release();
+  return S_OK;
+}
+
+FakeWlanConnectionProfileDetails::~FakeWlanConnectionProfileDetails() = default;
+
+}  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.h b/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.h
new file mode 100644
index 0000000..d4d53540
--- /dev/null
+++ b/chrome/browser/media/router/discovery/test_support/win/fake_wlan_connection_profile_details.h
@@ -0,0 +1,45 @@
+// Copyright 2024 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_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WLAN_CONNECTION_PROFILE_DETAILS_H_
+#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WLAN_CONNECTION_PROFILE_DETAILS_H_
+
+#include <windows.networking.connectivity.h>
+#include <wrl.h>
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+
+namespace media_router {
+class FakeWinrtNetworkEnvironment;
+
+class FakeWlanConnectionProfileDetails final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
+          ABI::Windows::Networking::Connectivity::
+              IWlanConnectionProfileDetails> {
+ public:
+  FakeWlanConnectionProfileDetails(
+      base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment,
+      const std::string& ssid);
+
+  // Implement the IWlanConnectionProfileDetails interface.
+  HRESULT __stdcall GetConnectedSsid(HSTRING* value) final;
+
+  FakeWlanConnectionProfileDetails(const FakeWlanConnectionProfileDetails&) =
+      delete;
+  FakeWlanConnectionProfileDetails& operator=(
+      const FakeWlanConnectionProfileDetails&) = delete;
+
+ private:
+  ~FakeWlanConnectionProfileDetails() final;
+
+  base::WeakPtr<FakeWinrtNetworkEnvironment> fake_network_environment_;
+  std::string ssid_;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_TEST_SUPPORT_WIN_FAKE_WLAN_CONNECTION_PROFILE_DETAILS_H_
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
index 139d318..9eb3d3f 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
@@ -33,6 +33,7 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_service.h"
+#include "components/variations/service/limited_entropy_synthetic_trial.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/variations_associated_data.h"
 #include "components/version_info/version_info.h"
@@ -303,6 +304,12 @@
     auto* init_params = chromeos::BrowserParamsProxy::Get();
     if (init_params->MetricsServiceClientId().has_value())
       client_id = init_params->MetricsServiceClientId().value();
+
+    // Sync the randomization seed from Ash Chrome so that the group assignment
+    // is the same on Lacros.
+    variations::LimitedEntropySyntheticTrial::SetSeedFromAsh(
+        local_state_, init_params->LimitedEntropySyntheticTrialSeed());
+
     // Beginning M120 this should always be there. Note:
     // The LES numbers are kept stable over the lifetime of the session.
     // They get read when the system is statrting up in Ash. So they do not
diff --git a/chrome/browser/metrics/structured/ash_event_storage.cc b/chrome/browser/metrics/structured/ash_event_storage.cc
index 2835f7c..2a2fe81 100644
--- a/chrome/browser/metrics/structured/ash_event_storage.cc
+++ b/chrome/browser/metrics/structured/ash_event_storage.cc
@@ -208,8 +208,11 @@
     }
   }
 
-  (*pre_user_events_)->Clear();
-  pre_user_events_->QueueWrite();
+  // Regardless of if there are any events cleanup the storage.
+  if (pre_user_events()) {
+    (*pre_user_events_)->Clear();
+    pre_user_events_->QueueWrite();
+  }
 
   // The write is fine because it will add to a task that is not tied to the
   // lifetime of |pre_user_events_|.
diff --git a/chrome/browser/metrics/structured/ash_event_storage_unittest.cc b/chrome/browser/metrics/structured/ash_event_storage_unittest.cc
index c456263..945682f2 100644
--- a/chrome/browser/metrics/structured/ash_event_storage_unittest.cc
+++ b/chrome/browser/metrics/structured/ash_event_storage_unittest.cc
@@ -46,11 +46,14 @@
   }
 
   std::unique_ptr<AshEventStorage> BuildTestStorage() {
-    return std::make_unique<AshEventStorage>(
+    auto storage = std::make_unique<AshEventStorage>(
         /*write_delay=*/base::Seconds(0),
         GetTestDirectory()
             .Append(FILE_PATH_LITERAL("structured_metrics"))
             .Append(FILE_PATH_LITERAL("events")));
+    // Wait for the device events to be loaded.
+    Wait();
+    return storage;
   }
 
   StructuredDataProto GetReport(AshEventStorage* storage) {
diff --git a/chrome/browser/metrics/variations/variations_service_browsertest.cc b/chrome/browser/metrics/variations/variations_service_browsertest.cc
new file mode 100644
index 0000000..5a091df6
--- /dev/null
+++ b/chrome/browser/metrics/variations/variations_service_browsertest.cc
@@ -0,0 +1,46 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_service.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_test.h"
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_params_proxy.h"
+#endif
+
+namespace variations {
+
+class VariationsServiceBrowserTest : public InProcessBrowserTest {
+ public:
+  VariationsServiceBrowserTest() = default;
+
+  VariationsServiceBrowserTest(const VariationsServiceBrowserTest&) = delete;
+  VariationsServiceBrowserTest& operator=(const VariationsServiceBrowserTest&) =
+      delete;
+
+  PrefService* local_state() { return g_browser_process->local_state(); }
+};
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+IN_PROC_BROWSER_TEST_F(VariationsServiceBrowserTest,
+                       LimitedEntropySyntheticTrialSeedTransfer) {
+  auto* ash_params = chromeos::BrowserParamsProxy::Get();
+  variations::VariationsService* variations_service =
+      g_browser_process->GetMetricsServicesManager()->GetVariationsService();
+  // Due to version skew, the Ash chrome version used in test might not have
+  // this field yet.
+  if (ash_params->LimitedEntropySyntheticTrialSeed() != 0u) {
+    EXPECT_EQ(variations_service->limited_entropy_synthetic_trial_
+                  .GetRandomizationSeed(local_state()),
+              ash_params->LimitedEntropySyntheticTrialSeed());
+  }
+}
+#endif
+
+}  // namespace variations
diff --git a/chrome/browser/pdf/pdf_extension_printing_test.cc b/chrome/browser/pdf/pdf_extension_printing_test.cc
index 62332ce..a1a8800 100644
--- a/chrome/browser/pdf/pdf_extension_printing_test.cc
+++ b/chrome/browser/pdf/pdf_extension_printing_test.cc
@@ -233,19 +233,6 @@
         std::make_unique<printing::PrinterBasicInfo>(printer_info));
   }
 
-  content::WebContents* GetEmbedderWebContents() {
-    content::WebContents* contents = GetActiveWebContents();
-
-    // OOPIF PDF viewer only has a single `WebContents`.
-    if (UseOopif()) {
-      return contents;
-    }
-
-    MimeHandlerViewGuest* guest =
-        pdf_extension_test_util::GetOnlyMimeHandlerView(contents);
-    return guest ? guest->embedder_web_contents() : nullptr;
-  }
-
   void SetupPrintViewManagerForJobMonitoring(content::RenderFrameHost* frame) {
     auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
     auto manager = std::make_unique<printing::TestPrintViewManager>(
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 401464be..502a5b9 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -1110,13 +1110,14 @@
     GTEST_SKIP();
   }
 
-  MimeHandlerViewGuest* guest_view = LoadPdfGetMimeHandlerView(
-      embedded_test_server()->GetURL("/pdf/test.pdf"));
-  ASSERT_TRUE(guest_view);
-  EXPECT_EQ(u"test.pdf", guest_view->GetController()
-                             .GetLastCommittedEntry()
-                             ->GetTitleForDisplay());
-  EXPECT_EQ(u"test.pdf", GetActiveWebContents()->GetTitle());
+  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test.pdf")));
+
+  const std::u16string kExpectedTitle = u"test.pdf";
+  EXPECT_EQ(kExpectedTitle, GetEmbedderWebContents()
+                                ->GetController()
+                                .GetLastCommittedEntry()
+                                ->GetTitleForDisplay());
+  EXPECT_EQ(kExpectedTitle, GetActiveWebContents()->GetTitle());
 }
 
 // This test ensures that titles are set properly for PDFs with /Title.
@@ -1126,17 +1127,19 @@
     GTEST_SKIP();
   }
 
-  MimeHandlerViewGuest* guest_view = LoadPdfGetMimeHandlerView(
-      embedded_test_server()->GetURL("/pdf/test-title.pdf"));
-  ASSERT_TRUE(guest_view);
-  EXPECT_EQ(u"PDF title test", guest_view->GetController()
-                                   .GetLastCommittedEntry()
-                                   ->GetTitleForDisplay());
-  EXPECT_EQ(u"PDF title test", GetActiveWebContents()->GetTitle());
+  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test-title.pdf")));
+
+  const std::u16string kExpectedTitle = u"PDF title test";
+  EXPECT_EQ(kExpectedTitle, GetEmbedderWebContents()
+                                ->GetController()
+                                .GetLastCommittedEntry()
+                                ->GetTitleForDisplay());
+  EXPECT_EQ(kExpectedTitle, GetActiveWebContents()->GetTitle());
 }
 
-// This test ensures that titles are set properly for embedded PDFs with /Title.
-IN_PROC_BROWSER_TEST_P(PDFExtensionTest, TabTitleWithEmbeddedPdf) {
+// This test ensures that titles are set properly for embedded PDFs (using data
+// URL). PDF /Title should be ignored for embedded PDFs.
+IN_PROC_BROWSER_TEST_P(PDFExtensionTest, TabTitleWithEmbeddedPdfDataUrl) {
   // TODO(crbug.com/1445746): Remove this once the test passes for OOPIF PDF.
   if (UseOopif()) {
     GTEST_SKIP();
@@ -1154,6 +1157,19 @@
   EXPECT_EQ(u"TabTitleWithEmbeddedPdf", GetActiveWebContents()->GetTitle());
 }
 
+// This test ensures that tab titles are set properly for embedded PDFs.
+// PDF /Title should be ignored for embedded PDFs.
+IN_PROC_BROWSER_TEST_P(PDFExtensionTest, TabTitleWithEmbeddedPdf) {
+  // TODO(crbug.com/1445746): Remove this once the test passes for OOPIF PDF.
+  if (UseOopif()) {
+    GTEST_SKIP();
+  }
+
+  ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/pdf_embed.html")));
+
+  EXPECT_EQ(u"TabWithEmbeddedPdf", GetActiveWebContents()->GetTitle());
+}
+
 // Flaky, http://crbug.com/767427
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
 #define MAYBE_PdfZoomWithoutBubble DISABLED_PdfZoomWithoutBubble
@@ -3666,8 +3682,8 @@
   if (UseOopif()) {
     // Verify the pdf has loaded. The test will timeout if the PDF fails to
     // load.
-    GetTestPdfViewerStreamManager(incognito_contents)
-        ->WaitUntilPdfLoaded(incognito_primary_main_frame);
+    ASSERT_TRUE(GetTestPdfViewerStreamManager(incognito_contents)
+                    ->WaitUntilPdfLoaded(incognito_primary_main_frame));
   } else {
     ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(
         incognito_primary_main_frame));
@@ -3685,8 +3701,8 @@
   if (UseOopif()) {
     // Verify the pdf has loaded. The test will timeout if the PDF fails to
     // load.
-    GetTestPdfViewerStreamManager(incognito_contents)
-        ->WaitUntilPdfLoadedInFirstChild();
+    ASSERT_TRUE(GetTestPdfViewerStreamManager(incognito_contents)
+                    ->WaitUntilPdfLoadedInFirstChild());
   } else {
     ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(
         incognito_contents->GetPrimaryMainFrame()));
@@ -3707,8 +3723,8 @@
   // Verify the pdf has loaded. The test will timeout if the PDF fails to
   // load.
   auto* incognito_contents = GetIncognitoActiveWebContents();
-  GetTestPdfViewerStreamManager(incognito_contents)
-      ->WaitUntilPdfLoadedInFirstChild();
+  ASSERT_TRUE(GetTestPdfViewerStreamManager(incognito_contents)
+                  ->WaitUntilPdfLoadedInFirstChild());
 }
 
 // PDF extension tests for the OOPIF PDF viewer.
@@ -3745,49 +3761,50 @@
   // `EnsurePDFHasLoaded()` uses postMessage() to check that the PDF has loaded,
   // so calling it is sufficient to check that a postMessage() connection has
   // been established.
-  ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(
-      GetActiveWebContents()->GetPrimaryMainFrame()));
+  content::RenderFrameHost* embedder_host =
+      ChildFrameAt(GetActiveWebContents()->GetPrimaryMainFrame(), 0);
+  ASSERT_TRUE(embedder_host);
+  ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(embedder_host));
 }
 
 // This test verifies the correctness of util `FindFullPagePdfExtensionHost`.
 IN_PROC_BROWSER_TEST_F(PDFExtensionOopifTest,
                        OopifPdfFindFullPagePdfExtensionHost) {
+  auto* web_contents = GetActiveWebContents();
   {
     // Navigate to a non-PDF page.
     ASSERT_TRUE(ui_test_utils::NavigateToURL(
         browser(), embedded_test_server()->GetURL("/title1.html")));
 
     // Verify that there is no full-page pdf extension host on non-PDF page.
-    EXPECT_FALSE(
-        pdf_frame_util::FindFullPagePdfExtensionHost(GetActiveWebContents()));
+    EXPECT_FALSE(pdf_frame_util::FindFullPagePdfExtensionHost(web_contents));
   }
 
   {
     // Load page with embedded PDF and make sure it succeeds.
     ASSERT_TRUE(ui_test_utils::NavigateToURL(
         browser(), embedded_test_server()->GetURL("/pdf/pdf_embed.html")));
-    GetTestPdfViewerStreamManager(GetActiveWebContents())
-        ->WaitUntilPdfLoadedInFirstChild();
+    ASSERT_TRUE(GetTestPdfViewerStreamManager(web_contents)
+                    ->WaitUntilPdfLoadedInFirstChild());
 
     // Verify that there is no full-page pdf extension host on embedded PDF.
-    EXPECT_FALSE(
-        pdf_frame_util::FindFullPagePdfExtensionHost(GetActiveWebContents()));
+    EXPECT_FALSE(pdf_frame_util::FindFullPagePdfExtensionHost(web_contents));
   }
 
   {
     // Load a full-page PDF and make sure it succeeds.
     ASSERT_TRUE(ui_test_utils::NavigateToURL(
         browser(), embedded_test_server()->GetURL("/pdf/test.pdf")));
-    GetTestPdfViewerStreamManager(GetActiveWebContents())
-        ->WaitUntilPdfLoaded(GetActiveWebContents()->GetPrimaryMainFrame());
+    ASSERT_TRUE(GetTestPdfViewerStreamManager(web_contents)
+                    ->WaitUntilPdfLoaded(web_contents->GetPrimaryMainFrame()));
 
     content::RenderFrameHost* child_frame =
-        content::ChildFrameAt(GetActiveWebContents()->GetPrimaryMainFrame(), 0);
+        content::ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
     ASSERT_TRUE(child_frame);
 
     // Verify that `FindFullPagePdfExtensionHost` returns the correct frame.
-    EXPECT_EQ(child_frame, pdf_frame_util::FindFullPagePdfExtensionHost(
-                               GetActiveWebContents()));
+    EXPECT_EQ(child_frame,
+              pdf_frame_util::FindFullPagePdfExtensionHost(web_contents));
   }
 }
 
diff --git a/chrome/browser/pdf/pdf_extension_test_base.cc b/chrome/browser/pdf/pdf_extension_test_base.cc
index 481a1e7..d8bd2d5 100644
--- a/chrome/browser/pdf/pdf_extension_test_base.cc
+++ b/chrome/browser/pdf/pdf_extension_test_base.cc
@@ -168,6 +168,19 @@
   return browser()->tab_strip_model()->GetActiveWebContents();
 }
 
+content::WebContents* PDFExtensionTestBase::GetEmbedderWebContents() {
+  content::WebContents* contents = GetActiveWebContents();
+
+  // OOPIF PDF viewer only has a single `WebContents`.
+  if (UseOopif()) {
+    return contents;
+  }
+
+  MimeHandlerViewGuest* guest =
+      pdf_extension_test_util::GetOnlyMimeHandlerView(contents);
+  return guest ? guest->embedder_web_contents() : nullptr;
+}
+
 TestGuestViewManager* PDFExtensionTestBase::GetGuestViewManager(
     content::BrowserContext* profile) {
   if (!profile) {
@@ -254,7 +267,9 @@
 PDFExtensionTestBase::EnsurePDFHasLoadedWithValidFrameTree() {
   content::WebContents* contents = GetActiveWebContents();
   testing::AssertionResult result =
-      pdf_extension_test_util::EnsurePDFHasLoaded(contents);
+      UseOopif() ? GetTestPdfViewerStreamManager(contents)->WaitUntilPdfLoaded(
+                       contents->GetPrimaryMainFrame())
+                 : pdf_extension_test_util::EnsurePDFHasLoaded(contents);
 
   // Ensure the frame tree contains a PDF extension host and a PDF plugin frame.
   EXPECT_TRUE(pdf_extension_test_util::GetOnlyPdfExtensionHost(contents));
diff --git a/chrome/browser/pdf/pdf_extension_test_base.h b/chrome/browser/pdf/pdf_extension_test_base.h
index acbbd8511..4312e91 100644
--- a/chrome/browser/pdf/pdf_extension_test_base.h
+++ b/chrome/browser/pdf/pdf_extension_test_base.h
@@ -70,6 +70,11 @@
 
   content::WebContents* GetActiveWebContents();
 
+  // For OOPIF PDF viewer, returns the active `WebContents`, as there is only a
+  // single `WebContents`. For GuestView PDF viewer, returns the embedder
+  // `WebContents`.
+  content::WebContents* GetEmbedderWebContents();
+
  protected:
   guest_view::TestGuestViewManager* GetGuestViewManager(
       content::BrowserContext* profile = nullptr);
diff --git a/chrome/browser/pdf/pdf_extension_test_util.cc b/chrome/browser/pdf/pdf_extension_test_util.cc
index f95b9257..ea28443c 100644
--- a/chrome/browser/pdf/pdf_extension_test_util.cc
+++ b/chrome/browser/pdf/pdf_extension_test_util.cc
@@ -9,7 +9,9 @@
 #include <vector>
 
 #include "base/containers/flat_set.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -40,6 +42,21 @@
 
 }  // namespace
 
+content::RenderFrameHost* GetPdfExtensionHostFromEmbedder(
+    content::RenderFrameHost* embedder_host) {
+  // PDF embedder hosts should have one child, which is the extension host.
+  if (content::ChildFrameAt(embedder_host, 1)) {
+    return nullptr;
+  }
+
+  content::RenderFrameHost* child_host =
+      content::ChildFrameAt(embedder_host, 0);
+  return child_host &&
+                 IsPdfExtensionOrigin(child_host->GetLastCommittedOrigin())
+             ? child_host
+             : nullptr;
+}
+
 content::RenderFrameHost* GetOnlyPdfExtensionHost(
     content::WebContents* contents) {
   std::vector<content::RenderFrameHost*> extension_hosts =
@@ -95,26 +112,52 @@
     const content::ToRenderFrameHost& frame,
     bool wait_for_hit_test_data,
     const std::string& pdf_element) {
-  bool load_success = content::EvalJs(frame, content::JsReplace(R"(
-            new Promise(resolve => {
-              window.addEventListener('message', event => {
-                if (event.origin !==
-                        'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai') {
-                  return;
-                }
-                if (event.data.type === 'documentLoaded') {
-                  resolve(
-                      event.data.load_state === 'success');
-                } else if (event.data.type === 'passwordPrompted') {
-                  resolve(true);
-                }
-              });
-              document.getElementsByTagName($1)[0].postMessage(
-                {type: 'initialize'});
-            });
-            )",
-                                                                pdf_element))
-                          .ExtractBool();
+  // OOPIF PDF intentionally doesn't support postMessage() API for embedders.
+  // postMessage() can still be used if the script is injected into the
+  // extension frame.
+  static constexpr char kEnsurePdfHasLoadedScript[] = R"(
+    new Promise(resolve => {
+      window.addEventListener('message', event => {
+        if (event.origin !==
+                'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai') {
+          return;
+        }
+        if (event.data.type === 'documentLoaded') {
+          resolve(event.data.load_state === 'success');
+        } else if (event.data.type === 'passwordPrompted') {
+          resolve(true);
+        }
+      });
+      %s.postMessage(
+        {type: 'initialize'});
+    });
+  )";
+
+  static constexpr char kOopifPostMessageTarget[] = "window";
+  static constexpr char kGuestViewPostMessageTarget[] =
+      "document.getElementsByTagName($1)[0]";
+
+  bool use_oopif =
+      base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif);
+
+  // For OOPIF PDF viewer, the target frame should be the PDF extension frame.
+  // Otherwise, it should be whatever frame was given.
+  content::RenderFrameHost* frame_rfh = frame.render_frame_host();
+  content::RenderFrameHost* target_frame =
+      use_oopif ? GetPdfExtensionHostFromEmbedder(frame_rfh) : frame_rfh;
+
+  if (use_oopif && !target_frame) {
+    return testing::AssertionFailure() << "Failed to get PDF extension frame.";
+  }
+
+  const std::string post_message_target =
+      use_oopif ? kOopifPostMessageTarget
+                : content::JsReplace(kGuestViewPostMessageTarget, pdf_element);
+  bool load_success =
+      content::EvalJs(target_frame,
+                      base::StringPrintf(kEnsurePdfHasLoadedScript,
+                                         post_message_target.c_str()))
+          .ExtractBool();
 
   if (wait_for_hit_test_data) {
     frame.render_frame_host()->ForEachRenderFrameHost(
diff --git a/chrome/browser/pdf/pdf_extension_test_util.h b/chrome/browser/pdf/pdf_extension_test_util.h
index 00eeedc0..e834727 100644
--- a/chrome/browser/pdf/pdf_extension_test_util.h
+++ b/chrome/browser/pdf/pdf_extension_test_util.h
@@ -29,6 +29,11 @@
 
 namespace pdf_extension_test_util {
 
+// Gets the PDF extension host that is the child of `embedder_host`. The
+// embedder host should only have one child, otherwise returns nullptr.
+content::RenderFrameHost* GetPdfExtensionHostFromEmbedder(
+    content::RenderFrameHost* embedder_host);
+
 // Gets the PDF extension host for a given `WebContents`. There should only be
 // one extension host in `contents`, otherwise returns nullptr.
 content::RenderFrameHost* GetOnlyPdfExtensionHost(
@@ -54,6 +59,9 @@
 // successfully, otherwise it indicates failure. If it doesn't finish loading,
 // the test will hang.
 //
+// In order to ensure an OOPIF PDF has loaded, `frame` must be an embedder host,
+// and the extension host must have already been created.
+//
 // Tests that attempt to send mouse/pointer events should pass `true` for
 // `wait_for_hit_test_data`, otherwise the necessary hit test data may not be
 // available by the time this function returns. (This behavior is the default,
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager.cc b/chrome/browser/pdf/pdf_viewer_stream_manager.cc
index 5e07484..0649c46a 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager.cc
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager.cc
@@ -196,22 +196,30 @@
 
 void PdfViewerStreamManager::RenderFrameDeleted(
     content::RenderFrameHost* render_frame_host) {
-  // If this is an unrelated host, ignore.
-  StreamInfo* claimed_stream_info = GetClaimedStreamInfo(render_frame_host);
-  if (!claimed_stream_info &&
-      !ContainsUnclaimedStreamInfo(render_frame_host->GetFrameTreeNodeId())) {
+  // When the PDF embeder frame is deleted, delete its stream.
+  if (GetClaimedStreamInfo(render_frame_host)) {
+    DeleteClaimedStreamInfo(render_frame_host);
+    // DO NOT add code past this point. `this` may have been deleted.
     return;
   }
 
-  // An unclaimed `StreamInfo`'s FrameTreeNode may delete a speculative
-  // `content::RenderFrameHost` before the embedder `content::RenderFrameHost`
-  // commits and claims the stream. The speculative `content::RenderFrameHost`
-  // won't be considered active, and shouldn't cause the stream to be deleted.
-  if (!claimed_stream_info && !render_frame_host->IsActive()) {
+  // If `render_frame_host` isn't active, ignore. An unclaimed `StreamInfo`'s
+  // FrameTreeNode may delete a speculative `content::RenderFrameHost` before
+  // the embedder `content::RenderFrameHost` commits and claims the stream. The
+  // speculative `content::RenderFrameHost` won't be considered active, and
+  // shouldn't cause the stream to be deleted.
+  if (!render_frame_host->IsActive()) {
     return;
   }
 
-  DeleteStreamInfo(render_frame_host);
+  // If `render_frame_host` is an unrelated host (there isn't an unclaimed
+  // stream), ignore.
+  int frame_tree_node_id = render_frame_host->GetFrameTreeNodeId();
+  if (!ContainsUnclaimedStreamInfo(frame_tree_node_id)) {
+    return;
+  }
+
+  DeleteUnclaimedStreamInfo(frame_tree_node_id);
   // DO NOT add code past this point. `this` may have been deleted.
 }
 
@@ -234,7 +242,8 @@
   // embedder host is navigating to another PDF URL, then a new `StreamInfo`
   // should have already been created and claimed by `new_host`, so it's still
   // safe to delete `old_host`'s `StreamInfo`.
-  DeleteStreamInfo(old_host);
+  DeleteClaimedStreamInfo(old_host);
+  // DO NOT add code past this point. `this` may have been deleted.
 }
 
 void PdfViewerStreamManager::FrameDeleted(int frame_tree_node_id) {
@@ -388,7 +397,7 @@
   return stream_info;
 }
 
-void PdfViewerStreamManager::DeleteStreamInfo(
+void PdfViewerStreamManager::DeleteClaimedStreamInfo(
     content::RenderFrameHost* embedder_host) {
   auto iter = stream_infos_.find(GetEmbedderHostInfo(embedder_host));
   CHECK(iter != stream_infos_.end());
@@ -407,6 +416,15 @@
   }
 }
 
+void PdfViewerStreamManager::DeleteUnclaimedStreamInfo(int frame_tree_node_id) {
+  CHECK(stream_infos_.erase(GetUnclaimedEmbedderHostInfo(frame_tree_node_id)));
+
+  if (stream_infos_.empty()) {
+    web_contents()->RemoveUserData(UserDataKey());
+    // DO NOT add code past this point. RemoveUserData() deleted `this`.
+  }
+}
+
 bool PdfViewerStreamManager::MaybeRegisterPdfSubresourceOverride(
     content::NavigationHandle* navigation_handle) {
   // Only register the subresource override if `navigation_handle` is for the
@@ -418,8 +436,6 @@
     return false;
   }
 
-  // The stream container is no longer needed after registering the subresource
-  // override.
   navigation_handle->RegisterSubresourceOverride(
       claimed_stream_info->stream()->TakeTransferrableURLLoader());
 
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager.h b/chrome/browser/pdf/pdf_viewer_stream_manager.h
index 9be440b..c3e0edc 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager.h
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager.h
@@ -244,9 +244,13 @@
   // `ContainsUnclaimedStreamInfo()` before calling this.
   StreamInfo* ClaimStreamInfo(content::RenderFrameHost* embedder_host);
 
-  // Deletes the stream info associated with `embedder_host`, and deletes
-  // `this` if there are no remaining stream infos.
-  void DeleteStreamInfo(content::RenderFrameHost* embedder_host);
+  // Deletes the claimed stream info associated with `embedder_host`, and
+  // deletes `this` if there are no remaining stream infos.
+  void DeleteClaimedStreamInfo(content::RenderFrameHost* embedder_host);
+
+  // Deletes the unclaimed stream info associated with `frame_tree_node_id`, and
+  // deletes `this` if there are no remaining stream infos.
+  void DeleteUnclaimedStreamInfo(int frame_tree_node_id);
 
   // Intended to be called during the PDF content frame's
   // `ReadyToCommitNavigation()` event. Registers navigations occurring in a PDF
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc b/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
index b5f084c..447b51f 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
@@ -218,7 +218,7 @@
 }
 
 // If the embedder render frame is deleted, the stream should be deleted.
-TEST_F(PdfViewerStreamManagerTest, RenderFrameDeleted) {
+TEST_F(PdfViewerStreamManagerTest, RenderFrameDeletedWithClaimedStream) {
   auto* actual_host = CreateChildRenderFrameHost(main_rfh(), "actual host");
   actual_host = NavigateAndCommit(actual_host, GURL(kOriginalUrl1));
 
@@ -232,9 +232,36 @@
   manager->RenderFrameDeleted(main_rfh());
   ASSERT_EQ(manager, pdf_viewer_stream_manager());
 
+  // `PdfViewerStreamManager::RenderFrameDeleted()` should cause the stream
+  // associated with `actual_host` to be deleted.
+  manager->RenderFrameDeleted(actual_host);
+
   // There are no remaining streams, so `PdfViewerStreamManager` should delete
   // itself.
+  EXPECT_FALSE(pdf_viewer_stream_manager());
+}
+
+TEST_F(PdfViewerStreamManagerTest, RenderFrameDeletedWithUnclaimedStream) {
+  auto* actual_host = CreateChildRenderFrameHost(main_rfh(), "actual host");
+  actual_host = NavigateAndCommit(actual_host, GURL(kOriginalUrl1));
+
+  PdfViewerStreamManager* manager = pdf_viewer_stream_manager();
+  manager->AddStreamContainer(actual_host->GetFrameTreeNodeId(), "internal_id",
+                              pdf_test_util::GenerateSampleStreamContainer(1));
+
+  // The stream hasn't been claimed, so the stream container can't be retrieved.
+  ASSERT_FALSE(manager->GetStreamContainer(actual_host));
+
+  // Unrelated hosts should be ignored.
+  manager->RenderFrameDeleted(main_rfh());
+  ASSERT_EQ(manager, pdf_viewer_stream_manager());
+
+  // `PdfViewerStreamManager::RenderFrameDeleted()` should cause the stream
+  // associated with `actual_host` to be deleted.
   manager->RenderFrameDeleted(actual_host);
+
+  // There are no remaining streams, so `PdfViewerStreamManager` should delete
+  // itself.
   EXPECT_FALSE(pdf_viewer_stream_manager());
 }
 
diff --git a/chrome/browser/pdf/test_pdf_viewer_stream_manager.cc b/chrome/browser/pdf/test_pdf_viewer_stream_manager.cc
index ac6456b5..5bc875d 100644
--- a/chrome/browser/pdf/test_pdf_viewer_stream_manager.cc
+++ b/chrome/browser/pdf/test_pdf_viewer_stream_manager.cc
@@ -9,11 +9,13 @@
 #include "base/check.h"
 #include "base/containers/flat_set.h"
 #include "base/run_loop.h"
+#include "chrome/browser/pdf/pdf_extension_test_util.h"
 #include "chrome/browser/pdf/pdf_viewer_stream_manager.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "content/public/test/browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace pdf {
 
@@ -52,7 +54,7 @@
   std::move(on_pdf_loaded_).Run();
 }
 
-void TestPdfViewerStreamManager::WaitUntilPdfLoaded(
+testing::AssertionResult TestPdfViewerStreamManager::WaitUntilPdfLoaded(
     content::RenderFrameHost* embedder_host) {
   // If all of the PDF frames haven't navigated, wait.
   auto* claimed_stream_info = GetClaimedStreamInfo(embedder_host);
@@ -62,16 +64,16 @@
     run_loop.Run();
   }
 
-  // TODO(crbug.com/1445746): Currently, this only waits until all of the PDF
-  // frames finished navigating. Wait until the PDF extension and content has
-  // finished loading, too.
+  // Wait until the PDF extension and content are loaded.
+  return pdf_extension_test_util::EnsurePDFHasLoaded(embedder_host);
 }
 
-void TestPdfViewerStreamManager::WaitUntilPdfLoadedInFirstChild() {
+testing::AssertionResult
+TestPdfViewerStreamManager::WaitUntilPdfLoadedInFirstChild() {
   content::RenderFrameHost* embedder_host =
       ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
   CHECK(embedder_host);
-  WaitUntilPdfLoaded(embedder_host);
+  return WaitUntilPdfLoaded(embedder_host);
 }
 
 TestPdfViewerStreamManagerFactory::TestPdfViewerStreamManagerFactory() {
diff --git a/chrome/browser/pdf/test_pdf_viewer_stream_manager.h b/chrome/browser/pdf/test_pdf_viewer_stream_manager.h
index 0fce2bf..20db7b1 100644
--- a/chrome/browser/pdf/test_pdf_viewer_stream_manager.h
+++ b/chrome/browser/pdf/test_pdf_viewer_stream_manager.h
@@ -9,6 +9,7 @@
 #include "base/functional/callback_forward.h"
 #include "chrome/browser/pdf/pdf_viewer_stream_manager.h"
 #include "content/public/browser/web_contents_user_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
 class NavigationHandle;
@@ -35,14 +36,16 @@
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
 
-  // Wait until the PDF has finished loading. `embedder_host` must be a PDF
-  // embedder host, otherwise this will hang the test.
-  void WaitUntilPdfLoaded(content::RenderFrameHost* embedder_host);
+  // Wait until the PDF has finished loading. Returns true if the PDF loads
+  // successfully, false otherwise. The test will hang if `embedder_host` is not
+  // a PDF, or if the PDF frames never finish navigating.
+  [[nodiscard]] testing::AssertionResult WaitUntilPdfLoaded(
+      content::RenderFrameHost* embedder_host);
 
   // Same as `WaitUntilPdfLoaded()`, but the first child of the primary main
   // frame should be the embedder. This is a common case where an HTML page only
   // embeds a single PDF.
-  void WaitUntilPdfLoadedInFirstChild();
+  [[nodiscard]] testing::AssertionResult WaitUntilPdfLoadedInFirstChild();
 
  private:
   base::OnceClosure on_pdf_loaded_;
diff --git a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.cc b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.cc
index f9e13dda..738af9a1 100644
--- a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.cc
+++ b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.cc
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/check_is_test.h"
 #include "base/functional/bind.h"
 #include "base/json/json_writer.h"
 #include "base/memory/scoped_refptr.h"
@@ -37,11 +38,128 @@
 #include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/reporting/util/statusor.h"
 #include "content/public/browser/browser_thread.h"
+#include "net/base/backoff_entry.h"
+#include "net/http/http_status_code.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace reporting {
 
 namespace {
+
+// Returns `true` if HTTP response code indicates an irrecoverable error.
+bool IsIrrecoverableError(int response_code) {
+  return response_code >= ::net::HTTP_BAD_REQUEST &&
+         response_code < ::net::HTTP_INTERNAL_SERVER_ERROR &&
+         response_code !=
+             ::net::HTTP_CONFLICT;  // Overlapping seq_id ranges detected
+}
+
+// Generates new backoff entry.
+std::unique_ptr<::net::BackoffEntry> GetBackoffEntry(
+    ::reporting::Priority priority) {
+  // Retry policy for SECURITY queue.
+  static const ::net::BackoffEntry::Policy kSecurityUploadBackoffPolicy = {
+      // Number of initial errors to ignore before applying
+      // exponential back-off rules.
+      /*num_errors_to_ignore=*/0,
+
+      // Initial delay is 10 seconds.
+      /*initial_delay_ms=*/10 * 1000,
+
+      // Factor by which the waiting time will be multiplied.
+      /*multiply_factor=*/2,
+
+      // Fuzzing percentage.
+      /*jitter_factor=*/0.1,
+
+      // Maximum delay is 1 minute.
+      /*maximum_backoff_ms=*/1 * 60 * 1000,
+
+      // It's up to the caller to reset the backoff time.
+      /*entry_lifetime_ms=*/-1,
+
+      /*always_use_initial_delay=*/true,
+  };
+  // Retry policy for all other queues, including initial key delivery.
+  static const ::net::BackoffEntry::Policy kDefaultUploadBackoffPolicy = {
+      // Number of initial errors to ignore before applying
+      // exponential back-off rules.
+      /*num_errors_to_ignore=*/0,
+
+      // Initial delay is 10 seconds.
+      /*initial_delay_ms=*/10 * 1000,
+
+      // Factor by which the waiting time will be multiplied.
+      /*multiply_factor=*/2,
+
+      // Fuzzing percentage.
+      /*jitter_factor=*/0.1,
+
+      // Maximum delay is 24 hours.
+      /*maximum_backoff_ms=*/24 * 60 * 60 * 1000,
+
+      // It's up to the caller to reset the backoff time.
+      /*entry_lifetime_ms=*/-1,
+
+      /*always_use_initial_delay=*/true,
+  };
+  // Maximum backoff is set per priority. Current proposal is to set SECURITY
+  // events to be backed off only slightly: max delay is set to 1 minute.
+  // For all other priorities max delay is set to 24 hours.
+  auto backoff_entry = std::make_unique<::net::BackoffEntry>(
+      priority == ::reporting::SECURITY ? &kSecurityUploadBackoffPolicy
+                                        : &kDefaultUploadBackoffPolicy);
+  return backoff_entry;
+}
+
+// State of single priority queue uploads.
+// It is a singleton, protected implicitly by the fact that all relevant
+// EncryptedReportingJobConfiguration actions are called on the sequenced task
+// runner.
+struct UploadState {
+  // Highest sequence id that has been posted for upload.
+  int64_t last_sequence_id;
+  // Generation id that has been posted for upload.
+  int64_t last_generation_id;
+
+  // Time when the next request will be allowed.
+  // This is essentially the cache value of the backoff->GetReleaseTime().
+  // When the time is reached, one request is allowed, backoff is updated as if
+  // the request failed, and the new release time is cached.
+  base::TimeTicks earliest_retry_timestamp;
+
+  // Current backoff entry for this priority.
+  std::unique_ptr<::net::BackoffEntry> backoff_entry;
+};
+// Map of all the queues states.
+using UploadStateMap = base::flat_map<::reporting::Priority, UploadState>;
+
+UploadStateMap* state_map() {
+  static base::NoDestructor<UploadStateMap> map;
+  return map.get();
+}
+
+UploadState* GetState(::reporting::Priority priority,
+                      int64_t generation_id,
+                      int64_t sequence_id) {
+  auto state_it = state_map()->find(priority);
+  if (state_it == state_map()->end() ||
+      state_it->second.last_generation_id != generation_id) {
+    // This priority pops up for the first time or (rare case) generation has
+    // changed. Record new state and allow upload.
+    state_it = state_map()
+                   ->insert_or_assign(
+                       priority,
+                       UploadState{.last_sequence_id = sequence_id,
+                                   .last_generation_id = generation_id,
+                                   .backoff_entry = GetBackoffEntry(priority)})
+                   .first;
+    state_it->second.earliest_retry_timestamp =
+        state_it->second.backoff_entry->GetReleaseTime();
+  }
+  return &state_it->second;
+}
+
 // Builds uploading payload.
 // Returns dictionary (null in case of failure) and matching memory reservation.
 void BuildPayload(
@@ -196,15 +314,12 @@
 // static
 std::unique_ptr<EncryptedReportingClient> EncryptedReportingClient::Create(
     std::unique_ptr<Delegate> delegate) {
-  return base::WrapUnique(new EncryptedReportingClient(
-      GenerationGuidIsRequired(), std::move(delegate)));
+  return base::WrapUnique(new EncryptedReportingClient(std::move(delegate)));
 }
 
 EncryptedReportingClient::EncryptedReportingClient(
-    bool is_generation_guid_required,
     std::unique_ptr<Delegate> delegate)
-    : is_generation_guid_required_(is_generation_guid_required),
-      delegate_(std::move(delegate)) {}
+    : delegate_(std::move(delegate)) {}
 
 EncryptedReportingClient::~EncryptedReportingClient() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -219,15 +334,37 @@
     policy::CloudPolicyClient* cloud_policy_client,
     ResponseCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::TimeDelta delay = WhenIsAllowedToProceed(records);
+  if (delay.is_positive()) {
+    // Reject upload.
+    std::move(callback).Run(base::unexpected(
+        Status(error::OUT_OF_RANGE, "Too many upload requests")));
+    return;
+  }
+  // Accept upload.
+  AccountForAllowedJob(records);
+
   // Construct payload on thread pool, then resume action on the current thread.
   // Perform Build on a thread pool, and upload result on UI.
-  auto create_job_cb = base::BindPostTaskToCurrentDefault(
-      base::BindOnce(&EncryptedReportingClient::CreateUploadJob,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(context),
-                     cloud_policy_client, std::move(callback)));
+  Priority priority = Priority::UNDEFINED_PRIORITY;
+  int64_t last_generation_id = -1L;
+  int64_t last_sequence_id = -1L;
+  if (!records.empty()) {
+    const auto& last_sequence_info = records.crbegin()->sequence_information();
+    priority = last_sequence_info.priority();
+    last_generation_id = last_sequence_info.generation_id();
+    last_sequence_id = last_sequence_info.sequencing_id();
+  }
+  auto create_job_cb = base::BindPostTaskToCurrentDefault(base::BindOnce(
+      &EncryptedReportingClient::CreateUploadJob,
+      weak_ptr_factory_.GetWeakPtr(), std::move(context), cloud_policy_client,
+      base::BindOnce(&EncryptedReportingClient::AccountForUploadResponse,
+                     priority, last_generation_id, last_sequence_id),
+      std::move(callback)));
   base::ThreadPool::PostTask(
       FROM_HERE, {},
-      base::BindOnce(&BuildPayload, is_generation_guid_required_,
+      base::BindOnce(&BuildPayload, GenerationGuidIsRequired(),
                      need_encryption_key, config_file_version,
                      std::move(records), std::move(scoped_reservation),
                      std::move(create_job_cb)));
@@ -236,6 +373,8 @@
 void EncryptedReportingClient::CreateUploadJob(
     std::optional<base::Value::Dict> context,
     policy::CloudPolicyClient* cloud_policy_client,
+    policy::EncryptedReportingJobConfiguration::UploadResponseCallback
+        response_cb,
     ResponseCallback callback,
     std::optional<base::Value::Dict> payload_result,
     ScopedReservation scoped_reservation) {
@@ -278,6 +417,7 @@
       device_management_service->configuration()
           ->GetEncryptedReportingServerUrl(),
       std::move(payload_result.value()), cloud_policy_client,
+      std::move(response_cb),
       base::BindOnce(&EncryptedReportingClient::OnReportUploadCompleted,
                      weak_ptr_factory_.GetWeakPtr(),
                      std::move(scoped_reservation), request_payload_size,
@@ -287,14 +427,6 @@
   if (context.has_value()) {
     config->UpdateContext(std::move(context.value()));
   }
-  const base::TimeDelta delay = config->WhenIsAllowedToProceed();
-  if (delay.is_positive()) {
-    // Reject upload.
-    config->CancelNotAllowedJob();  // Invokes callback to response back.
-    return;
-  }
-  // Accept upload.
-  config->AccountForAllowedJob();
   std::unique_ptr<policy::DeviceManagementService::Job> job =
       device_management_service->CreateJob(std::move(config));
   request_jobs_.emplace(std::move(job));
@@ -314,12 +446,12 @@
   if (job) {
     request_jobs_.erase(job);
   }
-  if (response_code == ::policy::DeviceManagementService::kTooManyRequests) {
+  if (response_code == ::net::HTTP_TOO_MANY_REQUESTS) {
     std::move(callback).Run(base::unexpected(
         Status(error::OUT_OF_RANGE, "Too many upload requests")));
     return;
   }
-  if (response_code != ::policy::DeviceManagementService::kSuccess) {
+  if (response_code != ::net::HTTP_OK) {
     std::move(callback).Run(base::unexpected(
         Status(error::DATA_LOSS,
                base::StrCat(
@@ -360,6 +492,97 @@
   std::move(callback).Run(std::move(response.value()));
 }
 
+// static
+base::TimeDelta EncryptedReportingClient::WhenIsAllowedToProceed(
+    const std::vector<EncryptedRecord>& records) {
+  // If there are no records, allow upload (it will not overload the server).
+  if (records.empty()) {
+    return base::TimeDelta();  // 0 - allowed right away.
+  }
+  // Now pick up the state.
+  const auto& last_sequence_info = records.crbegin()->sequence_information();
+  const auto* const state = GetState(last_sequence_info.priority(),
+                                     last_sequence_info.generation_id(),
+                                     last_sequence_info.sequencing_id());
+  // Use and update previously recorded state, base upload decision on it.
+  if (state->last_sequence_id > last_sequence_info.sequencing_id()) {
+    // Sequence id decreased, the upload is outdated, reject it forever.
+    return base::TimeDelta::Max();
+  }
+  if (state->last_sequence_id < last_sequence_info.sequencing_id()) {
+    // Sequence id increased, keep validating.
+    switch (last_sequence_info.priority()) {
+      case Priority::SECURITY:
+        // For SECURITY events the request is allowed.
+        return base::TimeDelta();  // 0 - allowed right away.
+      default: {
+        // For all other priorities we will act like in case of request’s
+        // last_sequence_id is == last_sequence_id above - observing the
+        // backoff time expiration.
+      }
+    }
+  }
+  // Allow upload only if earliest retry time has passed.
+  // Return delta till the allowed time - if positive, upload is going to be
+  // rejected.
+  return state->earliest_retry_timestamp -
+         state->backoff_entry->GetTimeTicksNow();
+}
+
+// static
+void EncryptedReportingClient::AccountForAllowedJob(
+    const std::vector<EncryptedRecord>& records) {
+  Priority priority = Priority::UNDEFINED_PRIORITY;
+  int64_t last_generation_id = -1L;
+  int64_t last_sequence_id = -1L;
+  if (!records.empty()) {
+    const auto& last_sequence_info = records.crbegin()->sequence_information();
+    priority = last_sequence_info.priority();
+    last_generation_id = last_sequence_info.generation_id();
+    last_sequence_id = last_sequence_info.sequencing_id();
+  }
+  auto* const state = GetState(priority, last_generation_id, last_sequence_id);
+  // Update state to reflect highest sequence_id_ (we never allow upload with
+  // lower sequence_id_).
+  state->last_sequence_id = last_sequence_id;
+  // Calculate delay as exponential backoff (based on the retry_count).
+  // Update backoff under assumption that this request fails.
+  // If it is responded successfully, we will reset it.
+  state->backoff_entry->InformOfRequest(/*succeeded=*/false);
+  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
+}
+
+// static
+void EncryptedReportingClient::AccountForUploadResponse(Priority priority,
+                                                        int64_t generation_id,
+                                                        int64_t sequence_id,
+                                                        int net_error,
+                                                        int response_code) {
+  // Analyze the net error and update upload state for possible future retries.
+  auto* const state = GetState(priority, generation_id, sequence_id);
+  if (net_error != ::net::OK) {
+    // Network error
+  } else if (IsIrrecoverableError(response_code)) {
+    // Irrecoverable error code returned by server,
+    // impose artificial 24h backoff.
+    state->backoff_entry->SetCustomReleaseTime(
+        state->backoff_entry->GetTimeTicksNow() + base::Days(1));
+  }
+  // For all other cases keep the currently set retry time.
+  // In case of success, inform backoff entry about that.
+  if (net_error == ::net::OK && response_code == ::net::HTTP_OK) {
+    state->backoff_entry->InformOfRequest(/*succeeded=*/true);
+  }
+  // Cache earliest retry time based on the current backoff entry.
+  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
+}
+
+// static
+void EncryptedReportingClient::ResetUploadsStateForTest() {
+  CHECK_IS_TEST();
+  state_map()->clear();
+}
+
 // ======== PayloadSizePerHourUmaReporter ==========
 
 // static
diff --git a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.h b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.h
index 61bda0e..ad5d436 100644
--- a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.h
+++ b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.h
@@ -19,6 +19,7 @@
 #include "base/values.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
 #include "components/reporting/proto/synced/record.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/reporting/resources/resource_manager.h"
@@ -116,24 +117,39 @@
                     policy::CloudPolicyClient* cloud_policy_client,
                     ResponseCallback callback);
 
+  // Test-only method that resets collected uploads state.
+  static void ResetUploadsStateForTest();
+
  private:
+  friend class EncryptedReportingClientTest;
+  FRIEND_TEST_ALL_PREFIXES(EncryptedReportingClientTest,
+                           IdenticalUploadRetriesThrottled);
+  FRIEND_TEST_ALL_PREFIXES(EncryptedReportingClientTest,
+                           UploadsSequenceThrottled);
+  FRIEND_TEST_ALL_PREFIXES(EncryptedReportingClientTest,
+                           SecurityUploadsSequenceNotThrottled);
+  FRIEND_TEST_ALL_PREFIXES(EncryptedReportingClientTest,
+                           FailedUploadsSequenceThrottled);
+
   using JobSet =
       base::flat_set<std::unique_ptr<policy::DeviceManagementService::Job>,
                      base::UniquePtrComparator>;
 
   // Constructor called by factory only.
-  EncryptedReportingClient(bool is_generation_guid_required,
-                           std::unique_ptr<Delegate> delegate);
+  explicit EncryptedReportingClient(std::unique_ptr<Delegate> delegate);
 
   // Constructs upload job after the data is converted into JSON, assigned to
   // `payload_result` (`nullopt` if there was an error). Calls `callback` once
   // the job has been responded or if an error has been detected, and releases
   // `scoped_reservation`.
-  void CreateUploadJob(std::optional<base::Value::Dict> context,
-                       policy::CloudPolicyClient* cloud_policy_client,
-                       ResponseCallback callback,
-                       std::optional<base::Value::Dict> payload_result,
-                       ScopedReservation scoped_reservation);
+  void CreateUploadJob(
+      std::optional<base::Value::Dict> context,
+      policy::CloudPolicyClient* cloud_policy_client,
+      policy::EncryptedReportingJobConfiguration::UploadResponseCallback
+          response_cb,
+      ResponseCallback callback,
+      std::optional<base::Value::Dict> payload_result,
+      ScopedReservation scoped_reservation);
 
   // Callback for encrypted report upload requests.
   void OnReportUploadCompleted(ScopedReservation scoped_reservation,
@@ -146,11 +162,26 @@
                                int response_code,
                                std::optional<base::Value::Dict> response);
 
+  // Checks the new job against the history, determines how soon the upload will
+  // be allowed. Returns positive value if not allowed, and 0 or negative
+  // otherwise.
+  static base::TimeDelta WhenIsAllowedToProceed(
+      const std::vector<EncryptedRecord>& records);
+
+  // Account for the job, that was allowed to proceed.
+  static void AccountForAllowedJob(const std::vector<EncryptedRecord>& records);
+
+  // Accounts for net error and response code of the upload.
+  static void AccountForUploadResponse(Priority priority,
+                                       int64_t generation_id,
+                                       int64_t sequence_id,
+                                       int net_error,
+                                       int response_code);
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   JobSet request_jobs_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  const bool is_generation_guid_required_;
   const std::unique_ptr<Delegate> delegate_;
 
   // Reports accumulated payload sizes per hour via UMA.
diff --git a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client_unittest.cc b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client_unittest.cc
index e79e475..936e7ff 100644
--- a/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client_unittest.cc
@@ -21,11 +21,11 @@
 #include "base/values.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
 #include "chrome/browser/policy/device_management_service_configuration.h"
+#include "chrome/browser/policy/messaging_layer/upload/encrypted_reporting_client.h"
 #include "chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chromeos/ash/components/system/statistics_provider.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
-#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
@@ -45,6 +45,7 @@
 #endif
 
 using testing::Eq;
+using testing::Ge;
 using testing::Property;
 using testing::SizeIs;
 using testing::StartsWith;
@@ -107,34 +108,33 @@
     device_management_service_->ScheduleInitialization(0);
     TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(
         url_loader_factory_.GetSafeWeakWrapper());
-    BuildPayload();
 
     cloud_policy_client_.SetDMToken(kDmToken);
     cloud_policy_client_.client_id_ = kClientId;
+
+    context_.SetByDottedPath("browser.userAgent", "agent-test-value");
   }
 
   void TearDown() override {
-    policy::EncryptedReportingJobConfiguration::ResetUploadsStateForTest();
+    EncryptedReportingClient::ResetUploadsStateForTest();
 
     EXPECT_THAT(memory_resource_->GetUsed(), Eq(0uL));
   }
 
-  void BuildPayload() {
-    context_.SetByDottedPath("browser.userAgent", "agent-test-value");
+  void AddRecordToPayload(Priority priority = Priority::SLOW_BATCH) {
+    payload_records_.emplace_back();
 
-    EncryptedRecord encrypted_record;
+    EncryptedRecord& encrypted_record = payload_records_.back();
     encrypted_record.set_encrypted_wrapped_record(kEncryptedRecord);
 
     SequenceInformation* const sequence_information =
         encrypted_record.mutable_sequence_information();
     sequence_information->set_generation_id(kGenerationId);
-    sequence_information->set_sequencing_id(sequence_id_);
-    sequence_information->set_priority(Priority::SLOW_BATCH);
-
-    payload_records_.emplace_back(encrypted_record);
+    sequence_information->set_sequencing_id(sequence_id_++);
+    sequence_information->set_priority(priority);
   }
 
-  void DecrementSequenceId() { --sequence_id_; }
+  void DecrementSequenceId(int64_t by = 0L) { sequence_id_ -= (by + 1L); }
 
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -166,14 +166,15 @@
       std::make_unique<FakeDelegate>(device_management_service_.get()));
 
   {
+    AddRecordToPayload();
     ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                          memory_resource_);
     ASSERT_TRUE(scoped_reservation.reserved());
     test::TestEvent<StatusOr<base::Value::Dict>> response_event;
     encrypted_reporting_client->UploadReport(
         need_encryption_key_, config_file_version_, payload_records_,
-        std::move(scoped_reservation), std::move(context_),
-        &cloud_policy_client_, response_event.cb());
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
     task_environment_.RunUntilIdle();
 
     ASSERT_THAT(*url_loader_factory_.pending_requests(), SizeIs(1));
@@ -208,19 +209,18 @@
   // Avoid rate limiting by time, but still reject the upload because of lower
   // sequence id.
   task_environment_.FastForwardBy(base::Minutes(1));
-  DecrementSequenceId();
+  DecrementSequenceId(1L);
 
   {
-    BuildPayload();
-
+    AddRecordToPayload();
     ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                          memory_resource_);
     ASSERT_TRUE(scoped_reservation.reserved());
     test::TestEvent<StatusOr<base::Value::Dict>> response_event;
     encrypted_reporting_client->UploadReport(
         need_encryption_key_, config_file_version_, payload_records_,
-        std::move(scoped_reservation), std::move(context_),
-        &cloud_policy_client_, response_event.cb());
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
 
     // Sequence ID decreased, upload is rejected.
     const auto actual_response = response_event.result();
@@ -228,7 +228,8 @@
                 Property(&StatusOr<base::Value::Dict>::error,
                          AllOf(Property(&Status::code, Eq(error::OUT_OF_RANGE)),
                                Property(&Status::error_message,
-                                        StrEq("Too many upload requests")))));
+                                        StrEq("Too many upload requests")))))
+        << actual_response.error();
   }
 }
 
@@ -236,13 +237,14 @@
   auto encrypted_reporting_client =
       EncryptedReportingClient::Create(std::make_unique<FakeDelegate>(nullptr));
 
+  AddRecordToPayload();
   ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                        memory_resource_);
   ASSERT_TRUE(scoped_reservation.reserved());
   test::TestEvent<StatusOr<base::Value::Dict>> response_event;
   encrypted_reporting_client->UploadReport(
       need_encryption_key_, config_file_version_, payload_records_,
-      std::move(scoped_reservation), std::move(context_), &cloud_policy_client_,
+      std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
       response_event.cb());
   const auto actual_response = response_event.result();
   EXPECT_THAT(
@@ -262,6 +264,7 @@
       std::make_unique<FakeDelegate>(device_management_service_.get()));
 
   {
+    AddRecordToPayload();
     ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                          memory_resource_);
     ASSERT_TRUE(scoped_reservation.reserved());
@@ -304,20 +307,22 @@
   // Repeat the same upload, get it rejected by rate limiter.
   task_environment_.FastForwardBy(base::Seconds(1));
   {
+    AddRecordToPayload();
     ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                          memory_resource_);
     ASSERT_TRUE(scoped_reservation.reserved());
     test::TestEvent<StatusOr<base::Value::Dict>> response_event;
     encrypted_reporting_client->UploadReport(
         need_encryption_key_, config_file_version_, payload_records_,
-        std::move(scoped_reservation), std::move(context_),
-        &cloud_policy_client_, response_event.cb());
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
     const auto actual_response = response_event.result();
     EXPECT_THAT(actual_response,
                 Property(&StatusOr<base::Value::Dict>::error,
                          AllOf(Property(&Status::code, Eq(error::OUT_OF_RANGE)),
                                Property(&Status::error_message,
-                                        StrEq("Too many upload requests")))));
+                                        StrEq("Too many upload requests")))))
+        << actual_response.error();
   }
 }
 
@@ -329,13 +334,14 @@
   // the request headers.
   auto encrypted_reporting_client = EncryptedReportingClient::Create(
       std::make_unique<FakeDelegate>(device_management_service_.get()));
+  AddRecordToPayload();
   ScopedReservation scoped_reservation(RecordsSize(payload_records_),
                                        memory_resource_);
   ASSERT_TRUE(scoped_reservation.reserved());
   test::TestEvent<StatusOr<base::Value::Dict>> response_event;
   encrypted_reporting_client->UploadReport(
       need_encryption_key_, config_file_version_, payload_records_,
-      std::move(scoped_reservation), std::move(context_), nullptr,
+      std::move(scoped_reservation), context_.Clone(), nullptr,
       base::DoNothing());
   task_environment_.RunUntilIdle();
 
@@ -346,4 +352,231 @@
       (*url_loader_factory_.pending_requests())[0].request.headers.ToString(),
       kDmToken));
 }
+
+TEST_F(EncryptedReportingClientTest, IdenticalUploadRetriesThrottled) {
+  const size_t kTotalRetries = 10;
+
+  auto encrypted_reporting_client = EncryptedReportingClient::Create(
+      std::make_unique<FakeDelegate>(device_management_service_.get()));
+
+  base::TimeDelta expected_delay_after = base::Seconds(10);
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    AddRecordToPayload();
+    ScopedReservation scoped_reservation(RecordsSize(payload_records_),
+                                         memory_resource_);
+    ASSERT_TRUE(scoped_reservation.reserved());
+
+    auto allowed_delay =
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_);
+    if (i == 0) {
+      // First upload allowed immediately.
+      EXPECT_FALSE(allowed_delay.is_positive());
+    } else {
+      // Further uploads allowed with delay.
+      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
+      // Double the expectation for the next retry.
+      expected_delay_after *= 2;
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(
+          encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+              .is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+            .is_positive());
+
+    test::TestEvent<StatusOr<base::Value::Dict>> response_event;
+    encrypted_reporting_client->UploadReport(
+        need_encryption_key_, config_file_version_, payload_records_,
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
+    task_environment_.RunUntilIdle();
+
+    ASSERT_THAT(*url_loader_factory_.pending_requests(), SizeIs(1));
+
+    const std::string& pending_request_url =
+        (*url_loader_factory_.pending_requests())[0].request.url.spec();
+    EXPECT_THAT(pending_request_url, StartsWith(kServerUrl));
+
+    url_loader_factory_.SimulateResponseForPendingRequest(
+        pending_request_url,
+        base::StringPrintf(R"({"%s" : "%s"})", kResponseKey, kResponseValue));
+
+    const auto actual_response = response_event.result();
+    ASSERT_TRUE(actual_response.has_value());
+    ASSERT_THAT(actual_response.value(), SizeIs(1));
+    ASSERT_TRUE(actual_response.value().FindString(kResponseKey));
+    EXPECT_THAT(*(actual_response.value().FindString(kResponseKey)),
+                StrEq(kResponseValue));
+
+    encrypted_reporting_client->AccountForAllowedJob(payload_records_);
+  }
+}
+
+TEST_F(EncryptedReportingClientTest, UploadsSequenceThrottled) {
+  const size_t kTotalRetries = 10;
+
+  auto encrypted_reporting_client = EncryptedReportingClient::Create(
+      std::make_unique<FakeDelegate>(device_management_service_.get()));
+
+  base::TimeDelta expected_delay_after = base::Seconds(10);
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    AddRecordToPayload();
+    ScopedReservation scoped_reservation(RecordsSize(payload_records_),
+                                         memory_resource_);
+    ASSERT_TRUE(scoped_reservation.reserved());
+
+    auto allowed_delay =
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_);
+    if (i == 0) {
+      // First upload allowed immediately.
+      EXPECT_FALSE(allowed_delay.is_positive());
+    } else {
+      // Further uploads allowed with delay.
+      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
+      // Double the expectation for the next retry.
+      expected_delay_after *= 2;
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(
+          encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+              .is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+            .is_positive());
+
+    test::TestEvent<StatusOr<base::Value::Dict>> response_event;
+    encrypted_reporting_client->UploadReport(
+        need_encryption_key_, config_file_version_, payload_records_,
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
+    task_environment_.RunUntilIdle();
+
+    ASSERT_THAT(*url_loader_factory_.pending_requests(), SizeIs(1));
+
+    const std::string& pending_request_url =
+        (*url_loader_factory_.pending_requests())[0].request.url.spec();
+    EXPECT_THAT(pending_request_url, StartsWith(kServerUrl));
+
+    url_loader_factory_.SimulateResponseForPendingRequest(
+        pending_request_url,
+        base::StringPrintf(R"({"%s" : "%s"})", kResponseKey, kResponseValue));
+
+    const auto actual_response = response_event.result();
+    ASSERT_TRUE(actual_response.has_value());
+    ASSERT_THAT(actual_response.value(), SizeIs(1));
+    ASSERT_TRUE(actual_response.value().FindString(kResponseKey));
+    EXPECT_THAT(*(actual_response.value().FindString(kResponseKey)),
+                StrEq(kResponseValue));
+
+    encrypted_reporting_client->AccountForAllowedJob(payload_records_);
+  }
+}
+
+TEST_F(EncryptedReportingClientTest, SecurityUploadsSequenceNotThrottled) {
+  const size_t kTotalRetries = 10;
+
+  auto encrypted_reporting_client = EncryptedReportingClient::Create(
+      std::make_unique<FakeDelegate>(device_management_service_.get()));
+
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    AddRecordToPayload(Priority::SECURITY);
+    ScopedReservation scoped_reservation(RecordsSize(payload_records_),
+                                         memory_resource_);
+    ASSERT_TRUE(scoped_reservation.reserved());
+
+    // New SECURITY event upload is allowed immediately.
+    EXPECT_FALSE(
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+            .is_positive());
+
+    test::TestEvent<StatusOr<base::Value::Dict>> response_event;
+    encrypted_reporting_client->UploadReport(
+        need_encryption_key_, config_file_version_, payload_records_,
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
+    task_environment_.RunUntilIdle();
+
+    ASSERT_THAT(*url_loader_factory_.pending_requests(), SizeIs(1));
+
+    const std::string& pending_request_url =
+        (*url_loader_factory_.pending_requests())[0].request.url.spec();
+    EXPECT_THAT(pending_request_url, StartsWith(kServerUrl));
+
+    url_loader_factory_.SimulateResponseForPendingRequest(
+        pending_request_url,
+        base::StringPrintf(R"({"%s" : "%s"})", kResponseKey, kResponseValue));
+
+    const auto actual_response = response_event.result();
+    ASSERT_TRUE(actual_response.has_value());
+    ASSERT_THAT(actual_response.value(), SizeIs(1));
+    ASSERT_TRUE(actual_response.value().FindString(kResponseKey));
+    EXPECT_THAT(*(actual_response.value().FindString(kResponseKey)),
+                StrEq(kResponseValue));
+
+    encrypted_reporting_client->AccountForAllowedJob(payload_records_);
+  }
+}
+
+TEST_F(EncryptedReportingClientTest, FailedUploadsSequenceThrottled) {
+  const size_t kTotalRetries = 10;
+
+  auto encrypted_reporting_client = EncryptedReportingClient::Create(
+      std::make_unique<FakeDelegate>(device_management_service_.get()));
+
+  for (size_t i = 0; i < kTotalRetries; ++i) {
+    AddRecordToPayload();
+    ScopedReservation scoped_reservation(RecordsSize(payload_records_),
+                                         memory_resource_);
+    ASSERT_TRUE(scoped_reservation.reserved());
+
+    auto allowed_delay =
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_);
+    if (i == 0) {
+      // The very first upload is allowed.
+      EXPECT_FALSE(allowed_delay.is_positive());
+    } else {
+      // Delay after permanent error is 1 day.
+      EXPECT_THAT(allowed_delay, Ge(base::Days(1)));
+      // Move forward to allow.
+      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
+      EXPECT_TRUE(
+          encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+              .is_positive());
+      task_environment_.FastForwardBy(base::Seconds(1));
+    }
+
+    EXPECT_FALSE(
+        encrypted_reporting_client->WhenIsAllowedToProceed(payload_records_)
+            .is_positive());
+
+    test::TestEvent<StatusOr<base::Value::Dict>> response_event;
+    encrypted_reporting_client->UploadReport(
+        need_encryption_key_, config_file_version_, payload_records_,
+        std::move(scoped_reservation), context_.Clone(), &cloud_policy_client_,
+        response_event.cb());
+    task_environment_.RunUntilIdle();
+
+    ASSERT_THAT(*url_loader_factory_.pending_requests(), SizeIs(1));
+
+    const std::string& pending_request_url =
+        (*url_loader_factory_.pending_requests())[0].request.url.spec();
+    EXPECT_THAT(pending_request_url, StartsWith(kServerUrl));
+
+    url_loader_factory_.SimulateResponseForPendingRequest(
+        pending_request_url, "",
+        /*status=*/::net::HTTP_UNAUTHORIZED);  // Permanent error.
+
+    const auto actual_response = response_event.result();
+    ASSERT_FALSE(actual_response.has_value());
+
+    encrypted_reporting_client->AccountForAllowedJob(payload_records_);
+  }
+}
 }  // namespace reporting
diff --git a/chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.cc b/chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.cc
index 9bf3f4f3..b197e64c 100644
--- a/chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.cc
+++ b/chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.cc
@@ -27,7 +27,6 @@
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/cloud/dm_token.h"
-#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_service.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
@@ -110,7 +109,7 @@
 }
 
 ReportingServerConnector::TestEnvironment::~TestEnvironment() {
-  policy::EncryptedReportingJobConfiguration::ResetUploadsStateForTest();
+  EncryptedReportingClient::ResetUploadsStateForTest();
   base::Singleton<ReportingServerConnector>::OnExit(nullptr);
 }
 
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 6efdc4bd..690f748 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -550,38 +550,6 @@
 // Please keep the list of deprecated prefs in chronological order. i.e. Add to
 // the bottom of the list, not here at the top.
 
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-const char kArcTermsShownInOobe[] = "arc.terms.shown_in_oobe";
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
-
-// Deprecated 02/2023.
-const char kSyncInvalidationVersions[] = "sync.invalidation_versions";
-const char kSyncInvalidationVersions2[] = "sync.invalidation_versions2";
-
-// Deprecated 02/2023.
-const char kClearPluginLSODataEnabled[] = "browser.clear_lso_data_enabled";
-const char kContentSettingsPluginAllowlist[] =
-    "profile.content_settings.plugin_whitelist";
-const char kPepperFlashSettingsEnabled[] =
-    "browser.pepper_flash_settings_enabled";
-const char kPluginsAllowOutdated[] = "plugins.allow_outdated";
-const char kPluginsLastInternalDirectory[] = "plugins.last_internal_directory";
-const char kPluginsPluginsList[] = "plugins.plugins_list";
-const char kPluginsShowDetails[] = "plugins.show_details";
-
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-const char kWebAppsUrlHandlerInfo[] = "web_apps.url_handler_info";
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-const char kHasSeenSmartLockSignInRemovedNotification[] =
-    "easy_unlock.has_seen_smart_lock_sign_in_removed_notification";
-const char kEasyUnlockLocalStateTpmKeys[] = "easy_unlock.public_tpm_keys";
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // Deprecated 03/2023.
 const char kGoogleSearchDomainMixingMetricsEmitterLastMetricsTime[] =
     "browser.last_google_search_domain_mixing_metrics_time";
@@ -1013,16 +981,6 @@
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  registry->RegisterDictionaryPref(kWebAppsUrlHandlerInfo);
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  registry->RegisterDictionaryPref(kEasyUnlockLocalStateTpmKeys);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // Deprecated 03/2023.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterTimeDeltaPref(kGlanceablesSignoutScreenshotDuration,
@@ -1140,31 +1098,6 @@
     user_prefs::PrefRegistrySyncable* registry) {
   chrome_browser_net::secure_dns::RegisterProbesSettingBackupPref(registry);
 
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  registry->RegisterBooleanPref(kArcTermsShownInOobe, false);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
-  // Deprecated 02/2023.
-  registry->RegisterDictionaryPref(kSyncInvalidationVersions);
-  registry->RegisterDictionaryPref(kSyncInvalidationVersions2);
-
-  // Deprecated 02/2023.
-  registry->RegisterBooleanPref(kClearPluginLSODataEnabled, false);
-  registry->RegisterDictionaryPref(kContentSettingsPluginAllowlist);
-  registry->RegisterBooleanPref(kPepperFlashSettingsEnabled, false);
-  registry->RegisterBooleanPref(kPluginsAllowOutdated, false);
-  registry->RegisterFilePathPref(kPluginsLastInternalDirectory,
-                                 base::FilePath());
-  registry->RegisterListPref(kPluginsPluginsList);
-  registry->RegisterBooleanPref(kPluginsShowDetails, false);
-
-// Deprecated 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  registry->RegisterBooleanPref(kHasSeenSmartLockSignInRemovedNotification,
-                                false);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
   // Deprecated 03/2023.
   registry->RegisterTimePref(
       kGoogleSearchDomainMixingMetricsEmitterLastMetricsTime, base::Time());
@@ -2198,16 +2131,6 @@
   // BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS
   // Please don't delete the preceding line. It is used by PRESUBMIT.py.
 
-// Added 02/2023.
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  local_state->ClearPref(kWebAppsUrlHandlerInfo);
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-
-// Added 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  local_state->ClearPref(kEasyUnlockLocalStateTpmKeys);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // Added 03/2023.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   local_state->ClearPref(kGlanceablesSignoutScreenshotDuration);
@@ -2364,29 +2287,6 @@
     }
   }
 
-// Added 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  profile_prefs->ClearPref(kArcTermsShownInOobe);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
-  // Added 02/2023.
-  profile_prefs->ClearPref(kSyncInvalidationVersions);
-  profile_prefs->ClearPref(kSyncInvalidationVersions2);
-
-  // Added 02/2023.
-  profile_prefs->ClearPref(kClearPluginLSODataEnabled);
-  profile_prefs->ClearPref(kContentSettingsPluginAllowlist);
-  profile_prefs->ClearPref(kPepperFlashSettingsEnabled);
-  profile_prefs->ClearPref(kPluginsAllowOutdated);
-  profile_prefs->ClearPref(kPluginsLastInternalDirectory);
-  profile_prefs->ClearPref(kPluginsPluginsList);
-  profile_prefs->ClearPref(kPluginsShowDetails);
-
-// Added 02/2023.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  profile_prefs->ClearPref(kHasSeenSmartLockSignInRemovedNotification);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // Added 03/2023.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::ambient::prefs::MigrateDeprecatedPrefs(*profile_prefs);
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 7f0f101..4dd3da39 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -294,6 +294,7 @@
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/file_manager/cloud_upload_prefs_watcher.h"
 #include "chrome/browser/ash/input_method/editor_mediator_factory.h"
+#include "chrome/browser/ash/language_packs/language_pack_font_service_factory.h"
 #include "chrome/browser/ash/policy/dlp/files_policy_notification_manager_factory.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
 #include "chrome/browser/push_notification/push_notification_service_factory.h"
@@ -602,6 +603,9 @@
   if (chromeos::features::IsOrcaEnabled()) {
     ash::input_method::EditorMediatorFactory::GetInstance();
   }
+  if (base::FeatureList::IsEnabled(ash::features::kLanguagePacksFonts)) {
+    ash::language_packs::LanguagePackFontServiceFactory::GetInstance();
+  }
 #endif
   AutocompleteClassifierFactory::GetInstance();
   AutocompleteControllerEmitter::EnsureFactoryBuilt();
diff --git a/chrome/browser/profiles/keep_alive/profile_keep_alive_types.cc b/chrome/browser/profiles/keep_alive/profile_keep_alive_types.cc
index 687307e2..85002bc 100644
--- a/chrome/browser/profiles/keep_alive/profile_keep_alive_types.cc
+++ b/chrome/browser/profiles/keep_alive/profile_keep_alive_types.cc
@@ -83,6 +83,8 @@
       return out << "kOsIntegrationForceUnregistration";
     case ProfileKeepAliveOrigin::kRemoteDebugging:
       return out << "kRemoteDebugging";
+    case ProfileKeepAliveOrigin::kHeadlessCommand:
+      return out << "kHeadlessCommand";
   }
   NOTREACHED();
   return out << static_cast<int>(origin);
diff --git a/chrome/browser/profiles/keep_alive/profile_keep_alive_types.h b/chrome/browser/profiles/keep_alive/profile_keep_alive_types.h
index c3f4739..25c496f 100644
--- a/chrome/browser/profiles/keep_alive/profile_keep_alive_types.h
+++ b/chrome/browser/profiles/keep_alive/profile_keep_alive_types.h
@@ -165,9 +165,13 @@
   // closed.
   kRemoteDebugging = 38,
 
-  kMaxValue = kRemoteDebugging,
+  // Used by Headless Command Processor to retain the profile used by the
+  // command handler, which does not belong to any window.
+  kHeadlessCommand = 39,
+
+  kMaxValue = kHeadlessCommand,
 };
-// LINT.ThenChange(/tools/metrics/histograms/enums.xml)
+// LINT.ThenChange(/tools/metrics/histograms/metadata/profile/enums.xml)
 
 std::ostream& operator<<(std::ostream& out,
                          const ProfileKeepAliveOrigin& origin);
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index 94331fe..afbb4b3c 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -584,8 +584,8 @@
     // frame.
     content::RenderFrameHost* frame;
     if (UseOopif()) {
-      GetTestPdfViewerStreamManager(web_contents)
-          ->WaitUntilPdfLoadedInFirstChild();
+      ASSERT_TRUE(GetTestPdfViewerStreamManager(web_contents)
+                      ->WaitUntilPdfLoadedInFirstChild());
       frame = pdf_extension_test_util::GetOnlyPdfExtensionHost(web_contents);
     } else {
       auto* guest_view =
diff --git a/chrome/browser/resources/ash/settings/controls/settings_radio_group.html b/chrome/browser/resources/ash/settings/controls/settings_radio_group.html
index 3003a754..3638615 100644
--- a/chrome/browser/resources/ash/settings/controls/settings_radio_group.html
+++ b/chrome/browser/resources/ash/settings/controls/settings_radio_group.html
@@ -6,6 +6,7 @@
 <cr-radio-group selected="[[selected]]"
     on-selected-changed="onSelectedChanged_"
     aria-label$="[[groupAriaLabel]]"
-    selectable-elements="[[selectableElements]]">
+    selectable-elements="[[selectableElements]]"
+    disabled="[[disabled]]">
   <slot></slot>
 </cr-radio-group>
diff --git a/chrome/browser/resources/ash/settings/controls/settings_radio_group.ts b/chrome/browser/resources/ash/settings/controls/settings_radio_group.ts
index 3ee7bd0..ced6062b 100644
--- a/chrome/browser/resources/ash/settings/controls/settings_radio_group.ts
+++ b/chrome/browser/resources/ash/settings/controls/settings_radio_group.ts
@@ -34,6 +34,11 @@
 
   static get properties() {
     return {
+      disabled: {
+        type: Boolean,
+        value: false,
+      },
+
       groupAriaLabel: String,
 
       /**
@@ -61,6 +66,7 @@
     ];
   }
 
+  disabled: boolean;
   groupAriaLabel: string;
   noSetPref: boolean;
   selected: string;
diff --git a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.html b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.html
index 9bfbbc1..77360cc 100644
--- a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.html
+++ b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.html
@@ -22,7 +22,8 @@
 <div id="timezoneRadioContainer" class="settings-box first">
   <settings-radio-group id="timeZoneRadioGroup"
       pref="{{prefs.generated.resolve_timezone_by_geolocation_on_off}}"
-      deep-link-focus-id$="[[Setting.kChangeTimeZone]]">
+      deep-link-focus-id$="[[Setting.kChangeTimeZone]]"
+      disabled="[[isGuest_]]">
     <controlled-radio-button
         id="timeZoneAutoDetectOn"
         name="true"
diff --git a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
index f8e5497f..8196bb6 100644
--- a/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
+++ b/chrome/browser/resources/ash/settings/date_time_page/timezone_subpage.ts
@@ -63,6 +63,13 @@
         notify: true,
       },
 
+      isGuest_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('isGuest');
+        },
+      },
+
       /**
        * Used by DeepLinkingMixin to focus this page's deep links.
        */
@@ -91,6 +98,7 @@
   }
 
   activeTimeZoneDisplayName: string;
+  private isGuest_: boolean;
   private browserProxy_: TimeZoneBrowserProxy;
   private showEnableSystemGeolocationDialog_: boolean;
   private shouldShowGeolocationWarningText_: boolean;
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts b/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
index f4a1cdc..0bdb84d 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
@@ -10,8 +10,9 @@
 import '../controls/settings_dropdown_menu.js';
 import '../os_settings_icons.html.js';
 
-import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
+import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
+import {assert} from 'chrome://resources/js/assert.js';
 import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -127,6 +128,11 @@
     this.$.remappingActionDropdown!.focus();
   }
 
+  override focus(): void {
+    assert(this.$.remappingActionDropdown);
+    this.$.remappingActionDropdown.focus();
+  }
+
   private observeButtonPresses(): void {
     if (this.inputDeviceSettingsProvider_ instanceof
         FakeInputDeviceSettingsProvider) {
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
index 66cbf4a..0256ac9 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
+++ b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
@@ -28,7 +28,7 @@
     width: 256px;
   }
 </style>
-<div class="subsection">
+<div class="subsection" id="subsection">
   <template is="dom-repeat"
       items="{{buttonRemappingList}}"
       index-as="index">
@@ -85,5 +85,6 @@
     id="keyCombinationInputDialog"
     button-remapping-list="{{buttonRemappingList}}"
     remapping-index="[[selectedButtonIndex_]]"
-    has-launcher-button="[[hasLauncherButton]]">
+    has-launcher-button="[[hasLauncherButton]]"
+    on-close="onKeyCombinationDialogClose_">
 </key-combination-input-dialog>
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
index 44b8820e4..d9019ab 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
@@ -16,6 +16,7 @@
 import './key_combination_input_dialog.js';
 
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
+import {assert} from 'chrome://resources/js/assert.js';
 import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -28,6 +29,7 @@
 export interface CustomizeButtonsSubsectionElement {
   $: {
     keyCombinationInputDialog: KeyCombinationInputDialogElement,
+    subsection: HTMLDivElement,
   };
 }
 
@@ -121,10 +123,14 @@
     this.addEventListener(
         'show-key-combination-dialog', this.showKeyCombinationDialog_);
     this.dragAndDropManager.init(this, this.onDrop_.bind(this));
+    this.addEventListener(
+        'key-combination-dialog-close', this.onKeyCombinationDialogClose_);
   }
 
   override disconnectedCallback(): void {
     this.dragAndDropManager.destroy();
+    this.removeEventListener(
+        'key-combination-dialog-close', this.onKeyCombinationDialogClose_);
   }
 
   private showRenamingDialog_(e: ShowRenamingDialogEvent): void {
@@ -243,6 +249,14 @@
           composed: true,
         }));
       };
+
+  private onKeyCombinationDialogClose_(): void {
+    const buttonRows =
+        this.$.subsection.querySelectorAll('customize-button-row');
+
+    assert(!!buttonRows && buttonRows.length > this.selectedButtonIndex_);
+    buttonRows[this.selectedButtonIndex_].focus();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.html b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.html
index b383aa15..ecf4f0eea 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.html
@@ -25,7 +25,7 @@
             label="$i18n{appNotificationsTitle}"
             on-click="onClickAppNotifications_"
             role-description="$i18n{subpageArrowRoleDescription}"
-            sub-label="[[getAppListCountDescription_(
+            sub-label="[[getAppNotificationsRowSublabel_(
                 appsWithNotifications_.*, isDndEnabled_)]]">
         </cr-link-row>
       </template>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
index 9c0e2a7..e3e299c4 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/os_apps_page.ts
@@ -370,9 +370,13 @@
     this.isDndEnabled_ = enabled;
   }
 
-  private getAppListCountDescription_(): string {
+  private getAppNotificationsRowSublabel_(): string {
+    if (this.isRevampWayfindingEnabled_) {
+      return this.i18n('appNotificationsRowSublabel');
+    }
+
     return this.isDndEnabled_ ?
-        this.i18n('appNotificationsDoNotDisturbDescription') :
+        this.i18n('appNotificationsDoNotDisturbEnabledDescription') :
         this.i18n(
             'appNotificationsCountDescription',
             this.appsWithNotifications_.length);
diff --git a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
index 0330b11..369b70a 100644
--- a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
+++ b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
@@ -46,7 +46,9 @@
                       'HTML[A-Za-z]{0,}Element|' +
                       // Exclude native DOM interfaces.
                       'UIEvent|UIEventInit|DOMError|' +
-                      // Exclude the SACache and SACommands classes.
+                      // Exclude ISearchUI.
+                      'ISearchUI|' +
+                      // Exclude the SA* classes.
                       'SACache|SACommands|SAChildNode|SANode|SARootNode)$',
                   match: false,
                 },
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index e7335d6..f82b219 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -54,6 +54,7 @@
   "background/logging/log_manager.ts",
   "common/msgs.ts",
   "common/tts_types.ts",
+  "panel/i_search_ui.ts",
   "panel/panel.ts",
   "panel/panel_captions.ts",
   "panel/panel_menu_item.ts",
@@ -166,7 +167,6 @@
   "learn_mode/learn_mode.js",
   "log_page/log.js",
   "log_page/log_loader.js",
-  "panel/i_search_ui.js",
   "panel/menu_manager.js",
   "panel/panel_interface.js",
   "panel/panel_menu.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.ts
similarity index 71%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.js
rename to chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.ts
index 0f9e987..8a5c109 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_ui.ts
@@ -14,10 +14,16 @@
 const Dir = constants.Dir;
 
 export class ISearchUI {
-  /** @param {!Element} input */
-  constructor(input) {
+  static instance?: ISearchUI;
+
+  onKeyDown: (event: KeyboardEvent) => boolean;
+  onTextInput: (event: Event) => boolean;
+
+  private input_: HTMLInputElement | null;
+  private dir_ = Dir.FORWARD;
+
+  constructor(input: HTMLInputElement) {
     this.input_ = input;
-    this.dir_ = Dir.FORWARD;
 
     this.onKeyDown = event => this.onKeyDown_(event);
     this.onTextInput = event => this.onTextInput_(event);
@@ -26,11 +32,7 @@
     input.addEventListener('textInput', this.onTextInput, false);
   }
 
-  /**
-   * @param {!Element} input
-   * @return {!Promise<ISearchUI>}
-   */
-  static async init(input) {
+  static async init(input: HTMLInputElement): Promise<ISearchUI> {
     if (ISearchUI.instance) {
       ISearchUI.instance.destroy();
     }
@@ -42,13 +44,7 @@
     return ISearchUI.instance;
   }
 
-  /**
-   * Listens to key down events.
-   * @param {Event} evt
-   * @return {boolean}
-   * @private
-   */
-  onKeyDown_(evt) {
+  private onKeyDown_(evt: KeyboardEvent): boolean {
     switch (evt.key) {
       case 'ArrowUp':
         this.dir_ = Dir.BACKWARD;
@@ -68,34 +64,27 @@
       default:
         return false;
     }
+    // TODO(b/314203187): Not null asserted, check that this is correct.
     BackgroundBridge.PanelBackground.incrementalSearch(
-        this.input_.value, this.dir_, true);
+        this.input_!.value, this.dir_, true);
     evt.preventDefault();
     evt.stopPropagation();
     return false;
   }
 
-  /**
-   * Listens to text input events.
-   * @param {Event} evt
-   * @return {boolean}
-   * @private
-   */
-  onTextInput_(evt) {
-    const searchStr = evt.target.value + evt.data;
+  private onTextInput_(evt: Event): boolean {
+    const searchStr =
+        (evt.target as HTMLInputElement).value + (evt as InputEvent).data;
     BackgroundBridge.PanelBackground.incrementalSearch(searchStr, this.dir_);
     return true;
   }
 
   /** Unregisters event handlers. */
-  destroy() {
+  destroy(): void {
     BackgroundBridge.PanelBackground.destroyISearch();
     const input = this.input_;
     this.input_ = null;
-    input.removeEventListener('keydown', this.onKeyDown, true);
-    input.removeEventListener('textInput', this.onTextInput, false);
+    input?.removeEventListener('keydown', this.onKeyDown, true);
+    input?.removeEventListener('textInput', this.onTextInput, false);
   }
-}
-
-/** @type {ISearchUI} */
-ISearchUI.instance;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
index 9576f43..18a6f469d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
@@ -43,7 +43,7 @@
   private brailleTableElement_ = $('braille-table') as HTMLTableElement;
   private brailleTableElement2_ = $('braille-table2') as HTMLTableElement;
   private searchContainer_ = $('search-container');
-  private searchInput_: HTMLElement = $('search')!;
+  private searchInput_ = $('search') as HTMLInputElement;
   private speechContainer_ = $('speech-container');
   private speechElement_ = $('speech');
   private tutorialReadyForTesting_ = false;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
index b72f7f0..cc7ac5e 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
@@ -41,6 +41,7 @@
   "nodes/switch_access_node.ts",
   "point_scan_manager.ts",
   "settings_manager.ts",
+  "switch_access.ts",
   "switch_access_constants.ts",
   "switch_access_loader.ts",
 ]
@@ -61,7 +62,6 @@
   "nodes/slider_node.js",
   "nodes/tab_node.js",
   "nodes/window_node.js",
-  "switch_access.js",
   "switch_access_predicate.js",
   "text_navigation_manager.js",
 ]
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.ts
similarity index 65%
rename from chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js
rename to chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.ts
index 33b8ac58..2f8d7a0 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access.ts
@@ -2,21 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AsyncUtil} from '../common/async_util.js';
-import {EventHandler} from '../common/event_handler.js';
-import {FlagName, Flags} from '../common/flags.js';
+import {AsyncUtil} from '/common/async_util.js';
+import {EventHandler} from '/common/event_handler.js';
+import {FlagName, Flags} from '/common/flags.js';
 
 import {Navigator} from './navigator.js';
 import {KeyboardRootNode} from './nodes/keyboard_node.js';
 import {ErrorType, Mode} from './switch_access_constants.js';
 
-const AutomationNode = chrome.automation.AutomationNode;
+type AutomationEvent = chrome.automation.AutomationEvent;
+type AutomationNode = chrome.automation.AutomationNode;
 const EventType = chrome.automation.EventType;
-const FindParams = chrome.automation.FindParams;
+type FindParams = chrome.automation.FindParams;
 const RoleType = chrome.automation.RoleType;
 
-let readyCallback;
-const readyPromise = new Promise(resolve => readyCallback = resolve);
+let readyCallback: VoidFunction;
+const readyPromise: Promise<void> =
+    new Promise(resolve => readyCallback = resolve);
 
 /**
  * The top-level class for the Switch Access accessibility feature. Handles
@@ -24,14 +26,11 @@
  * codebase.
  */
 export class SwitchAccess {
-  /** @private */
-  constructor() {
-    /* @private {!Mode} */
-    this.mode_ = Mode.ITEM_SCAN;
-  }
+  static instance?: SwitchAccess;
+  static mode = Mode.ITEM_SCAN;
+  private constructor() {}
 
-  /** @param {!AutomationNode} desktop */
-  static async init(desktop) {
+  static async init(desktop: AutomationNode): Promise<void> {
     if (SwitchAccess.instance) {
       throw new Error('Cannot create two SwitchAccess.instances');
     }
@@ -42,43 +41,32 @@
   }
 
   /** Starts Switch Access behavior. */
-  static start() {
+  static start(): void {
     KeyboardRootNode.startWatchingVisibility();
     readyCallback();
   }
 
-  /** @return {!Promise} */
-  static async ready() {
+  static async ready(): Promise<void> {
     return readyPromise;
   }
 
   /**
    * Returns whether or not the feature flag
    * for improved text input is enabled.
-   * @return {boolean}
    */
-  static improvedTextInputEnabled() {
-    return Flags.isEnabled(FlagName.SWITCH_ACCESS_TEXT);
-  }
-
-  /** @return {!Mode} */
-  static get mode() {
-    return SwitchAccess.instance.mode_;
-  }
-
-  /** @param {!Mode} newMode */
-  static set mode(newMode) {
-    SwitchAccess.instance.mode_ = newMode;
+  static improvedTextInputEnabled(): boolean {
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    return Flags.isEnabled(FlagName.SWITCH_ACCESS_TEXT)!;
   }
 
   /**
    * Helper function to robustly find a node fitting a given FindParams, even if
    * that node has not yet been created.
    * Used to find the menu and back button.
-   * @param {!FindParams} findParams
-   * @param {!function(!AutomationNode): void} foundCallback
    */
-  static findNodeMatching(findParams, foundCallback) {
+  static findNodeMatching(
+      findParams: FindParams,
+      foundCallback: (node: AutomationNode) => void): void {
     const desktop = Navigator.byItem.desktopNode;
     // First, check if the node is currently in the tree.
     let node = desktop.find(findParams);
@@ -89,9 +77,9 @@
     // If it's not currently in the tree, listen for changes to the desktop
     // tree.
     const eventHandler = new EventHandler(
-        desktop, EventType.CHILDREN_CHANGED, null /** callback */);
+        desktop, EventType.CHILDREN_CHANGED, (_evt: AutomationEvent) => {});
 
-    const onEvent = event => {
+    const onEvent = (event: AutomationEvent): void => {
       if (event.target.matches(findParams)) {
         // If the event target is the node we're looking for, we've found it.
         eventHandler.stop();
@@ -110,30 +98,23 @@
     eventHandler.start();
   }
 
-  /**
-   * Creates and records the specified error.
-   * @param {ErrorType} errorType
-   * @param {string} errorString
-   * @param {boolean} shouldRecover
-   * @return {!Error}
-   */
-  static error(errorType, errorString, shouldRecover = false) {
+  /** Creates and records the specified error. */
+  static error(
+      errorType: ErrorType, errorString: string,
+      shouldRecover = false): Error {
     if (shouldRecover) {
       setTimeout(Navigator.byItem.moveToValidNode.bind(Navigator.byItem), 0);
     }
     const errorTypeCountForUMA = Object.keys(ErrorType).length;
     chrome.metricsPrivate.recordEnumerationValue(
-        'Accessibility.CrosSwitchAccess.Error',
-        /** @type {number} */ (errorType), errorTypeCountForUMA);
+        'Accessibility.CrosSwitchAccess.Error', errorType,
+        errorTypeCountForUMA);
     return new Error(errorString);
   }
 
-  /**
-   * @param {!AutomationNode} desktop
-   * @param {AutomationNode} currentFocus
-   * @private
-   */
-  async waitForFocus_(desktop, currentFocus) {
+  private async waitForFocus_(
+      desktop: AutomationNode,
+      currentFocus: AutomationNode | undefined): Promise<void> {
     return new Promise(resolve => {
       // Focus is available. Finish init without waiting for further events.
       // Disallow web view nodes, which indicate a root web area is still
@@ -147,7 +128,7 @@
       // guaranteed. Otherwise, also set a timed callback to ensure we do
       // eventually init.
       let callbackId = 0;
-      const listener = maybeEvent => {
+      const listener = (maybeEvent: AutomationEvent | undefined): void => {
         if (maybeEvent && maybeEvent.target.role === RoleType.WEB_VIEW) {
           return;
         }
@@ -162,7 +143,4 @@
       callbackId = setTimeout(listener, 5000);
     });
   }
-}
-
-/** @type {SwitchAccess} */
-SwitchAccess.instance;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.html b/chrome/browser/resources/settings/privacy_page/cookies_page.html
index 6cfc37b8..abd8a53 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.html
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.html
@@ -204,7 +204,7 @@
         </settings-radio-group>
       </div>
     </template>
-    <template is="dom-if" if="[[is3pcdRedesignEnabled_]">
+    <template is="dom-if" if="[[is3pcdRedesignEnabled_]]">
       <div id="explanationText" class="secondary">
         $i18n{trackingProtectionPageDescription}
       </div>
@@ -230,7 +230,7 @@
         </div>
       </div>
     </template>
-    <template is="dom-if" if="[[isCookiesUiV2_]">
+    <template is="dom-if" if="[[isCookiesUiV2_]]">
       <h2 id="advancedHeader">$i18n{trackingProtectionAdvancedLabel}</h2>
       <settings-toggle-button
           id="blockThirdPartyToggle"
diff --git a/chrome/browser/sessions/session_service_unittest.cc b/chrome/browser/sessions/session_service_unittest.cc
index 0c42a45..7713cce 100644
--- a/chrome/browser/sessions/session_service_unittest.cc
+++ b/chrome/browser/sessions/session_service_unittest.cc
@@ -1459,7 +1459,18 @@
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
-TEST_F(SessionServiceTest, OpenedWindowNotRestoredInKiosk) {
+class SessionServiceKioskTest : public SessionServiceTest {
+ public:
+  void LogIn(const std::string& email) override {
+    chromeos::SetUpFakeKioskSession(email);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    ash_test_helper()->test_session_controller_client()->AddUserSession(
+        email, user_manager::UserType::kKioskApp);
+#endif
+  }
+};
+
+TEST_F(SessionServiceTest, OpenedWindowNotRestored) {
   // These preparation is necessary for `ShouldRestore` function to return true
   // in the regular user session.
   helper_.SetHasOpenTrackableBrowsers(false);
@@ -1467,8 +1478,13 @@
   service()->WindowClosed(window_id);
   // Make sure `ShouldRestore` returns true for the regular user session.
   EXPECT_TRUE(session_service_->ShouldRestore(browser()));
+}
 
-  chromeos::SetUpFakeKioskSession();
+TEST_F(SessionServiceKioskTest, OpenedWindowNotRestored) {
+  helper_.SetHasOpenTrackableBrowsers(false);
+  service()->WindowClosing(window_id);
+  service()->WindowClosed(window_id);
+  // Make sure `ShouldRestore` returns true for the kiosk user session.
   EXPECT_FALSE(session_service_->ShouldRestore(browser()));
 }
 #endif  //  BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
index 15fb707..6a0dc14 100644
--- a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
+++ b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
@@ -397,7 +397,8 @@
   ASSERT_TRUE(subframe_main_host);
   content::RenderFrameHost* embedder_host = ChildFrameAt(subframe_main_host, 0);
   ASSERT_TRUE(embedder_host);
-  GetTestPdfViewerStreamManager()->WaitUntilPdfLoaded(embedder_host);
+  ASSERT_TRUE(
+      GetTestPdfViewerStreamManager()->WaitUntilPdfLoaded(embedder_host));
 
   // The primary main frame shouldn't be the PDF embedder and shouldn't have a
   // PDF stream.
diff --git a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
index 6887b98b..6a439028 100644
--- a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
+++ b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
@@ -1521,7 +1521,8 @@
 
   void WaitUntilPdfLoaded(content::RenderFrameHost* embedder_host) {
     if (UseOopif()) {
-      GetTestPdfViewerStreamManager()->WaitUntilPdfLoaded(embedder_host);
+      ASSERT_TRUE(
+          GetTestPdfViewerStreamManager()->WaitUntilPdfLoaded(embedder_host));
     } else {
       auto* guest_view =
           GetTestGuestViewManager()->WaitForSingleGuestViewCreated();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 82003ffa..6436e06f 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2990,6 +2990,8 @@
       "views/select_file_dialog_extension.h",
       "views/select_file_dialog_extension_factory.cc",
       "views/select_file_dialog_extension_factory.h",
+      "webui/app_management/app_management_page_handler_chromeos.cc",
+      "webui/app_management/app_management_page_handler_chromeos.h",
       "webui/app_management/app_management_shelf_delegate_chromeos.cc",
       "webui/app_management/app_management_shelf_delegate_chromeos.h",
       "webui/ash/account_manager/account_manager_error_ui.cc",
@@ -4168,6 +4170,8 @@
       "webui/app_home/app_home_page_handler.h",
       "webui/app_home/app_home_ui.cc",
       "webui/app_home/app_home_ui.h",
+      "webui/app_management/web_app_settings_page_handler.cc",
+      "webui/app_management/web_app_settings_page_handler.h",
       "webui/app_settings/web_app_settings_navigation_throttle.cc",
       "webui/app_settings/web_app_settings_navigation_throttle.h",
       "webui/app_settings/web_app_settings_ui.cc",
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
index 6214abbb..28792e9 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
@@ -28,7 +28,8 @@
             "use_toolbar_bg_color_for_strip_transition_scrim";
 
     /**
-     * Whether the toolbar bg color will be used as the scrim overlay during a tab strip transition.
+     * Whether the toolbar bg color will be used as the scrim overlay on the tab strip / status bar
+     * during a tab strip transition.
      */
     public static final BooleanCachedFieldTrialParameter
             USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM =
@@ -58,4 +59,12 @@
     public static boolean shouldRecordSuppressionMetrics() {
         return ChromeFeatureList.sRecordSuppressionMetrics.isEnabled();
     }
+
+    /**
+     * @return Whether the toolbar bg color will be used as the scrim overlay on the tab strip and
+     *     status bar during a tab strip transition.
+     */
+    public static boolean shouldUseToolbarBgColorForStripTransitionScrim() {
+        return USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.getValue();
+    }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
index 6d36a61..fc6d875 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
@@ -533,6 +533,13 @@
         mLocationBar.getTabletCoordinator().tintBackground(textBoxColor);
         mLocationBar.updateVisualsForState();
         setToolbarHairlineColor(color);
+
+        // Notify the StatusBarColorController of the toolbar color change. This is to match the
+        // status bar's color with the toolbar color when the tab strip is hidden on a tablet when
+        // DYNAMIC_TOP_CHROME is enabled.
+        if (ToolbarFeatures.shouldUseToolbarBgColorForStripTransitionScrim()) {
+            notifyToolbarColorChanged(color);
+        }
     }
 
     /** Called when the currently visible New Tab Page changes. */
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
index e1fd15a..11059ea41 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
@@ -16,6 +16,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.Activity;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.view.View;
@@ -55,10 +56,12 @@
 import org.chromium.chrome.browser.toolbar.ButtonData.ButtonSpec;
 import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
 import org.chromium.chrome.browser.toolbar.R;
+import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarAllowCaptureReason;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
+import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarColorObserver;
 import org.chromium.ui.widget.ToastManager;
 
 import java.util.ArrayList;
@@ -74,6 +77,7 @@
     @Mock private StatusCoordinator mStatusCoordinator;
     @Mock private MenuButtonCoordinator mMenuButtonCoordinator;
     @Mock private TabStripTransitionCoordinator mTabStripTransitionCoordinator;
+    @Mock private ToolbarColorObserver mToolbarColorObserver;
     private Activity mActivity;
     private ToolbarTablet mToolbarTablet;
     private LinearLayout mToolbarTabletLayout;
@@ -102,6 +106,7 @@
         locationBarLayout.setStatusCoordinatorForTesting(mStatusCoordinator);
         mToolbarTablet.setMenuButtonCoordinatorForTesting(mMenuButtonCoordinator);
         mToolbarTablet.setTabStripTransitionCoordinator(mTabStripTransitionCoordinator);
+        mToolbarTablet.setToolbarColorObserver(mToolbarColorObserver);
         mToolbarTabletLayout =
                 (LinearLayout) mToolbarTablet.findViewById(R.id.toolbar_tablet_layout);
         mHomeButton = mToolbarTablet.findViewById(R.id.home_button);
@@ -588,6 +593,16 @@
         Assert.assertEquals("Long press callback not triggered.", 1, callback.getCallCount());
     }
 
+    @Test
+    public void testThemeColorChange() {
+        ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.setForTesting(true);
+        int color = Color.BLACK;
+        mToolbarTablet.onThemeColorChanged(color, false);
+        // Verify that ToolbarColorObserver is notified of the color change.
+        verify(mToolbarColorObserver).onToolbarColorChanged(color);
+        ToolbarFeatures.USE_TOOLBAR_BG_COLOR_FOR_STRIP_TRANSITION_SCRIM.setForTesting(false);
+    }
+
     private void longClickAndVerifyToast(int viewId, int stringId) {
         mToolbarTablet.onLongClick(mToolbarTablet.findViewById(viewId));
         assertTrue(
diff --git a/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc b/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
index bdbf178f..dd97bf15 100644
--- a/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/calendar/calendar_keyed_service_unittest.cc
@@ -11,13 +11,12 @@
 #include "ash/shell.h"
 #include "base/test/bind.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/calendar/calendar_keyed_service_factory.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
 #include "components/sync_preferences/pref_service_syncable.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "google_apis/common/api_error_codes.h"
 #include "google_apis/common/dummy_auth_service.h"
 #include "google_apis/common/test_util.h"
@@ -45,41 +44,22 @@
       delete;
   ~CalendarKeyedServiceTest() override = default;
 
+  void SetUp() override {
+    ProfileHelper::SetProfileToUserForTestingEnabled(true);
+    BrowserWithTestWindowTest::SetUp();
+  }
+
+  void TearDown() override {
+    BrowserWithTestWindowTest::TearDown();
+    ProfileHelper::SetProfileToUserForTestingEnabled(false);
+  }
+
   std::string GetDefaultProfileName() override { return kPrimaryProfileName; }
 
-  void LogIn(const std::string& email) override {
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-    GetSessionControllerClient()->AddUserSession(email);
-    GetSessionControllerClient()->SwitchActiveUser(account_id);
-  }
-
   TestingProfile* CreateSecondaryProfile() {
-    const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName));
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-    return profile_manager()->CreateTestingProfile(
-        kSecondaryProfileName,
-        std::unique_ptr<sync_preferences::PrefServiceSyncable>(),
-        u"Test profile",
-        /*avatar_id=*/1,
-        /*testing_factories=*/{});
+    LogIn(kSecondaryProfileName);
+    return CreateProfile(kSecondaryProfileName);
   }
-
-  void ActivateSecondaryProfile() {
-    const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName));
-    GetSessionControllerClient()->AddUserSession(kSecondaryProfileName);
-    GetSessionControllerClient()->SwitchActiveUser(account_id);
-  }
-
-  TestSessionControllerClient* GetSessionControllerClient() {
-    return ash_test_helper()->test_session_controller_client();
-  }
-
- private:
-  user_manager::TypedScopedUserManager<FakeChromeUserManager>
-      fake_user_manager_{std::make_unique<FakeChromeUserManager>()};
 };
 
 class CalendarKeyedServiceIOTest : public testing::Test {
@@ -197,7 +177,7 @@
 
   // Switching the active user should change the active client (multi-user
   // support).
-  ActivateSecondaryProfile();
+  SwitchActiveUser(kSecondaryProfileName);
   EXPECT_EQ(ash::Shell::Get()->calendar_controller()->GetClient(),
             secondary_calendar_service->client());
 }
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 c689a35a..adb1075 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
@@ -367,7 +367,8 @@
       ash::switches::kForceRefreshRateThrottle);
 
   if (auto* picker_controller = ash::Shell::Get()->picker_controller()) {
-    picker_client_ = std::make_unique<PickerClientImpl>(picker_controller);
+    picker_client_ = std::make_unique<PickerClientImpl>(
+        picker_controller, user_manager::UserManager::Get());
   }
 
   oobe_dialog_util_ = std::make_unique<ash::OobeDialogUtilImpl>();
diff --git a/chrome/browser/ui/ash/clipboard_history_url_title_fetcher_impl_unittest.cc b/chrome/browser/ui/ash/clipboard_history_url_title_fetcher_impl_unittest.cc
index dd2886e..22f34e5 100644
--- a/chrome/browser/ui/ash/clipboard_history_url_title_fetcher_impl_unittest.cc
+++ b/chrome/browser/ui/ash/clipboard_history_url_title_fetcher_impl_unittest.cc
@@ -12,12 +12,11 @@
 #include "base/task/cancelable_task_tracker.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_future.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/history/core/browser/history_service.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -81,24 +80,15 @@
 
 class ClipboardHistoryUrlTitleFetcherTest : public BrowserWithTestWindowTest {
  public:
-  ClipboardHistoryUrlTitleFetcherTest() {
-    auto fake_user_manager = std::make_unique<FakeChromeUserManager>();
-    fake_user_manager_ = fake_user_manager.get();
-    user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(fake_user_manager));
-  }
+  ClipboardHistoryUrlTitleFetcherTest() = default;
 
  protected:
   void CreateAndSwitchToSecondaryProfile() {
     const std::string kSecondaryProfileName = "secondary_profile@test";
-    const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName));
-
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-    fake_user_manager_->SwitchActiveUser(account_id);
-
-    profile_manager()->CreateTestingProfile(kSecondaryProfileName,
-                                            GetTestingFactories());
+    LogIn(kSecondaryProfileName);
+    CreateProfile(kSecondaryProfileName);
+    user_manager()->SwitchActiveUser(
+        AccountId::FromUserEmail(kSecondaryProfileName));
   }
 
   ClipboardHistoryUrlTitleFetcherImpl& fetcher() { return fetcher_; }
@@ -106,11 +96,17 @@
 
  private:
   // BrowserWithTestWindowTest:
+  void SetUp() override {
+    ash::ProfileHelper::SetProfileToUserForTestingEnabled(true);
+    BrowserWithTestWindowTest::SetUp();
+  }
+
   void TearDown() override {
     // Reset `history_service_` so that it does not dangle after the keyed
     // service is destroyed during `BrowserWithTestWindowTest::TearDown()`.
     history_service_ = nullptr;
     BrowserWithTestWindowTest::TearDown();
+    ash::ProfileHelper::SetProfileToUserForTestingEnabled(false);
   }
 
   TestingProfile::TestingFactories GetTestingFactories() override {
@@ -120,13 +116,6 @@
                  base::Unretained(this))}};
   }
 
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-  }
-
   std::unique_ptr<KeyedService> BuildHistoryService(
       content::BrowserContext* context) {
     auto history_service =
@@ -136,9 +125,6 @@
   }
 
   ClipboardHistoryUrlTitleFetcherImpl fetcher_;
-  std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
-  // Owned by, and therefore must be cleaned up before, `user_manager_enabler_`.
-  raw_ptr<FakeChromeUserManager> fake_user_manager_ = nullptr;
   // Owned by `KeyedServiceFactory`. Must be cleaned up before `TearDown()`.
   raw_ptr<MockHistoryService> history_service_ = nullptr;
 };
diff --git a/chrome/browser/ui/ash/faster_split_screen_browsertest.cc b/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
index 1349ec4d..b3b2837 100644
--- a/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
+++ b/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
@@ -8,6 +8,7 @@
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/overview_test_util.h"
+#include "ash/wm/splitview/faster_split_view.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
 #include "base/test/scoped_feature_list.h"
@@ -78,11 +79,9 @@
       ash::OverviewController::Get()->overview_session()->GetGridWithRootWindow(
           window->GetRootWindow());
   ASSERT_TRUE(overview_grid);
-  auto* faster_splitview_widget =
-      overview_grid->faster_splitview_widget_for_testing();
-  ASSERT_TRUE(faster_splitview_widget);
-  auto* settings_button = views::AsViewClass<ash::IconButton>(
-      faster_splitview_widget->GetContentsView()->children()[1]);
+  auto* faster_split_view = overview_grid->GetFasterSplitView();
+  ASSERT_TRUE(faster_split_view);
+  auto* settings_button = faster_split_view->settings_button();
   ASSERT_TRUE(settings_button);
 
   // Setup navigation observer to wait for the OS Settings page.
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory_unittest.cc
index d362e8c..0337308 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory_unittest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory_unittest.cc
@@ -9,52 +9,26 @@
 
 #include "ash/constants/ash_features.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ui/ash/glanceables/glanceables_keyed_service.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
 
 class GlanceablesKeyedServiceFactoryTest : public BrowserWithTestWindowTest {
  public:
-  GlanceablesKeyedServiceFactoryTest()
-      : scoped_user_manager_(std::make_unique<FakeChromeUserManager>()) {}
-
-  void LogIn(const std::string& email) override {
-    const auto account_id = AccountId::FromUserEmail(email);
-    fake_chrome_user_manager()->AddUser(account_id);
-    fake_chrome_user_manager()->LoginUser(account_id);
-    session_controller_client()->AddUserSession(email);
-    session_controller_client()->SwitchActiveUser(account_id);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
-    return profile_manager()->CreateTestingProfile(profile_name,
-                                                   /*is_main_profile=*/true);
-  }
-
-  // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-  FakeChromeUserManager* fake_chrome_user_manager() {
-    return static_cast<FakeChromeUserManager*>(
-        user_manager::UserManager::Get());
-  }
-
-  TestSessionControllerClient* session_controller_client() {
-    return ash_test_helper()->test_session_controller_client();
-  }
-
-  TestingProfileManager* profile_manager() {
-    return BrowserWithTestWindowTest::profile_manager();
+    auto* profile =
+        profile_manager()->CreateTestingProfile(profile_name,
+                                                /*is_main_profile=*/true);
+    OnUserProfileCreated(profile_name, profile);
+    return profile;
   }
 
  protected:
   base::test::ScopedFeatureList feature_list_{features::kGlanceablesV2};
-  user_manager::ScopedUserManager scoped_user_manager_;
 };
 
 TEST_F(GlanceablesKeyedServiceFactoryTest, NoSupportWhenFeatureIsDisabled) {
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
index 6617579bf..9dc0f85f4 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_keyed_service_unittest.cc
@@ -34,36 +34,21 @@
 
 class GlanceablesKeyedServiceTest : public BrowserWithTestWindowTest {
  public:
-  GlanceablesKeyedServiceTest()
-      : scoped_user_manager_(std::make_unique<FakeChromeUserManager>()) {}
-
   // BrowserWithTestWindowTest:
   std::string GetDefaultProfileName() override { return kPrimaryProfileName; }
 
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-    const auto account_id = AccountId::FromUserEmail(email);
-    fake_chrome_user_manager()->AddUser(account_id);
-    fake_chrome_user_manager()->LoginUser(account_id);
-    session_controller_client()->AddUserSession(email);
-    session_controller_client()->SwitchActiveUser(account_id);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto prefs =
         std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
     RegisterUserProfilePrefs(prefs->registry());
     profile_prefs_ = prefs.get();
-    return profile_manager()->CreateTestingProfile(
+    auto* profile = profile_manager()->CreateTestingProfile(
         profile_name, std::move(prefs), u"Test profile", /*avatar_id=*/0,
         TestingProfile::TestingFactories(), /*is_supervised_profile=*/false,
         /*is_new_profile=*/std::nullopt, /*policy_service=*/std::nullopt,
         /*is_main_profile=*/true);
-  }
-
-  FakeChromeUserManager* fake_chrome_user_manager() {
-    return static_cast<FakeChromeUserManager*>(
-        user_manager::UserManager::Get());
+    OnUserProfileCreated(profile_name, profile);
+    return profile;
   }
 
   TestSessionControllerClient* session_controller_client() {
@@ -76,7 +61,6 @@
   // the profile.
   raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged>
       profile_prefs_ = nullptr;
-  user_manager::ScopedUserManager scoped_user_manager_;
 };
 
 TEST_F(GlanceablesKeyedServiceTest, RegistersClientsInAsh) {
@@ -104,15 +88,15 @@
   const auto first_account_id = AccountId::FromUserEmail(kPrimaryProfileName);
   const auto second_account_id =
       AccountId::FromUserEmail(kSecondaryProfileName);
-  fake_chrome_user_manager()->AddUser(second_account_id);
-  fake_chrome_user_manager()->LoginUser(second_account_id);
+  LogIn(kSecondaryProfileName);
   auto* secondary_profile =
       profile_manager()->CreateTestingProfile(kSecondaryProfileName,
                                               /*is_main_profile=*/false);
-  session_controller_client()->AddUserSession(kSecondaryProfileName);
+  OnUserProfileCreated(kSecondaryProfileName, secondary_profile);
+
+  SwitchActiveUser(kSecondaryProfileName);
   auto service_secondary =
       std::make_unique<GlanceablesKeyedService>(secondary_profile);
-  session_controller_client()->SwitchActiveUser(second_account_id);
 
   auto* const classroom_client_secondary = controller->GetClassroomClient();
   auto* const tasks_client_secondary = controller->GetTasksClient();
@@ -121,7 +105,7 @@
   EXPECT_NE(classroom_client_primary, classroom_client_secondary);
   EXPECT_NE(tasks_client_primary, tasks_client_secondary);
 
-  session_controller_client()->SwitchActiveUser(first_account_id);
+  SwitchActiveUser(kPrimaryProfileName);
   EXPECT_EQ(classroom_client_primary, controller->GetClassroomClient());
   EXPECT_EQ(tasks_client_primary, controller->GetTasksClient());
 }
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index 211c5424..cc347ac 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "ash/components/arc/session/arc_service_manager.h"
 #include "ash/constants/ash_features.h"
+#include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/holding_space/holding_space_client.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_controller.h"
@@ -43,7 +44,7 @@
 #include "chrome/browser/ash/file_suggest/file_suggest_test_util.h"
 #include "chrome/browser/ash/file_suggest/file_suggest_util.h"
 #include "chrome/browser/ash/file_suggest/mock_file_suggest_keyed_service.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
 #include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
@@ -63,7 +64,7 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/sync_preferences/pref_service_mock_factory.h"
 #include "components/sync_preferences/pref_service_syncable.h"
-#include "components/user_manager/scoped_user_manager.h"
+#include "components/user_manager/user_names.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/test/fake_download_item.h"
 #include "content/public/test/mock_download_manager.h"
@@ -543,9 +544,7 @@
  public:
   HoldingSpaceKeyedServiceTest()
       : BrowserWithTestWindowTest(
-            base::test::TaskEnvironment::TimeSource::MOCK_TIME),
-        fake_user_manager_(new FakeChromeUserManager),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
     HoldingSpaceImage::SetUseZeroInvalidationDelayForTesting(true);
   }
 
@@ -559,6 +558,8 @@
 
   // BrowserWithTestWindowTest:
   void SetUp() override {
+    ash::ProfileHelper::SetProfileToUserForTestingEnabled(true);
+
     // The test's task environment starts with a mock time close to the Unix
     // epoch, but the files that back holding space items are created with
     // accurate timestamps. Advance the clock so that the test's mock time and
@@ -582,6 +583,8 @@
   void TearDown() override {
     BrowserWithTestWindowTest::TearDown();
     disks::DiskMountManager::Shutdown();
+
+    ash::ProfileHelper::SetProfileToUserForTestingEnabled(false);
   }
 
   TestingProfile::TestingFactories GetTestingFactories() override {
@@ -595,15 +598,6 @@
                  temp_dir_.GetPath())}};
   }
 
-  // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-  void LogIn(const std::string& email) override {
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-    GetSessionControllerClient()->AddUserSession(email);
-    GetSessionControllerClient()->SwitchActiveUser(account_id);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto* profile = BrowserWithTestWindowTest::CreateProfile(profile_name);
     SetUpDownloadManager(profile);
@@ -612,14 +606,12 @@
 
   TestingProfile* CreateSecondaryProfile(
       std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs = nullptr) {
-    const std::string kSecondaryProfileName = "secondary_profile";
-    const AccountId account_id(AccountId::FromUserEmail(kSecondaryProfileName));
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->LoginUser(account_id);
-    TestingProfile* profile = profile_manager()->CreateTestingProfile(
-        kSecondaryProfileName, std::move(prefs), u"Test profile",
-        1 /*avatar_id*/, GetTestingFactories());
-    SetUpDownloadManager(profile);
+    constexpr char kSecondaryProfileName[] = "secondary_profile";
+    LogIn(kSecondaryProfileName);
+    auto* profile = profile_manager()->CreateTestingProfile(
+        kSecondaryProfileName, std::move(prefs), /*user_name=*/std::u16string(),
+        /*avatar_id=*/0, GetTestingFactories());
+    OnUserProfileCreated(kSecondaryProfileName, profile);
     return profile;
   }
 
@@ -734,8 +726,6 @@
   }
 
  private:
-  raw_ptr<FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
   std::map<Profile*, testing::NiceMock<MockDownloadManager>*>
       download_managers_;
   arc::ArcServiceManager arc_service_manager_;
@@ -769,28 +759,90 @@
     All,
     HoldingSpaceKeyedServiceWithExperimentalFeatureTest,
     testing::Combine(/*enable_camera_app_integration=*/testing::Bool(),
-                     /*enable_predictability*/ testing::Bool(),
+                     /*enable_predictability=*/testing::Bool(),
                      /*enabled_suggestions=*/testing::Bool()));
 
-TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureTest, GuestUserProfile) {
-  // Construct a guest session profile.
-  TestingProfile::Builder guest_profile_builder;
-  guest_profile_builder.SetGuestSession();
-  guest_profile_builder.SetProfileName("guest_profile");
-  guest_profile_builder.AddTestingFactories(
-      {{arc::ArcFileSystemBridge::GetFactory(),
-        base::BindRepeating(&BuildArcFileSystemBridge)},
-       {file_manager::VolumeManagerFactory::GetInstance(),
-        base::BindRepeating(&BuildVolumeManager)}});
-  std::unique_ptr<TestingProfile> guest_profile = guest_profile_builder.Build();
+class HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest
+    : public HoldingSpaceKeyedServiceWithExperimentalFeatureTest {
+ public:
+  HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest() {
+    // To let ProfileHelper::GetUserByProfile() directly return
+    // the created guest user, without faking directory paths.
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        ash::switches::kIgnoreUserProfileMappingForTests);
+  }
+
+  void TearDown() override {
+    profile_.reset();
+    HoldingSpaceKeyedServiceWithExperimentalFeatureTest::TearDown();
+  }
+
+  std::string GetDefaultProfileName() override {
+    return user_manager::kGuestUserName;
+  }
+
+  void LogIn(const std::string& email) override {
+    CHECK_EQ(email, user_manager::kGuestUserName);
+    auto account_id = user_manager::GuestAccountId();
+
+    user_manager()->AddGuestUser(account_id);
+    user_manager()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
+  }
+
+  TestingProfile* CreateProfile(const std::string& profile_name) override {
+    CHECK_EQ(profile_name, user_manager::kGuestUserName);
+    CHECK(!profile_);
+
+    // Construct a guest session profile.
+    // Profile is created outside of TestingProfileManager management
+    // to inject more factories.
+    TestingProfile::Builder guest_profile_builder;
+    guest_profile_builder.SetGuestSession();
+    guest_profile_builder.SetProfileName(profile_name);
+    guest_profile_builder.AddTestingFactories(
+        {{arc::ArcFileSystemBridge::GetFactory(),
+          base::BindRepeating(&BuildArcFileSystemBridge)},
+         {file_manager::VolumeManagerFactory::GetInstance(),
+          base::BindRepeating(&BuildVolumeManager)}});
+    profile_ = guest_profile_builder.Build();
+    OnUserProfileCreated(profile_name, profile_.get());
+    return profile_.get();
+  }
+
+  std::unique_ptr<Browser> CreateBrowser(
+      Profile* profile,
+      Browser::Type browser_type,
+      bool hosted_app,
+      BrowserWindow* browser_window) override {
+    // Do not create browser.
+    return nullptr;
+  }
+
+ private:
+  std::unique_ptr<TestingProfile> profile_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest,
+    testing::Combine(/*enable_camera_app_integration=*/testing::Bool(),
+                     /*enable_predictability=*/testing::Bool(),
+                     /*enabled_suggestions=*/testing::Bool()));
+
+TEST_P(HoldingSpaceKeyedServiceWithExperimentalFeatureForGuestTest,
+       GuestUserProfile) {
+  auto* guest_profile = profile();
 
   // Service instances should be created for guest sessions but note that the
   // service factory will redirect to use the primary OTR profile.
   ASSERT_TRUE(guest_profile);
   ASSERT_FALSE(guest_profile->IsOffTheRecord());
   HoldingSpaceKeyedService* const guest_profile_service =
-      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
-          guest_profile.get());
+      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(guest_profile);
   ASSERT_TRUE(guest_profile_service);
 
   // Since the service factory redirects to use the primary OTR profile in the
@@ -810,7 +862,7 @@
       guest_profile->GetProfileUserName());
   TestingProfile* const secondary_otr_guest_profile =
       secondary_otr_guest_profile_builder.BuildOffTheRecord(
-          guest_profile.get(), Profile::OTRProfileID::CreateUniqueForTesting());
+          guest_profile, Profile::OTRProfileID::CreateUniqueForTesting());
   ASSERT_TRUE(secondary_otr_guest_profile);
   ASSERT_TRUE(secondary_otr_guest_profile->IsOffTheRecord());
 
diff --git a/chrome/browser/ui/ash/picker/picker_client_impl.cc b/chrome/browser/ui/ash/picker/picker_client_impl.cc
index b09899c..6b0f3c8 100644
--- a/chrome/browser/ui/ash/picker/picker_client_impl.cc
+++ b/chrome/browser/ui/ash/picker/picker_client_impl.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/ash/picker/picker_client_impl.h"
 
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <utility>
@@ -14,8 +15,14 @@
 #include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/notimplemented.h"
+#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h"
+#include "chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h"
 #include "chrome/browser/ash/app_list/search/search_engine.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/ash_web_view_impl.h"
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
@@ -24,6 +31,8 @@
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/simple_url_loader.h"
+#include "ui/base/page_transition_types.h"
+#include "ui/base/window_open_disposition.h"
 #include "url/url_constants.h"
 
 namespace ash {
@@ -60,21 +69,19 @@
 
 }  // namespace
 
-PickerClientImpl::PickerClientImpl(ash::PickerController* controller)
+PickerClientImpl::PickerClientImpl(ash::PickerController* controller,
+                                   user_manager::UserManager* user_manager)
     : controller_(controller) {
   controller_->SetClient(this);
 
-  auto* user_manager = user_manager::UserManager::Get();
   // As `PickerClientImpl` is initialised in
   // `ChromeBrowserMainExtraPartsAsh::PostProfileInit`, the user manager does
   // not notify us of the first user "change".
   ActiveUserChanged(user_manager->GetActiveUser());
-  user_manager->AddSessionStateObserver(this);
+  user_session_state_observation_.Observe(user_manager);
 }
 
 PickerClientImpl::~PickerClientImpl() {
-  user_manager::UserManager::Get()->RemoveSessionStateObserver(this);
-
   controller_->SetClient(nullptr);
 }
 
@@ -166,4 +173,77 @@
   profile_ = profile;
 
   search_engine_ = std::make_unique<app_list::SearchEngine>(profile_);
+  if (crosapi::browser_util::IsLacrosEnabled()) {
+    search_engine_->AddProvider(
+        std::make_unique<app_list::OmniboxLacrosProvider>(
+            profile_, &app_list_controller_delegate_,
+            crosapi::CrosapiManager::Get()));
+  } else {
+    search_engine_->AddProvider(std::make_unique<app_list::OmniboxProvider>(
+        profile_, &app_list_controller_delegate_));
+  }
+}
+
+PickerClientImpl::PickerAppListControllerDelegate::
+    PickerAppListControllerDelegate() = default;
+PickerClientImpl::PickerAppListControllerDelegate::
+    ~PickerAppListControllerDelegate() = default;
+
+void PickerClientImpl::PickerAppListControllerDelegate::DismissView() {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+aura::Window*
+PickerClientImpl::PickerAppListControllerDelegate::GetAppListWindow() {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return nullptr;
+}
+
+int64_t
+PickerClientImpl::PickerAppListControllerDelegate::GetAppListDisplayId() {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return 0;
+}
+
+bool PickerClientImpl::PickerAppListControllerDelegate::IsAppPinned(
+    const std::string& app_id) {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return false;
+}
+
+bool PickerClientImpl::PickerAppListControllerDelegate::IsAppOpen(
+    const std::string& app_id) const {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return false;
+}
+
+void PickerClientImpl::PickerAppListControllerDelegate::PinApp(
+    const std::string& app_id) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+void PickerClientImpl::PickerAppListControllerDelegate::UnpinApp(
+    const std::string& app_id) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+AppListControllerDelegate::Pinnable
+PickerClientImpl::PickerAppListControllerDelegate::GetPinnable(
+    const std::string& app_id) {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return AppListControllerDelegate::NO_PIN;
+}
+
+void PickerClientImpl::PickerAppListControllerDelegate::CreateNewWindow(
+    bool incognito,
+    bool should_trigger_session_restore) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+void PickerClientImpl::PickerAppListControllerDelegate::OpenURL(
+    Profile* profile,
+    const GURL& url,
+    ui::PageTransition transition,
+    WindowOpenDisposition disposition) {
+  NOTIMPLEMENTED_LOG_ONCE();
 }
diff --git a/chrome/browser/ui/ash/picker/picker_client_impl.h b/chrome/browser/ui/ash/picker/picker_client_impl.h
index ccac318..505a7b9 100644
--- a/chrome/browser/ui/ash/picker/picker_client_impl.h
+++ b/chrome/browser/ui/ash/picker/picker_client_impl.h
@@ -5,12 +5,17 @@
 #ifndef CHROME_BROWSER_UI_ASH_PICKER_PICKER_CLIENT_IMPL_H_
 #define CHROME_BROWSER_UI_ASH_PICKER_PICKER_CLIENT_IMPL_H_
 
+#include <cstdint>
 #include <memory>
 #include <string>
 
 #include "ash/public/cpp/picker/picker_client.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
+#include "ui/base/page_transition_types.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
 
 class Profile;
 
@@ -22,6 +27,10 @@
 class PickerController;
 }
 
+namespace aura {
+class Window;
+}
+
 namespace user_manager {
 class User;
 }
@@ -33,7 +42,9 @@
  public:
   // Sets this instance as the client of `controller`.
   // Automatically unsets the client when this instance is destroyed.
-  explicit PickerClientImpl(ash::PickerController* controller);
+  // `manager` needs to outlive this class.
+  explicit PickerClientImpl(ash::PickerController* controller,
+                            user_manager::UserManager* user_manager);
   PickerClientImpl(const PickerClientImpl&) = delete;
   PickerClientImpl& operator=(const PickerClientImpl&) = delete;
   ~PickerClientImpl() override;
@@ -50,6 +61,29 @@
   void ActiveUserChanged(user_manager::User* active_user) override;
 
  private:
+  // Implements `AppListControllerDelegate` with empty methods. Used only for
+  // constructing search engine providers.
+  class PickerAppListControllerDelegate : public AppListControllerDelegate {
+   public:
+    PickerAppListControllerDelegate();
+    ~PickerAppListControllerDelegate() override;
+
+    // AppListControllerDelegate overrides:
+    void DismissView() override;
+    aura::Window* GetAppListWindow() override;
+    int64_t GetAppListDisplayId() override;
+    bool IsAppPinned(const std::string& app_id) override;
+    bool IsAppOpen(const std::string& app_id) const override;
+    void PinApp(const std::string& app_id) override;
+    void UnpinApp(const std::string& app_id) override;
+    Pinnable GetPinnable(const std::string& app_id) override;
+    void CreateNewWindow(bool incognito,
+                         bool should_trigger_session_restore) override;
+    void OpenURL(Profile* profile,
+                 const GURL& url,
+                 ui::PageTransition transition,
+                 WindowOpenDisposition disposition) override;
+  };
   void SetProfileByUser(const user_manager::User* user);
   void SetProfile(Profile* profile);
 
@@ -57,6 +91,11 @@
   raw_ptr<Profile> profile_ = nullptr;
 
   std::unique_ptr<app_list::SearchEngine> search_engine_;
+  PickerAppListControllerDelegate app_list_controller_delegate_;
+
+  base::ScopedObservation<user_manager::UserManager,
+                          user_manager::UserManager::UserSessionStateObserver>
+      user_session_state_observation_{this};
 
   base::WeakPtrFactory<PickerClientImpl> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/ash/picker/picker_client_impl_unittest.cc b/chrome/browser/ui/ash/picker/picker_client_impl_unittest.cc
new file mode 100644
index 0000000..f104914
--- /dev/null
+++ b/chrome/browser/ui/ash/picker/picker_client_impl_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2024 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/ash/picker/picker_client_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "ash/picker/picker_controller.h"
+#include "base/functional/bind.h"
+#include "base/test/test_future.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/test/test_shared_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct TestServerSetup {
+  std::unique_ptr<net::test_server::EmbeddedTestServer> server;
+  net::test_server::EmbeddedTestServerHandle handle;
+};
+
+TestServerSetup CreateAndStartTestServer(
+    net::test_server::EmbeddedTestServer::HandleRequestCallback
+        handle_request_callback) {
+  auto test_server = std::make_unique<net::test_server::EmbeddedTestServer>();
+  test_server->RegisterRequestHandler(std::move(handle_request_callback));
+  net::test_server::EmbeddedTestServerHandle handle =
+      test_server->StartAndReturnHandle();
+  return {
+      .server = std::move(test_server),
+      .handle = std::move(handle),
+  };
+}
+
+class PickerClientImplTest : public testing::Test {
+ public:
+  PickerClientImplTest()
+      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
+        test_shared_url_loader_factory_(
+            base::MakeRefCounted<network::TestSharedURLLoaderFactory>()),
+        fake_user_manager_(std::make_unique<user_manager::FakeUserManager>()),
+        testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+
+  void SetUp() override { ASSERT_TRUE(testing_profile_manager_.SetUp()); }
+  void TearDown() override {
+    for (const user_manager::User* user : fake_user_manager_->GetUsers()) {
+      fake_user_manager_->OnUserProfileWillBeDestroyed(user->GetAccountId());
+    }
+  }
+
+  // Returns the user manager used in this test, logged into a fake user.
+  user_manager::UserManager* GetUserManagerLoggedInAsFakeUser() {
+    AccountId account_id = AccountId::FromUserEmail("test@test");
+
+    const user_manager::User* user = fake_user_manager_->AddUser(account_id);
+    fake_user_manager_->UserLoggedIn(account_id, user->username_hash(),
+                                     /*browser_restart=*/false,
+                                     /*is_child=*/false);
+    TestingProfile* profile = CreateTestingProfileForAccount(account_id);
+    fake_user_manager_->OnUserProfileCreated(account_id, profile->GetPrefs());
+    return fake_user_manager_.Get();
+  }
+
+ private:
+  TestingProfile* CreateTestingProfileForAccount(const AccountId& account_id) {
+    return testing_profile_manager_.CreateTestingProfile(
+        account_id.GetUserEmail(), /*is_main_profile=*/false,
+        test_shared_url_loader_factory_);
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  scoped_refptr<network::SharedURLLoaderFactory>
+      test_shared_url_loader_factory_;
+  // Keep `fake_user_manager_` before `testing_profile_manager_` to match
+  // destruction order in production:
+  // https://crsrc.org/c/chrome/browser/ash/chrome_browser_main_parts_ash.cc;l=1668;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c
+  // https://crsrc.org/c/chrome/browser/ash/chrome_browser_main_parts_ash.cc;l=1719;drc=c7da8fba0e20c71d61e5c78ecd6a3872c4c56e6c
+  user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
+      fake_user_manager_;
+  TestingProfileManager testing_profile_manager_;
+};
+
+TEST_F(PickerClientImplTest, DownloadGifReturnsGifOnSuccess) {
+  ash::PickerController controller;
+  PickerClientImpl client(&controller, GetUserManagerLoggedInAsFakeUser());
+  TestServerSetup server_setup = CreateAndStartTestServer(base::BindRepeating(
+      [](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        auto http_response =
+            std::make_unique<net::test_server::BasicHttpResponse>();
+        http_response->set_code(net::HTTP_OK);
+        http_response->set_content("hello");
+        http_response->set_content_type("text/plain");
+        return http_response;
+      }));
+
+  base::test::TestFuture<const std::string&> future;
+  client.DownloadGifToString(ash::ValidGifUrl::CreateForTesting(
+                                 server_setup.server->GetURL("/test.gif")),
+                             future.GetCallback());
+
+  EXPECT_EQ(future.Get(), "hello");
+}
+
+TEST_F(PickerClientImplTest, DownloadGifReturnsEmptyOnFailure) {
+  ash::PickerController controller;
+  PickerClientImpl client(&controller, GetUserManagerLoggedInAsFakeUser());
+  TestServerSetup server_setup = CreateAndStartTestServer(base::BindRepeating(
+      [](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        auto http_response =
+            std::make_unique<net::test_server::BasicHttpResponse>();
+        http_response->set_code(net::HTTP_NOT_FOUND);
+        return http_response;
+      }));
+
+  base::test::TestFuture<const std::string&> future;
+  client.DownloadGifToString(ash::ValidGifUrl::CreateForTesting(
+                                 server_setup.server->GetURL("/test.gif")),
+                             future.GetCallback());
+
+  EXPECT_EQ(future.Get(), "");
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/ash/session_controller_client_impl.cc b/chrome/browser/ui/ash/session_controller_client_impl.cc
index f37a4fd..a9edff1 100644
--- a/chrome/browser/ui/ash/session_controller_client_impl.cc
+++ b/chrome/browser/ui/ash/session_controller_client_impl.cc
@@ -444,8 +444,8 @@
 
   if (static_cast<ash::ChromeUserManager*>(user_manager)
           ->GetMultiProfileUserController()
-          ->GetPrimaryUserPolicy() !=
-      ash::MultiProfileUserController::ALLOWED) {
+          ->GetPrimaryUserPolicy() ==
+      user_manager::MultiUserSignInPolicy::kNotAllowed) {
     return ash::AddUserSessionPolicy::ERROR_NOT_ALLOWED_PRIMARY_USER;
   }
 
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 379f73d..70ce411 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -93,7 +93,6 @@
 #include "chrome/browser/ash/eche_app/app_id.h"
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/login/demo_mode/demo_mode_test_helper.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/system_web_apps/apps/camera_app/camera_system_web_app_info.h"
 #include "chrome/browser/ash/system_web_apps/apps/os_flags_system_web_app_info.h"
@@ -148,6 +147,7 @@
 #include "chrome/test/base/test_browser_window_aura.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/standalone_browser/feature_refs.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -177,8 +177,6 @@
 #include "components/sync/test/test_sync_service.h"
 #include "components/sync_preferences/pref_model_associator.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "components/user_manager/fake_user_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "components/viz/test/test_gpu_service_holder.h"
 #include "components/webapps/common/web_app_id.h"
 #include "content/public/browser/web_contents.h"
@@ -610,17 +608,6 @@
       StartWebAppProvider(profile());
   }
 
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-    AccountId account_id = AccountId::FromUserEmail(email);
-    user_manager()->AddUser(account_id);
-    user_manager()->UserLoggedIn(
-        account_id,
-        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
-        /*browser_restart=*/false,
-        /*is_child=*/false);
-  }
-
   virtual bool StartWebAppProviderForMainProfile() const { return true; }
 
   void StartWebAppProvider(Profile* profile) {
@@ -1653,8 +1640,9 @@
     return CreateProfile(email);
   }
 
-  // Switch to another user.
-  void SwitchActiveUser(const AccountId& account_id) {
+  // Switch to another user by AccountId.
+  // TODO(b/40286020): Migrate into SwitchActiveUser().
+  void SwitchActiveUserByAccountId(const AccountId& account_id) {
     user_manager()->SwitchActiveUser(account_id);
     ash::MultiUserWindowManagerImpl::Get()->SetAnimationSpeedForTest(
         ash::MultiUserWindowManagerImpl::ANIMATION_SPEED_DISABLED);
@@ -2858,7 +2846,7 @@
   EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
 
   shelf_controller_->SetProfileForTest(profile2);
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
 
   EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
   EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
@@ -2873,7 +2861,7 @@
   arc_test_.app_instance()->SendTaskDestroyed(2);
 
   shelf_controller_->SetProfileForTest(profile());
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
 
   EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
   EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
@@ -3340,11 +3328,11 @@
         multi_user_util::GetAccountIdFromProfile(profile2));
     const AccountId account_id(
         multi_user_util::GetAccountIdFromProfile(profile()));
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
 
     // After switching back the item should be back.
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     EXPECT_EQ(2, model_->item_count());
     // Note we destroy now the gmail app with the closure end.
   }
@@ -3371,19 +3359,19 @@
     EXPECT_EQ(1, model_->item_count());
 
     // However - switching to the user should show it.
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(2, model_->item_count());
 
     // Second test: Remove the app when the user is not active and see that it
     // works.
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     EXPECT_EQ(1, model_->item_count());
     // Note: the closure ends and the browser will go away.
   }
   EXPECT_EQ(1, model_->item_count());
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(1, model_->item_count());
 }
 
@@ -3405,11 +3393,11 @@
     std::unique_ptr<V1App> v1_app(CreateRunningV1App(
         profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
     EXPECT_EQ(2, model_->item_count());
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
   }
   // After the app was destroyed, switch back. (which caused already a crash).
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
 
   // Create the same app again - which was also causing the crash.
   EXPECT_EQ(1, model_->item_count());
@@ -3419,7 +3407,7 @@
         profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
     EXPECT_EQ(2, model_->item_count());
   }
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
 }
 
@@ -3436,7 +3424,7 @@
       multi_user_util::GetAccountIdFromProfile(profile()));
   const AccountId account_id2(
       multi_user_util::GetAccountIdFromProfile(profile2));
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   {
     // Create a "windowed gmail app".
     std::unique_ptr<V1App> v1_app(
@@ -3444,19 +3432,19 @@
     EXPECT_EQ(1, model_->item_count());
 
     // However - switching to the user should show it.
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     EXPECT_EQ(2, model_->item_count());
 
     // Second test: Remove the app when the user is not active and see that it
     // works.
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
     v1_app.reset();
   }
   EXPECT_EQ(1, model_->item_count());
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(1, model_->item_count());
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
 }
 
@@ -3535,7 +3523,7 @@
 
   // Switch to the secondary user, and verify the app only installed in the
   // primary profile is removed from the model.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
 
   EXPECT_EQ(std::vector<std::string>({app_constants::kChromeAppId}),
             GetAppsShownInShelf());
@@ -4177,7 +4165,7 @@
 
   // Switch to the other user and make sure that only that browser window gets
   // shown.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item2);
 
   // Transferred browsers of other users should not show up in the list.
@@ -4288,7 +4276,7 @@
   TestingProfile* profile2 = CreateMultiUserProfile(user2);
   const AccountId account_id2(
       multi_user_util::GetAccountIdFromProfile(profile2));
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
 
   // No item should have content yet.
   CheckAppMenu(shelf_controller_.get(), item_browser, 0, nullptr);
@@ -4323,11 +4311,11 @@
       multi_user_util::GetAccountIdFromProfile(profile2));
 
   // After switching users the item should go away.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
 
   // And it should come back when switching back.
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(2, model_->item_count());
 }
 
@@ -4347,7 +4335,7 @@
   EXPECT_EQ(1, model_->item_count());
 
   // Switch to an inactive user.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
 
   // Add the v2 app to the inactive user and check that no item was added to
@@ -4358,12 +4346,12 @@
     EXPECT_EQ(1, model_->item_count());
 
     // Switch to the primary user and check that the item is shown.
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     EXPECT_EQ(2, model_->item_count());
 
     // Switch to the second user and check that the item goes away - even if the
     // item gets closed.
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
   }
 
@@ -4372,7 +4360,7 @@
 
   // Switching then back to the default user should not show the additional
   // item anymore.
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(1, model_->item_count());
 }
 
@@ -4457,14 +4445,14 @@
           base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
   extension_service1->Init();
 
-  SwitchActiveUser(account_id1);
+  SwitchActiveUserByAccountId(account_id1);
 
   // A v2 app for user #1 should be shown first and get hidden when switching
   // to desktop #2.
   extension_service1->AddExtension(extension1_.get());
   V2App v2_app_1(profile1, extension1_.get());
   EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
 
   // Add a v2 app for user #1 while on desktop #2 should not be shown.
@@ -4487,7 +4475,7 @@
 
   // Switching back to desktop#1 and creating an app for user #1 should move
   // the app on desktop #1.
-  SwitchActiveUser(account_id1);
+  SwitchActiveUserByAccountId(account_id1);
   V2App v2_app_4(profile1, extension1_.get());
   EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
   EXPECT_TRUE(v2_app_2.window()->GetNativeWindow()->IsVisible());
@@ -4496,15 +4484,15 @@
 
   // Switching to desktop #3 and creating an app for user #1 should place it
   // on that user's desktop (#1).
-  SwitchActiveUser(account_id3);
+  SwitchActiveUserByAccountId(account_id3);
   V2App v2_app_5(profile1, extension1_.get());
   EXPECT_FALSE(v2_app_5.window()->GetNativeWindow()->IsVisible());
-  SwitchActiveUser(account_id1);
+  SwitchActiveUserByAccountId(account_id1);
   EXPECT_TRUE(v2_app_5.window()->GetNativeWindow()->IsVisible());
 
   // Switching to desktop #2, hiding the app window and creating an app should
   // teleport there automatically.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   v2_app_1.window()->Hide();
   V2App v2_app_6(profile1, extension1_.get());
   EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
@@ -4524,11 +4512,11 @@
   // If switch to account_id2 is not run, the following switch to account_id
   // is invalid, because the user account is not changed, so switch to
   // account_id2 first.
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
 
   const AccountId account_id(
       multi_user_util::GetAccountIdFromProfile(profile()));
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(1, model_->item_count());
 
   AddExtension(extension1_.get());
@@ -4544,7 +4532,7 @@
   }
   {
     // Switch user, hide and show the app and switch back.
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
 
     v2_app_1.window()->Hide();
@@ -4553,18 +4541,18 @@
     v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
     EXPECT_EQ(1, model_->item_count());
 
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     EXPECT_EQ(2, model_->item_count());
   }
   {
     // Switch user, hide the app, switch back and then show it again.
-    SwitchActiveUser(account_id2);
+    SwitchActiveUserByAccountId(account_id2);
     EXPECT_EQ(1, model_->item_count());
 
     v2_app_1.window()->Hide();
     EXPECT_EQ(1, model_->item_count());
 
-    SwitchActiveUser(account_id);
+    SwitchActiveUserByAccountId(account_id);
     // The following expectation does not work in current impl. It was working
     // before because MultiProfileSupport is not attached to user associated
     // with profile() hence not actually handling windows for the user. It is
@@ -4616,12 +4604,12 @@
   EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
 
   // Switch to a new profile
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_EQ(1, model_->item_count());
   EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
 
   // Switch back
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_EQ(2, model_->item_count());
   EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
 
@@ -4664,13 +4652,13 @@
   EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
 
   // Switch to a new profile
-  SwitchActiveUser(account_id2);
+  SwitchActiveUserByAccountId(account_id2);
   EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
   EXPECT_EQ(1, model_->item_count());
   EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
 
   // Switch back
-  SwitchActiveUser(account_id);
+  SwitchActiveUserByAccountId(account_id);
   EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
   EXPECT_EQ(2, model_->item_count());
   EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
diff --git a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
index b436aa7..4b49779 100644
--- a/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
+++ b/chrome/browser/ui/ash/user_education/chrome_user_education_delegate_unittest.cc
@@ -81,10 +81,6 @@
 // Base class for tests of the `ChromeUserEducationDelegate`.
 class ChromeUserEducationDelegateTest : public BrowserWithTestWindowTest {
  public:
-  ChromeUserEducationDelegateTest()
-      : user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(user_manager_.get())) {}
-
   // Returns the `AccountId` for the primary `profile()`.
   const AccountId& account_id() const {
     return ash::BrowserContextHelper::Get()
@@ -105,23 +101,6 @@
     delegate_ = std::make_unique<ChromeUserEducationDelegate>();
   }
 
-  // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-  void LogIn(const std::string& email) override {
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    // Register user.
-    user_manager_->AddUser(account_id);
-    user_manager_->LoginUser(account_id);
-
-    // Activate session.
-    auto* client = ash_test_helper()->test_session_controller_client();
-    client->AddUserSession(email);
-    client->SwitchActiveUser(account_id);
-  }
-
-  // User management.
-  const raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-
   // The delegate instance under test.
   std::unique_ptr<ChromeUserEducationDelegate> delegate_;
 };
diff --git a/chrome/browser/ui/browser_command_controller_unittest.cc b/chrome/browser/ui/browser_command_controller_unittest.cc
index c25d56c..d781eef1 100644
--- a/chrome/browser/ui/browser_command_controller_unittest.cc
+++ b/chrome/browser/ui/browser_command_controller_unittest.cc
@@ -220,25 +220,18 @@
   chrome::BrowserCommandController command_controller(browser());
   const CommandUpdater* command_updater = &command_controller;
 
-  bool enabled = true;
-  size_t profiles_count = 1U;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
   // Chrome OS uses system tray menu to handle multi-profiles.
-  enabled = false;
-  profiles_count = 2U;
-#endif
+  bool enabled = !BUILDFLAG(IS_CHROMEOS_ASH);
 
-  ASSERT_EQ(profiles_count, profile_manager->GetNumberOfProfiles());
+  ASSERT_EQ(1u, profile_manager->GetNumberOfProfiles());
   EXPECT_EQ(enabled, command_updater->IsCommandEnabled(IDC_SHOW_AVATAR_MENU));
 
   testing_profile_manager->CreateTestingProfile("p2");
-  profiles_count++;
-  ASSERT_EQ(profiles_count, profile_manager->GetNumberOfProfiles());
+  ASSERT_EQ(2u, profile_manager->GetNumberOfProfiles());
   EXPECT_EQ(enabled, command_updater->IsCommandEnabled(IDC_SHOW_AVATAR_MENU));
 
   testing_profile_manager->DeleteTestingProfile("p2");
-  profiles_count--;
-  ASSERT_EQ(profiles_count, profile_manager->GetNumberOfProfiles());
+  ASSERT_EQ(1u, profile_manager->GetNumberOfProfiles());
   EXPECT_EQ(enabled, command_updater->IsCommandEnabled(IDC_SHOW_AVATAR_MENU));
 }
 
diff --git a/chrome/browser/ui/browser_finder_chromeos_unittest.cc b/chrome/browser/ui/browser_finder_chromeos_unittest.cc
index ab0cfc95..bc31a8ce 100644
--- a/chrome/browser/ui/browser_finder_chromeos_unittest.cc
+++ b/chrome/browser/ui/browser_finder_chromeos_unittest.cc
@@ -7,7 +7,6 @@
 #include "ash/public/cpp/multi_user_window_manager.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/multi_user/multi_profile_support.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
@@ -15,7 +14,6 @@
 #include "chrome/test/base/test_browser_window_aura.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/account_id/account_id.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user.h"
 #include "ui/base/ui_base_features.h"
 
@@ -30,10 +28,7 @@
 
 class BrowserFinderChromeOSTest : public BrowserWithTestWindowTest {
  protected:
-  BrowserFinderChromeOSTest()
-      : fake_user_manager_(new ash::FakeChromeUserManager),
-        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {}
-
+  BrowserFinderChromeOSTest() = default;
   BrowserFinderChromeOSTest(const BrowserFinderChromeOSTest&) = delete;
   BrowserFinderChromeOSTest& operator=(const BrowserFinderChromeOSTest&) =
       delete;
@@ -64,20 +59,9 @@
   // BrowserWithTestWindow:
   std::string GetDefaultProfileName() override { return kTestAccount1; }
 
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    fake_user_manager_->AddUser(account_id);
-    fake_user_manager_->UserLoggedIn(
-        account_id,
-        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
-        /*browser_restart=*/false,
-        /*is_child=*/false);
-  }
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto* profile = BrowserWithTestWindowTest::CreateProfile(profile_name);
-    auto* user = fake_user_manager_->FindUserAndModify(
+    auto* user = user_manager()->FindUserAndModify(
         AccountId::FromUserEmail(profile_name));
     ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user, profile);
     // Force creation of MultiProfileSupport.
@@ -87,10 +71,6 @@
   }
 
   raw_ptr<TestingProfile> second_profile_;
-
-  // |fake_user_manager_| is owned by |user_manager_enabler_|
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
 };
 
 TEST_F(BrowserFinderChromeOSTest, IncognitoBrowserMatchTest) {
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index 8b5fc77..7109bcc 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -33,6 +33,8 @@
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h"
+#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
+#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_io_data.h"
 #include "chrome/browser/sessions/app_session_service.h"
@@ -243,6 +245,7 @@
     profile_ = browser->profile();
 
   if (!browser || !browser->is_type_normal()) {
+    CHECK(profile_);
     // In some conditions a new browser object cannot be created. The most
     // common reason for not being able to create browser is having this call
     // when the browser process is shutting down. This can also fail if the
@@ -269,12 +272,12 @@
 
     browser = Browser::Create(params);
   }
+  CHECK(profile_);
 
   bool first_tab = true;
   bool process_headless_commands = headless::ShouldProcessHeadlessCommands();
   custom_handlers::ProtocolHandlerRegistry* registry =
-      profile_ ? ProtocolHandlerRegistryFactory::GetForBrowserContext(profile_)
-               : nullptr;
+      ProtocolHandlerRegistryFactory::GetForBrowserContext(profile_);
   for (auto& tab : tabs) {
     // We skip URLs that we'd have to launch an external protocol handler for.
     // This avoids us getting into an infinite loop asking ourselves to open
@@ -300,10 +303,13 @@
     // Headless mode is restricted to only one url in the command line, so
     // just grab the first one assuming it's the target.
     if (first_tab && process_headless_commands) {
+      auto profile_keepalive = std::make_unique<ScopedProfileKeepAlive>(
+          profile_, ProfileKeepAliveOrigin::kHeadlessCommand);
       headless::ProcessHeadlessCommands(
           profile_, tab.url,
           base::BindOnce(
               [](base::WeakPtr<Browser> browser,
+                 std::unique_ptr<ScopedProfileKeepAlive> profile_keepalive,
                  headless::HeadlessCommandHandler::Result result) {
                 if (browser && browser->window()) {
 #if BUILDFLAG(IS_MAC)
@@ -316,7 +322,7 @@
                   browser->window()->Close();
                 }
               },
-              browser->AsWeakPtr()));
+              browser->AsWeakPtr(), std::move(profile_keepalive)));
       continue;
     }
 
diff --git a/chrome/browser/ui/tab_contents/chrome_web_contents_menu_helper_unittest.cc b/chrome/browser/ui/tab_contents/chrome_web_contents_menu_helper_unittest.cc
index 4a54b2c3..e419cbe 100644
--- a/chrome/browser/ui/tab_contents/chrome_web_contents_menu_helper_unittest.cc
+++ b/chrome/browser/ui/tab_contents/chrome_web_contents_menu_helper_unittest.cc
@@ -35,9 +35,13 @@
     RegisterUserProfilePrefs(prefs->registry());
     pref_service_ = prefs.get();
 
-    return profile_manager()->CreateTestingProfile(
+    auto* profile = profile_manager()->CreateTestingProfile(
         profile_name, std::move(prefs), std::u16string(), 0,
         TestingProfile::TestingFactories());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    OnUserProfileCreated(profile_name, profile);
+#endif
+    return profile;
   }
 
   sync_preferences::PrefServiceSyncable* pref_service() {
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
index 8aedd4f..66baa44 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
@@ -88,8 +88,7 @@
 }  // namespace
 
 class AppInfoDialogViewsTest : public BrowserWithTestWindowTest,
-                               public views::WidgetObserver,
-                               public ProfileObserver {
+                               public views::WidgetObserver {
  public:
   AppInfoDialogViewsTest() = default;
 
@@ -149,50 +148,12 @@
     BrowserWithTestWindowTest::TearDown();
   }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // BrowserWithTestWindowTest:
-  void LogIn(const std::string& email) override {
-    // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-    AccountId account_id = AccountId::FromUserEmail(email);
-    CHECK(user_manager());
-    user_manager()->AddUser(account_id);
-    user_manager()->UserLoggedIn(
-        account_id,
-        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
-        /*browser_restart=*/false,
-        /*is_child=*/false);
-  }
-#endif
-
   TestingProfile* CreateProfile(const std::string& profile_name) override {
     auto* profile = BrowserWithTestWindowTest::CreateProfile(profile_name);
-    // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    auto account_id = AccountId::FromUserEmail(profile_name);
-    ash::AnnotatedAccountId::Set(profile, account_id);
-    user_manager()->OnUserProfileCreated(account_id, profile->GetPrefs());
-    auto observation =
-        std::make_unique<base::ScopedObservation<Profile, ProfileObserver>>(
-            this);
-    observation->Observe(profile);
-    profile_observations_.push_back(std::move(observation));
-#endif
     extension_environment_.SetProfile(profile);
     return profile;
   }
 
-  // TODO(crbug.com/1494005): merge into BrowserWithTestWindowTest.
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  void OnProfileWillBeDestroyed(Profile* profile) override {
-    CHECK(base::EraseIf(profile_observations_, [profile](auto& observation) {
-      return observation->IsObservingSource(profile);
-    }));
-    const auto* account_id = ash::AnnotatedAccountId::Get(profile);
-    CHECK(account_id);
-    user_manager()->OnUserProfileWillBeDestroyed(*account_id);
-  }
-#endif
-
  protected:
   void ShowAppInfo(const std::string& app_id) {
     ShowAppInfoForProfile(app_id, extension_environment_.profile());
@@ -249,9 +210,6 @@
 #endif
   };
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  std::vector<
-      std::unique_ptr<base::ScopedObservation<Profile, ProfileObserver>>>
-      profile_observations_;
   std::unique_ptr<ash::ShelfModel> shelf_model_;
   std::unique_ptr<ChromeShelfController> chrome_shelf_controller_;
   std::unique_ptr<ArcAppTest> arc_test_;
diff --git a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
index 2473bd6..ecbd729 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
@@ -25,7 +25,9 @@
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/omnibox/browser/vector_icons.h"
+#include "ui/color/color_id.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/views/style/typography.h"
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #include "components/plus_addresses/resources/vector_icons.h"
 #endif
@@ -518,19 +520,31 @@
   int non_primary_text_style = ShouldApplyNewAutofillPopupStyle()
                                    ? views::style::TextStyle::STYLE_BODY_3
                                    : views::style::TextStyle::STYLE_PRIMARY;
-  return std::make_unique<views::Label>(
+  auto label = std::make_unique<views::Label>(
       main_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
       main_text.is_primary ? primary_text_style : non_primary_text_style);
+
+  if (!main_text.is_primary && ShouldApplyNewAutofillPopupStyle()) {
+    label->SetEnabledColorId(ui::kColorLabelForegroundSecondary);
+  }
+
+  return label;
 }
 
 // Creates a label for the suggestion's minor text.
 std::unique_ptr<views::Label> CreateMinorTextLabel(
     const Suggestion::Text& minor_text) {
-  return minor_text.value.empty()
-             ? nullptr
-             : std::make_unique<views::Label>(
-                   minor_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
-                   GetSecondaryTextStyle());
+  if (minor_text.value.empty()) {
+    return nullptr;
+  }
+
+  auto label = std::make_unique<views::Label>(
+      minor_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT,
+      GetSecondaryTextStyle());
+  if (ShouldApplyNewAutofillPopupStyle()) {
+    label->SetEnabledColorId(ui::kColorLabelForegroundSecondary);
+  }
+  return label;
 }
 
 int GetMaxPopupAddressProfileWidth() {
@@ -572,6 +586,9 @@
               label_text.value,
               ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL,
               text_style ? *text_style : GetSecondaryTextStyle()));
+      if (ShouldApplyNewAutofillPopupStyle()) {
+        label->SetEnabledColorId(ui::kColorLabelForegroundSecondary);
+      }
       content_view.TrackLabel(label);
       // TODO(crbug.com/1459990): Remove feature check as part of the clean up.
       if (!base::FeatureList::IsEnabled(
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_content_view.cc b/chrome/browser/ui/views/autofill/popup/popup_row_content_view.cc
index 4cce5ac..815dc96 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_content_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_content_view.cc
@@ -42,19 +42,21 @@
     SetBackground(nullptr);
   }
 
-  // Set style for each label in this cell depending on its current selection
-  // state.
-  for (views::Label* label : tracked_labels_) {
-    label->SetAutoColorReadabilityEnabled(false);
+  if (!ShouldApplyNewAutofillPopupStyle()) {
+    // Set style for each label in this cell depending on its current selection
+    // state.
+    for (views::Label* label : tracked_labels_) {
+      label->SetAutoColorReadabilityEnabled(false);
 
-    // If the current suggestion is selected or the label is disabled,
-    // override the style. Otherwise, use the color that corresponds to the
-    // actual style of the label.
-    int style = label->GetEnabled() ? (selected ? views::style::STYLE_SELECTED
-                                                : label->GetTextStyle())
-                                    : views::style::STYLE_DISABLED;
-    label->SetEnabledColorId(views::TypographyProvider::Get().GetColorId(
-        label->GetTextContext(), style));
+      // If the current suggestion is selected or the label is disabled,
+      // override the style. Otherwise, use the color that corresponds to the
+      // actual style of the label.
+      int style = label->GetEnabled() ? (selected ? views::style::STYLE_SELECTED
+                                                  : label->GetTextStyle())
+                                      : views::style::STYLE_DISABLED;
+      label->SetEnabledColorId(views::TypographyProvider::Get().GetColorId(
+          label->GetTextContext(), style));
+    }
   }
 
   SchedulePaint();
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_content_view_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_row_content_view_unittest.cc
index 2d48ba7..08ffc1a2 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_content_view_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_content_view_unittest.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/color/color_id.h"
@@ -108,16 +109,19 @@
       untracked_label->GetEnabledColor(),
       get_expected_color(*untracked_label, untracked_label->GetTextStyle()));
 
-  // On select updates only the tracked label's style.
-  view().UpdateStyle(/*selected=*/true);
-  EXPECT_NE(
-      tracked_label->GetEnabledColor(),
-      get_expected_color(*tracked_label, untracked_label->GetTextStyle()));
-  EXPECT_EQ(tracked_label->GetEnabledColor(),
-            get_expected_color(*tracked_label, views::style::STYLE_SELECTED));
-  EXPECT_EQ(
-      untracked_label->GetEnabledColor(),
-      get_expected_color(*untracked_label, untracked_label->GetTextStyle()));
+  // The label styles don't get changed with selection for the new style.
+  if (!ShouldApplyNewAutofillPopupStyle()) {
+    // On select updates only the tracked label's style.
+    view().UpdateStyle(/*selected=*/true);
+    EXPECT_NE(
+        tracked_label->GetEnabledColor(),
+        get_expected_color(*tracked_label, untracked_label->GetTextStyle()));
+    EXPECT_EQ(tracked_label->GetEnabledColor(),
+              get_expected_color(*tracked_label, views::style::STYLE_SELECTED));
+    EXPECT_EQ(
+        untracked_label->GetEnabledColor(),
+        get_expected_color(*untracked_label, untracked_label->GetTextStyle()));
+  }
 }
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
index e6f02a5d..a63156a 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
@@ -105,6 +105,9 @@
           suggestion.main_text, ShouldApplyNewAutofillPopupStyle()
                                     ? views::style::TextStyle::STYLE_BODY_3
                                     : views::style::TextStyle::STYLE_SECONDARY);
+  if (ShouldApplyNewAutofillPopupStyle()) {
+    main_text_label->SetEnabledColorId(ui::kColorLabelForegroundSecondary);
+  }
   main_text_label->SetEnabled(!suggestion.is_loading);
   view->TrackLabel(view->AddChildView(std::move(main_text_label)));
 
diff --git a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_unittest.cc b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_unittest.cc
index 08d6a90..38e418bf 100644
--- a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_unittest.cc
+++ b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_unittest.cc
@@ -62,9 +62,7 @@
   base::WeakPtrFactory<MockHost> weak_ptr_factory_{this};
 };
 
-class TestBubbleContentsWrapper
-    : public BubbleContentsWrapper,
-      public base::SupportsWeakPtr<TestBubbleContentsWrapper> {
+class TestBubbleContentsWrapper final : public BubbleContentsWrapper {
  public:
   explicit TestBubbleContentsWrapper(Profile* profile)
       : BubbleContentsWrapper(GURL(""), profile, 0, true, true, "Test") {}
@@ -73,8 +71,11 @@
   // BubbleContentsWrapper:
   void ReloadWebContents() override {}
   base::WeakPtr<BubbleContentsWrapper> GetWeakPtr() override {
-    return AsWeakPtr();
+    return weak_ptr_factory_.GetWeakPtr();
   }
+
+ private:
+  base::WeakPtrFactory<TestBubbleContentsWrapper> weak_ptr_factory_{this};
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/bubble/webui_bubble_dialog_view_unittest.cc b/chrome/browser/ui/views/bubble/webui_bubble_dialog_view_unittest.cc
index fc929cd..6d65707 100644
--- a/chrome/browser/ui/views/bubble/webui_bubble_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/bubble/webui_bubble_dialog_view_unittest.cc
@@ -18,17 +18,18 @@
 #include "ui/views/widget/unique_widget_ptr.h"
 
 namespace {
-class TestBubbleContentsWrapper
-    : public BubbleContentsWrapper,
-      public base::SupportsWeakPtr<TestBubbleContentsWrapper> {
+class TestBubbleContentsWrapper final : public BubbleContentsWrapper {
  public:
   explicit TestBubbleContentsWrapper(Profile* profile)
       : BubbleContentsWrapper(GURL(""), profile, 0, true, true, "Test") {}
   void ReloadWebContents() override {}
 
   base::WeakPtr<BubbleContentsWrapper> GetWeakPtr() override {
-    return AsWeakPtr();
+    return weak_ptr_factory_.GetWeakPtr();
   }
+
+ private:
+  base::WeakPtrFactory<TestBubbleContentsWrapper> weak_ptr_factory_{this};
 };
 }  // namespace
 
diff --git a/chrome/browser/ui/views/bubble/webui_bubble_manager_browsertest.cc b/chrome/browser/ui/views/bubble/webui_bubble_manager_browsertest.cc
index 9bc09e3..6e5a0d7 100644
--- a/chrome/browser/ui/views/bubble/webui_bubble_manager_browsertest.cc
+++ b/chrome/browser/ui/views/bubble/webui_bubble_manager_browsertest.cc
@@ -51,10 +51,8 @@
 WEB_UI_CONTROLLER_TYPE_IMPL(TestWebUIController)
 
 template <>
-class BubbleContentsWrapperT<TestWebUIController>
-    : public BubbleContentsWrapper,
-      public base::SupportsWeakPtr<
-          BubbleContentsWrapperT<TestWebUIController>> {
+class BubbleContentsWrapperT<TestWebUIController> final
+    : public BubbleContentsWrapper {
  public:
   BubbleContentsWrapperT(const GURL& webui_url,
                          content::BrowserContext* browser_context,
@@ -69,8 +67,11 @@
                               "Test") {}
   void ReloadWebContents() override {}
   base::WeakPtr<BubbleContentsWrapper> GetWeakPtr() override {
-    return AsWeakPtr();
+    return weak_ptr_factory_.GetWeakPtr();
   }
+
+ private:
+  base::WeakPtrFactory<BubbleContentsWrapper> weak_ptr_factory_{this};
 };
 
 class WebUIBubbleManagerBrowserTest : public InProcessBrowserTest {
diff --git a/chrome/browser/ui/views/bubble/webui_bubble_manager_unittest.cc b/chrome/browser/ui/views/bubble/webui_bubble_manager_unittest.cc
index 0bfe8c3f..72b3431 100644
--- a/chrome/browser/ui/views/bubble/webui_bubble_manager_unittest.cc
+++ b/chrome/browser/ui/views/bubble/webui_bubble_manager_unittest.cc
@@ -28,10 +28,8 @@
 WEB_UI_CONTROLLER_TYPE_IMPL(TestWebUIController)
 
 template <>
-class BubbleContentsWrapperT<TestWebUIController>
-    : public BubbleContentsWrapper,
-      public base::SupportsWeakPtr<
-          BubbleContentsWrapperT<TestWebUIController>> {
+class BubbleContentsWrapperT<TestWebUIController> final
+    : public BubbleContentsWrapper {
  public:
   BubbleContentsWrapperT(const GURL& webui_url,
                          content::BrowserContext* browser_context,
@@ -46,8 +44,11 @@
                               "Test") {}
   void ReloadWebContents() override {}
   base::WeakPtr<BubbleContentsWrapper> GetWeakPtr() override {
-    return AsWeakPtr();
+    return weak_ptr_factory_.GetWeakPtr();
   }
+
+ private:
+  base::WeakPtrFactory<BubbleContentsWrapper> weak_ptr_factory_{this};
 };
 
 class WebUIBubbleManagerTest : public ChromeViewsTestBase {
diff --git a/chrome/browser/ui/views/page_info/page_info_cookies_content_view_unittest.cc b/chrome/browser/ui/views/page_info/page_info_cookies_content_view_unittest.cc
index c84ad6d1..da29f0c 100644
--- a/chrome/browser/ui/views/page_info/page_info_cookies_content_view_unittest.cc
+++ b/chrome/browser/ui/views/page_info/page_info_cookies_content_view_unittest.cc
@@ -77,12 +77,6 @@
     AddTab(browser(), url);
     auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    fake_user_manager_->AddUserWithAffiliation(
-        AccountId::FromUserEmail(profile()->GetProfileUserName()),
-        /*is_affiliated=*/true);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
     presenter_ = std::make_unique<PageInfo>(
         std::make_unique<ChromePageInfoDelegate>(web_contents), web_contents,
         url);
@@ -96,6 +90,19 @@
     TestWithBrowserView::TearDown();
   }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  void LogIn(const std::string& email) override {
+    const AccountId account_id = AccountId::FromUserEmail(email);
+    user_manager()->AddUserWithAffiliation(account_id, /*is_affiliated=*/true);
+    ash_test_helper()->test_session_controller_client()->AddUserSession(email);
+    user_manager()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
+  }
+#endif
+
   PageInfoCookiesContentView* content_view() { return content_view_.get(); }
 
   views::StyledLabel* third_party_cookies_description_label() {
@@ -146,11 +153,6 @@
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<PageInfo> presenter_;
   std::unique_ptr<PageInfoCookiesContentView> content_view_;
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_{std::make_unique<ash::FakeChromeUserManager>()};
-#endif
 };
 
 class PageInfoCookiesContentViewPre3pcdTest
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
index 06d6c66c..4b227d89f 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
@@ -26,11 +26,9 @@
 #include "ash/constants/ash_switches.h"
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/common/pref_names.h"
-#include "components/user_manager/scoped_user_manager.h"
 #endif
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS_ASH)
@@ -41,11 +39,6 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-constexpr char kFakeUserName[] = "test@example.com";
-constexpr char kFakeGaiaId[] = "1234567890";
-#endif
-
 const char kFirstTestFeatureId[] = "feature-1";
 BASE_FEATURE(kTestFeature1, "FeatureName1", base::FEATURE_ENABLED_BY_DEFAULT);
 const char kSecondTestFeatureId[] = "feature-2";
@@ -59,10 +52,6 @@
  public:
   ChromeLabsButtonTest()
       :
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-        user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(user_manager_.get())),
-#endif
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
         channel_override_(chrome::ScopedChannelOverride(
             chrome::ScopedChannelOverride::Channel::kDev)),
@@ -74,12 +63,6 @@
   {
   }
   void SetUp() override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    const AccountId account_id(
-        AccountId::FromUserEmailGaiaId(kFakeUserName, kFakeGaiaId));
-    user_manager_->AddUser(account_id);
-    user_manager_->LoginUser(account_id);
-#endif
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kChromeLabs,
         {{features::kChromeLabsActivationPercentage.name, "100"}});
@@ -92,12 +75,6 @@
         chrome_labs_prefs::kBrowserLabsEnabledEnterprisePolicy, true);
   }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
- protected:
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-#endif
-
  private:
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   chrome::ScopedChannelOverride channel_override_;
@@ -189,14 +166,11 @@
  public:
   ChromeLabsButtonTestSecondaryUser() : ChromeLabsButtonTest() {}
 
-  void SetUp() override {
-    // Set the email of |secondary_user| to
-    // |TestingProfile::kDefaultProfileUserName| so
-    // |ProfileHelperImpl::GetUserByProfile| returns this user.
-    AccountId secondary_user =
-        AccountId::FromUserEmail(TestingProfile::kDefaultProfileUserName);
-    user_manager_->AddUser(secondary_user);
-    ChromeLabsButtonTest::SetUp();
+  void LogIn(const std::string& email) override {
+    // Fake primary user log-in, so that the created profile will be interpreted
+    // as secondary user's profile.
+    ChromeLabsButtonTest::LogIn("primary-user@domain.com");
+    ChromeLabsButtonTest::LogIn(email);
   }
 };
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_unittest.cc
index c30d565..469455b 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_unittest.cc
@@ -39,13 +39,11 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/memory/ptr_util.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/settings/about_flags.h"
 #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
 #include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
-#include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_manager.h"
 #endif
 
@@ -57,11 +55,6 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-constexpr char kFakeUserName[] = "test@example.com";
-constexpr char kFakeGaiaId[] = "1234567890";
-#endif
-
 const char kFirstTestFeatureId[] = "feature-1";
 const char kTestFeatureWithVariationId[] = "feature-2";
 const char kThirdTestFeatureId[] = "feature-3";
@@ -107,10 +100,6 @@
   ChromeLabsCoordinatorTest()
       : TestWithBrowserView(
             base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME),
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-        user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(user_manager_.get())),
-#endif
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
         channel_override_(chrome::ScopedChannelOverride(
             chrome::ScopedChannelOverride::Channel::kDev)),
@@ -136,13 +125,6 @@
   }
 
   void SetUp() override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    const AccountId account_id(
-        AccountId::FromUserEmailGaiaId(kFakeUserName, kFakeGaiaId));
-    user_manager_->AddUser(account_id);
-    user_manager_->LoginUser(account_id);
-#endif
-
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kChromeLabs,
         {{features::kChromeLabsActivationPercentage.name, "100"}});
@@ -183,11 +165,6 @@
   std::unique_ptr<ChromeLabsCoordinator> chrome_labs_coordinator_;
 
  private:
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-#endif
-
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   chrome::ScopedChannelOverride channel_override_;
 #endif
@@ -252,10 +229,6 @@
   ChromeLabsViewControllerTest()
       : TestWithBrowserView(
             base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME),
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-        user_manager_(new ash::FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(user_manager_.get())),
-#endif
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
         channel_override_(chrome::ScopedChannelOverride(
             chrome::ScopedChannelOverride::Channel::kDev)),
@@ -281,13 +254,6 @@
   }
 
   void SetUp() override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    const AccountId account_id(
-        AccountId::FromUserEmailGaiaId(kFakeUserName, kFakeGaiaId));
-    user_manager_->AddUser(account_id);
-    user_manager_->LoginUser(account_id);
-#endif
-
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kChromeLabs,
         {{features::kChromeLabsActivationPercentage.name, "100"}});
@@ -400,10 +366,6 @@
   raw_ptr<views::Widget, DanglingUntriaged> bubble_widget_;
 
  private:
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-#endif
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   chrome::ScopedChannelOverride channel_override_;
 #endif
@@ -481,21 +443,10 @@
 
 class ChromeLabsAshFeatureTest : public ChromeLabsFeatureTest {
  public:
-  ChromeLabsAshFeatureTest()
-      : ChromeLabsFeatureTest(),
-        user_manager_(new FakeChromeUserManager()),
-        user_manager_enabler_(base::WrapUnique(user_manager_.get())) {
+  ChromeLabsAshFeatureTest() {
     SessionManagerClient::InitializeFakeInMemory();
     FakeSessionManagerClient::Get()->set_supports_browser_restart(true);
-    const AccountId account_id(
-        AccountId::FromUserEmailGaiaId(kFakeUserName, kFakeGaiaId));
-    user_manager_->AddUser(account_id);
-    user_manager_->LoginUser(account_id);
   }
-
- private:
-  raw_ptr<FakeChromeUserManager, DanglingUntriaged> user_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
 };
 
 TEST_P(ChromeLabsAshFeatureTest, ChangeSelectedOption) {
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index b3cb3ba8..36163ca 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -79,7 +79,6 @@
 #include "chrome/browser/ui/web_applications/web_app_dialogs.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
 #include "chrome/browser/ui/web_applications/web_app_menu_model.h"
-#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
 #include "chrome/browser/ui/webui/app_settings/web_app_settings_ui.h"
 #include "chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h"
 #include "chrome/browser/web_applications/app_service/web_app_publisher_helper.h"
@@ -157,6 +156,7 @@
 #else
 #include "chrome/browser/ui/webui/app_home/app_home.mojom.h"
 #include "chrome/browser/ui/webui/app_home/app_home_page_handler.h"
+#include "chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h"
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -698,14 +698,14 @@
 }
 
 #if !BUILDFLAG(IS_CHROMEOS)
-AppManagementPageHandlerBase CreateAppManagementPageHandler(Profile* profile) {
+WebAppSettingsPageHandler CreateAppManagementPageHandler(Profile* profile) {
   mojo::PendingReceiver<app_management::mojom::Page> page;
   mojo::Remote<app_management::mojom::PageHandler> handler;
   static auto delegate =
       WebAppSettingsUI::CreateAppManagementPageHandlerDelegate(profile);
-  return AppManagementPageHandlerBase(handler.BindNewPipeAndPassReceiver(),
-                                      page.InitWithNewPipeAndPassRemote(),
-                                      profile, *delegate);
+  return WebAppSettingsPageHandler(handler.BindNewPipeAndPassReceiver(),
+                                   page.InitWithNewPipeAndPassRemote(), profile,
+                                   *delegate);
 }
 #endif
 
@@ -1557,6 +1557,11 @@
   if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
   }
+  base::RunLoop run_loop;
+  WebAppProvider::GetForTest(profile())
+      ->policy_manager()
+      .SetRefreshPolicySettingsCompletedCallbackForTesting(
+          run_loop.QuitClosure());
   GURL url = GetUrlForSite(site);
   {
     ScopedListPrefUpdate update_list(profile()->GetPrefs(),
@@ -1565,6 +1570,7 @@
       return *item.GetDict().FindString(kManifestId) == url.spec();
     });
   }
+  run_loop.Run();
   AfterStateChangeAction();
 }
 
@@ -4185,6 +4191,11 @@
 
 void WebAppIntegrationTestDriver::ApplyRunOnOsLoginPolicy(Site site,
                                                           const char* policy) {
+  base::RunLoop run_loop;
+  WebAppProvider::GetForTest(profile())
+      ->policy_manager()
+      .SetRefreshPolicySettingsCompletedCallbackForTesting(
+          run_loop.QuitClosure());
   GURL url = GetUrlForSite(site);
   {
     ScopedListPrefUpdate update(profile()->GetPrefs(), prefs::kWebAppSettings);
@@ -4199,6 +4210,7 @@
 
     update_list.Append(std::move(dict_item));
   }
+  run_loop.Run();
 }
 
 void WebAppIntegrationTestDriver::UninstallPolicyAppById(
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
index 5343450..601b0d9 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
@@ -8,9 +8,9 @@
 #include <utility>
 #include <vector>
 
-#include "base/barrier_callback.h"
 #include "base/check.h"
 #include "base/functional/bind.h"
+#include "base/functional/concurrent_callbacks.h"
 #include "base/i18n/message_formatter.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/types/expected.h"
@@ -256,13 +256,11 @@
     int add_call_id,
     std::vector<SubAppInstallParams> requested_installs,
     webapps::ManifestId parent_manifest_id) {
-  const auto install_info_collector = base::BarrierCallback<
-      std::pair<webapps::ManifestId, std::unique_ptr<WebAppInstallInfo>>>(
-      requested_installs.size(),
-      base::BindOnce(&SubAppsServiceImpl::ProcessInstallData,
-                     weak_ptr_factory_.GetWeakPtr(), add_call_id));
-
   WebAppProvider* provider = GetWebAppProvider(render_frame_host());
+  base::ConcurrentCallbacks<
+      std::pair<webapps::ManifestId, std::unique_ptr<WebAppInstallInfo>>>
+      concurrent;
+
   // Schedule data collection for each requested install
   for (const auto& [manifest_id, url_to_load] : requested_installs) {
     // Check if app is the parent app itself
@@ -271,7 +269,6 @@
           .results.emplace_back(SubAppsServiceAddResult::New(
               ConvertUrlToPath(manifest_id),
               blink::mojom::SubAppsServiceResultCode::kFailure));
-      install_info_collector.Run(std::pair(GURL(), nullptr));
       continue;
     }
 
@@ -282,7 +279,6 @@
           .results.emplace_back(SubAppsServiceAddResult::New(
               ConvertUrlToPath(manifest_id),
               blink::mojom::SubAppsServiceResultCode::kSuccess));
-      install_info_collector.Run(std::pair(GURL(), nullptr));
       continue;
     }
 
@@ -294,8 +290,12 @@
               return std::pair(manifest_app_id, std::move(install_info));
             },
             manifest_id)
-            .Then(install_info_collector));
+            .Then(concurrent.CreateCallback()));
   }
+
+  std::move(concurrent)
+      .Done(base::BindOnce(&SubAppsServiceImpl::ProcessInstallData,
+                           weak_ptr_factory_.GetWeakPtr(), add_call_id));
 }
 
 void SubAppsServiceImpl::ProcessInstallData(
@@ -375,14 +375,9 @@
 void SubAppsServiceImpl::ScheduleSubAppInstalls(int add_call_id) {
   AddCallInfo& add_call_info = add_call_info_.at(add_call_id);
 
-  const auto install_results_collector =
-      base::BarrierCallback<SubAppInstallResult>(
-          add_call_info.install_infos.size(),
-          base::BindOnce(&SubAppsServiceImpl::FinishAddCall,
-                         weak_ptr_factory_.GetWeakPtr(), add_call_id));
-
   // Schedule install for each install_info that was collected
   WebAppProvider* provider = GetWebAppProvider(render_frame_host());
+  base::ConcurrentCallbacks<SubAppInstallResult> concurrent;
   for (auto& install_info : add_call_info.install_infos) {
     webapps::ManifestId manifest_id = install_info->manifest_id;
     provider->scheduler().InstallFromInfo(
@@ -394,8 +389,11 @@
               return SubAppInstallResult(manifest_id, app_id, result_code);
             },
             manifest_id)
-            .Then(install_results_collector));
+            .Then(concurrent.CreateCallback()));
   }
+  std::move(concurrent)
+      .Done(base::BindOnce(&SubAppsServiceImpl::FinishAddCall,
+                           weak_ptr_factory_.GetWeakPtr(), add_call_id));
 }
 
 void SubAppsServiceImpl::FinishAddCall(
@@ -468,17 +466,16 @@
     return std::move(result_callback).Run(std::move(result));
   }
 
-  auto remove_barrier_callback =
-      base::BarrierCallback<SubAppsServiceRemoveResultPtr>(
-          manifest_id_paths.size(),
-          base::BindOnce(&SubAppsServiceImpl::NotifyUninstall,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         std::move(result_callback)));
-
+  // Take weak pointer early as this may get deleted by RemoveSubApp().
+  base::WeakPtr<SubAppsServiceImpl> weak_ptr = weak_ptr_factory_.GetWeakPtr();
+  base::ConcurrentCallbacks<SubAppsServiceRemoveResultPtr> concurrent;
   for (const std::string& manifest_id_path : manifest_id_paths) {
-    RemoveSubApp(manifest_id_path, remove_barrier_callback,
+    RemoveSubApp(manifest_id_path, concurrent.CreateCallback(),
                  GetAppId(render_frame_host()));
   }
+  std::move(concurrent)
+      .Done(base::BindOnce(&SubAppsServiceImpl::NotifyUninstall, weak_ptr,
+                           std::move(result_callback)));
 }
 
 void SubAppsServiceImpl::RemoveSubApp(
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 41110c5b..041232c 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/web_applications/web_app.h"
+
 #include <stddef.h>
+
 #include <memory>
 #include <optional>
 #include <set>
@@ -11,12 +14,12 @@
 #include <utility>
 #include <vector>
 
-#include "base/barrier_callback.h"
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/concurrent_callbacks.h"
 #include "base/run_loop.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
@@ -70,7 +73,6 @@
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/test/web_app_test_observers.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
-#include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
@@ -1752,9 +1754,13 @@
   base::HistogramTester tester;
   base::test::TestFuture<Result> result;
 
-  auto synchronize_barrier = base::BarrierCallback<Result>(
-      /*num_callbacks=*/2,
-      base::BindOnce(
+  base::ConcurrentCallbacks<Result> concurrent;
+  provider->os_integration_manager().UpdateShortcuts(
+      app_id, "Manifest test app", concurrent.CreateCallback());
+  provider->os_integration_manager().Synchronize(
+      app_id, base::BindOnce(concurrent.CreateCallback(), Result::kOk));
+  std::move(concurrent)
+      .Done(base::BindOnce(
           [&](base::OnceCallback<void(Result)> result_callback,
               std::vector<Result> final_results) {
             DCHECK_EQ(2u, final_results.size());
@@ -1767,10 +1773,6 @@
           },
           result.GetCallback()));
 
-  provider->os_integration_manager().UpdateShortcuts(
-      app_id, "Manifest test app", synchronize_barrier);
-  provider->os_integration_manager().Synchronize(
-      app_id, base::BindOnce(synchronize_barrier, Result::kOk));
   ASSERT_TRUE(result.Wait());
   EXPECT_THAT(result.Get(), testing::Eq(Result::kOk));
 
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_base.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler_base.cc
index b9d343b3..cb9ee7e 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler_base.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_base.cc
@@ -18,6 +18,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/i18n/message_formatter.h"
 #include "base/logging.h"
+#include "base/notreached.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -69,7 +70,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/arc/session/connection_holder.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
-#include "chrome/browser/ash/apps/apk_web_app_service.h"
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/ash/crosapi/web_app_service_ash.h"
@@ -281,32 +281,6 @@
 
 }  // namespace
 
-AppManagementPageHandlerBase::AppManagementPageHandlerBase(
-    mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
-    mojo::PendingRemote<app_management::mojom::Page> page,
-    Profile* profile,
-    Delegate& delegate)
-    : receiver_(this, std::move(receiver)),
-      page_(std::move(page)),
-      profile_(profile),
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-      shelf_delegate_(this, profile),
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-      delegate_(delegate) {
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile_);
-  app_registry_cache_observer_.Observe(&proxy->AppRegistryCache());
-  preferred_apps_list_handle_observer_.Observe(&proxy->PreferredAppsList());
-
-  // On Chrome OS, file handler updates are already plumbed through
-  // App Service since the change will also affect the intent filters.
-  // There's no need to update twice.
-#if !BUILDFLAG(IS_CHROMEOS)
-  auto* provider = web_app::WebAppProvider::GetForWebApps(profile_);
-  registrar_observation_.Observe(&provider->registrar_unsafe());
-#endif
-}
-
 AppManagementPageHandlerBase::~AppManagementPageHandlerBase() {}
 
 void AppManagementPageHandlerBase::OnPinnedChanged(const std::string& app_id,
@@ -439,16 +413,6 @@
       app_id, std::move(permission));
 }
 
-void AppManagementPageHandlerBase::SetResizeLocked(const std::string& app_id,
-                                                   bool locked) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  apps::AppServiceProxyFactory::GetForProfile(profile_)->SetResizeLocked(
-      app_id, locked);
-#else
-  NOTIMPLEMENTED();
-#endif
-}
-
 void AppManagementPageHandlerBase::Uninstall(const std::string& app_id) {
   apps::AppServiceProxyFactory::GetForProfile(profile_)->Uninstall(
       app_id, apps::UninstallSource::kAppManagement,
@@ -461,98 +425,11 @@
       app_id);
 }
 
-void AppManagementPageHandlerBase::SetPreferredApp(const std::string& app_id,
-                                                   bool is_preferred_app) {
-#if BUILDFLAG(IS_CHROMEOS)
-  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
-  bool is_preferred_app_for_supported_links =
-      proxy->PreferredAppsList().IsPreferredAppForSupportedLinks(app_id);
-
-  if (is_preferred_app && !is_preferred_app_for_supported_links) {
-    proxy->SetSupportedLinksPreference(app_id);
-  } else if (!is_preferred_app && is_preferred_app_for_supported_links) {
-    proxy->RemoveSupportedLinksPreference(app_id);
-  }
-#else
-  web_app::WebAppProvider* provider =
-      web_app::WebAppProvider::GetForWebApps(profile_);
-
-  provider->scheduler().SetAppCapturesSupportedLinksDisableOverlapping(
-      app_id, is_preferred_app, base::DoNothing());
-#endif  // BUILDFLAG(IS_CHROMEOS)
-}
-
-void AppManagementPageHandlerBase::GetOverlappingPreferredApps(
-    const std::string& app_id,
-    GetOverlappingPreferredAppsCallback callback) {
-#if BUILDFLAG(IS_CHROMEOS)
-  auto intent_filters = GetSupportedLinkIntentFilters(profile_, app_id);
-  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
-  base::flat_set<std::string> app_ids =
-      proxy->PreferredAppsList().FindPreferredAppsForFilters(intent_filters);
-  app_ids.erase(app_id);
-
-  // Erase all IDs that do not correspond to installed apps in App Service. Such
-  // IDs could be apps that have been uninstalled but did not have their
-  // preference updated correctly, or the legacy "use_browser" preference. This
-  // prevents attempting to show an overlapping app dialog for an app that
-  // doesn't currently exist.
-  base::EraseIf(app_ids, [proxy](const std::string& app_id) {
-    return !proxy->AppRegistryCache().IsAppInstalled(app_id);
-  });
-  std::move(callback).Run(std::move(app_ids).extract());
-#else
-  web_app::WebAppProvider* provider =
-      web_app::WebAppProvider::GetForWebApps(profile_);
-  provider->scheduler().ScheduleCallbackWithResult(
-      "AppManagementPageHandlerBase::GetOverlappingPreferredApps",
-      web_app::AllAppsLockDescription(),
-      base::BindOnce(
-          [](const webapps::AppId& app_id, web_app::AllAppsLock& all_apps_lock,
-             base::Value::Dict& debug_value) {
-            return all_apps_lock.registrar().GetOverlappingAppsMatchingScope(
-                app_id);
-          },
-          app_id),
-      std::move(callback), /*arg_for_shutdown=*/std::vector<std::string>());
-#endif  // BUILDFLAG(IS_CHROMEOS)
-}
-
 void AppManagementPageHandlerBase::UpdateAppSize(const std::string& app_id) {
   auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
   proxy->UpdateAppSize(app_id);
 }
 
-void AppManagementPageHandlerBase::SetWindowMode(const std::string& app_id,
-                                                 apps::WindowMode window_mode) {
-  // On ChromeOS, apps should always open in a new window,
-  // hence window mode changes are not allowed.
-#if BUILDFLAG(IS_CHROMEOS)
-  NOTIMPLEMENTED();
-#else
-  auto* provider = web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_);
-
-  // Changing window mode is not allowed for isolated web apps.
-  if (provider->registrar_unsafe().IsIsolated(app_id)) {
-    return;
-  }
-
-  apps::AppServiceProxyFactory::GetForProfile(profile_)->SetWindowMode(
-      app_id, window_mode);
-#endif
-}
-
-void AppManagementPageHandlerBase::SetRunOnOsLoginMode(
-    const std::string& app_id,
-    apps::RunOnOsLoginMode run_on_os_login_mode) {
-#if BUILDFLAG(IS_CHROMEOS)
-  NOTIMPLEMENTED();
-#else
-  apps::AppServiceProxyFactory::GetForProfile(profile_)->SetRunOnOsLoginMode(
-      app_id, run_on_os_login_mode);
-#endif
-}
-
 void AppManagementPageHandlerBase::SetFileHandlingEnabled(
     const std::string& app_id,
     bool enabled) {
@@ -563,18 +440,24 @@
       app_id, std::move(permission));
 }
 
-void AppManagementPageHandlerBase::ShowDefaultAppAssociationsUi() {
-  DCHECK(CanShowDefaultAppAssociationsUi());
-#if BUILDFLAG(IS_WIN)
-  base::win::LaunchDefaultAppsSettingsModernDialog({});
-#endif
+AppManagementPageHandlerBase::AppManagementPageHandlerBase(
+    mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+    mojo::PendingRemote<app_management::mojom::Page> page,
+    Profile* profile,
+    Delegate& delegate)
+    : receiver_(this, std::move(receiver)),
+      page_(std::move(page)),
+      profile_(profile),
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      shelf_delegate_(this, profile),
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+      delegate_(delegate) {
+  apps::AppServiceProxy* proxy =
+      apps::AppServiceProxyFactory::GetForProfile(profile_);
+  app_registry_cache_observer_.Observe(&proxy->AppRegistryCache());
 }
 
-void AppManagementPageHandlerBase::OnWebAppFileHandlerApprovalStateChanged(
-    const webapps::AppId& app_id) {
-#if BUILDFLAG(IS_CHROMEOS)
-  NOTREACHED();
-#endif
+void AppManagementPageHandlerBase::NotifyAppChanged(const std::string& app_id) {
   app_management::mojom::AppPtr app;
 
   apps::AppServiceProxyFactory::GetForProfile(profile_)
@@ -585,6 +468,7 @@
         }
       });
 
+  // If an app with this id is not already installed, do nothing.
   if (!app) {
     return;
   }
@@ -592,18 +476,6 @@
   page_->OnAppChanged(std::move(app));
 }
 
-void AppManagementPageHandlerBase::OnAppRegistrarDestroyed() {
-  registrar_observation_.Reset();
-}
-
-#if !BUILDFLAG(IS_CHROMEOS)
-void AppManagementPageHandlerBase::OnWebAppUserLinkCapturingPreferencesChanged(
-    const webapps::AppId& app_id,
-    bool is_preferred) {
-  OnPreferredAppChanged(app_id, is_preferred);
-}
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
 app_management::mojom::AppPtr AppManagementPageHandlerBase::CreateUIAppPtr(
     const apps::AppUpdate& update) {
   auto app = app_management::mojom::App::New();
@@ -780,43 +652,6 @@
   return app;
 }
 
-void AppManagementPageHandlerBase::OpenStorePage(const std::string& app_id) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
-  auto* apk_service = ash::ApkWebAppService::Get(profile_);
-  proxy->AppRegistryCache().ForOneApp(
-      app_id, [&proxy, &apk_service](const apps::AppUpdate& update) {
-        if (update.InstallSource() == apps::InstallSource::kPlayStore) {
-          std::string package_name = update.PublisherId();
-          if (apk_service->IsWebAppInstalledFromArc(update.AppId())) {
-            package_name =
-                apk_service->GetPackageNameForWebApp(update.AppId()).value();
-          }
-          GURL url("https://play.google.com/store/apps/details?id=" +
-                   package_name);
-          proxy->LaunchAppWithUrl(arc::kPlayStoreAppId, ui::EF_NONE, url,
-                                  apps::LaunchSource::kFromChromeInternal);
-        } else if (update.InstallSource() ==
-                   apps::InstallSource::kChromeWebStore) {
-          GURL url("https://chrome.google.com/webstore/detail/" +
-                   update.AppId());
-          proxy->LaunchAppWithUrl(extensions::kWebStoreAppId, ui::EF_NONE, url,
-                                  apps::LaunchSource::kFromChromeInternal);
-        }
-      });
-#endif
-}
-
-void AppManagementPageHandlerBase::SetAppLocale(const std::string& app_id,
-                                                const std::string& locale_tag) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  apps::AppServiceProxyFactory::GetForProfile(profile_)->SetAppLocale(
-      app_id, locale_tag);
-#else
-  NOTIMPLEMENTED();
-#endif
-}
-
 void AppManagementPageHandlerBase::OnAppUpdate(const apps::AppUpdate& update) {
   if (update.ShowInManagementChanged() || update.ReadinessChanged()) {
     if (update.ShowInManagement().value_or(false) &&
@@ -837,31 +672,3 @@
     apps::AppRegistryCache* cache) {
   cache->RemoveObserver(this);
 }
-
-void AppManagementPageHandlerBase::OnPreferredAppChanged(
-    const std::string& app_id,
-    bool is_preferred_app) {
-  app_management::mojom::AppPtr app;
-
-  apps::AppServiceProxyFactory::GetForProfile(profile_)
-      ->AppRegistryCache()
-      .ForOneApp(app_id, [this, &app](const apps::AppUpdate& update) {
-        if (update.Readiness() == apps::Readiness::kReady) {
-          app = CreateUIAppPtr(update);
-        }
-      });
-
-  // If an app with this id is not already installed, do nothing.
-  if (!app) {
-    return;
-  }
-
-  app->is_preferred_app = is_preferred_app;
-
-  page_->OnAppChanged(std::move(app));
-}
-
-void AppManagementPageHandlerBase::OnPreferredAppsListWillBeDestroyed(
-    apps::PreferredAppsListHandle* handle) {
-  preferred_apps_list_handle_observer_.Reset();
-}
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_base.h b/chrome/browser/ui/webui/app_management/app_management_page_handler_base.h
index 3551444..cbb2471 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler_base.h
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_base.h
@@ -11,12 +11,9 @@
 #include "base/scoped_observation.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/web_applications/locks/all_apps_lock.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-#include "chrome/browser/web_applications/web_app_registrar_observer.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/permission.h"
-#include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
 #include "components/services/app_service/public/cpp/run_on_os_login_types.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -31,11 +28,8 @@
 
 class Profile;
 
-class AppManagementPageHandlerBase
-    : public app_management::mojom::PageHandler,
-      public apps::AppRegistryCache::Observer,
-      public apps::PreferredAppsListHandle::Observer,
-      public web_app::WebAppRegistrarObserver {
+class AppManagementPageHandlerBase : public app_management::mojom::PageHandler,
+                                     public apps::AppRegistryCache::Observer {
  public:
   //  Handles platform specific tasks.
   class Delegate {
@@ -49,12 +43,6 @@
     virtual gfx::NativeWindow GetUninstallAnchorWindow() const = 0;
   };
 
-  AppManagementPageHandlerBase(
-      mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
-      mojo::PendingRemote<app_management::mojom::Page> page,
-      Profile* profile,
-      Delegate& delegate);
-
   AppManagementPageHandlerBase(const AppManagementPageHandlerBase&) = delete;
   AppManagementPageHandlerBase& operator=(const AppManagementPageHandlerBase&) =
       delete;
@@ -73,39 +61,23 @@
   void SetPinned(const std::string& app_id, bool pinned) override;
   void SetPermission(const std::string& app_id,
                      apps::PermissionPtr permission) override;
-  void SetResizeLocked(const std::string& app_id, bool locked) override;
   void Uninstall(const std::string& app_id) override;
   void OpenNativeSettings(const std::string& app_id) override;
-  void SetPreferredApp(const std::string& app_id,
-                       bool is_preferred_app) override;
-  void GetOverlappingPreferredApps(
-      const std::string& app_id,
-      GetOverlappingPreferredAppsCallback callback) override;
   void UpdateAppSize(const std::string& app_id) override;
-  void SetWindowMode(const std::string& app_id,
-                     apps::WindowMode window_mode) override;
-  void SetRunOnOsLoginMode(
-      const std::string& app_id,
-      apps::RunOnOsLoginMode run_on_os_login_mode) override;
   void SetFileHandlingEnabled(const std::string& app_id, bool enabled) override;
-  void ShowDefaultAppAssociationsUi() override;
-  void OpenStorePage(const std::string& app_id) override;
-  void SetAppLocale(const std::string& app_id,
-                    const std::string& locale_tag) override;
 
-  // web_app::WebAppRegistrarObserver:
-  void OnWebAppFileHandlerApprovalStateChanged(
-      const webapps::AppId& app_id) override;
-  void OnAppRegistrarDestroyed() override;
+ protected:
+  AppManagementPageHandlerBase(
+      mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+      mojo::PendingRemote<app_management::mojom::Page> page,
+      Profile* profile,
+      Delegate& delegate);
 
-  // The following observers are used for user link capturing on W/M/L platforms
-  // to observe user link capturing preferences being changed
-  // in the registrar, so as to propagate the changes to the app-settings/ page
-  // to change the UI dynamically.
-#if !BUILDFLAG(IS_CHROMEOS)
-  void OnWebAppUserLinkCapturingPreferencesChanged(const webapps::AppId& app_id,
-                                                   bool is_preferred) override;
-#endif  // !BUILDFLAG(IS_CHROMEOS)
+  // Notify the WebUI frontend that the app with a given `app_id` has changed on
+  // the backend. Will generate a new AppPtr and send it to the frontend.
+  void NotifyAppChanged(const std::string& app_id);
+
+  Profile* profile() { return profile_; }
 
  private:
   app_management::mojom::AppPtr CreateUIAppPtr(const apps::AppUpdate& update);
@@ -115,12 +87,6 @@
   void OnAppRegistryCacheWillBeDestroyed(
       apps::AppRegistryCache* cache) override;
 
-  // apps::PreferredAppsListHandle::Observer overrides:
-  void OnPreferredAppChanged(const std::string& app_id,
-                             bool is_preferred_app) override;
-  void OnPreferredAppsListWillBeDestroyed(
-      apps::PreferredAppsListHandle* handle) override;
-
   mojo::Receiver<app_management::mojom::PageHandler> receiver_;
 
   mojo::Remote<app_management::mojom::Page> page_;
@@ -137,14 +103,6 @@
                           apps::AppRegistryCache::Observer>
       app_registry_cache_observer_{this};
 
-  base::ScopedObservation<apps::PreferredAppsListHandle,
-                          apps::PreferredAppsListHandle::Observer>
-      preferred_apps_list_handle_observer_{this};
-
-  base::ScopedObservation<web_app::WebAppRegistrar,
-                          web_app::WebAppRegistrarObserver>
-      registrar_observation_{this};
-
   base::WeakPtrFactory<AppManagementPageHandlerBase> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.cc
new file mode 100644
index 0000000..949ce60
--- /dev/null
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.cc
@@ -0,0 +1,158 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h"
+
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
+#include "chrome/browser/ash/apps/apk_web_app_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
+#include "components/services/app_service/public/cpp/intent_filter_util.h"
+#include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
+#include "extensions/common/constants.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
+
+namespace {
+
+// Returns a list of intent filters that support http/https given an app ID.
+apps::IntentFilters GetSupportedLinkIntentFilters(Profile* profile,
+                                                  const std::string& app_id) {
+  apps::IntentFilters intent_filters;
+  apps::AppServiceProxyFactory::GetForProfile(profile)
+      ->AppRegistryCache()
+      .ForOneApp(app_id,
+                 [&app_id, &intent_filters](const apps::AppUpdate& update) {
+                   if (update.Readiness() == apps::Readiness::kReady) {
+                     for (auto& filter : update.IntentFilters()) {
+                       if (apps_util::IsSupportedLinkForApp(app_id, filter)) {
+                         intent_filters.emplace_back(std::move(filter));
+                       }
+                     }
+                   }
+                 });
+  return intent_filters;
+}
+
+}  // namespace
+
+AppManagementPageHandlerChromeOs::AppManagementPageHandlerChromeOs(
+    mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+    mojo::PendingRemote<app_management::mojom::Page> page,
+    Profile* profile,
+    AppManagementPageHandlerBase::Delegate& delegate)
+    : AppManagementPageHandlerBase(std::move(receiver),
+                                   std::move(page),
+                                   profile,
+                                   delegate) {
+  apps::AppServiceProxy* proxy =
+      apps::AppServiceProxyFactory::GetForProfile(profile);
+  preferred_apps_list_handle_observer_.Observe(&proxy->PreferredAppsList());
+}
+
+AppManagementPageHandlerChromeOs::~AppManagementPageHandlerChromeOs() = default;
+
+void AppManagementPageHandlerChromeOs::SetResizeLocked(
+    const std::string& app_id,
+    bool locked) {
+  apps::AppServiceProxyFactory::GetForProfile(profile())->SetResizeLocked(
+      app_id, locked);
+}
+
+void AppManagementPageHandlerChromeOs::SetPreferredApp(
+    const std::string& app_id,
+    bool is_preferred_app) {
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
+  bool is_preferred_app_for_supported_links =
+      proxy->PreferredAppsList().IsPreferredAppForSupportedLinks(app_id);
+
+  if (is_preferred_app && !is_preferred_app_for_supported_links) {
+    proxy->SetSupportedLinksPreference(app_id);
+  } else if (!is_preferred_app && is_preferred_app_for_supported_links) {
+    proxy->RemoveSupportedLinksPreference(app_id);
+  }
+}
+
+void AppManagementPageHandlerChromeOs::GetOverlappingPreferredApps(
+    const std::string& app_id,
+    GetOverlappingPreferredAppsCallback callback) {
+  auto intent_filters = GetSupportedLinkIntentFilters(profile(), app_id);
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
+  base::flat_set<std::string> app_ids =
+      proxy->PreferredAppsList().FindPreferredAppsForFilters(intent_filters);
+  app_ids.erase(app_id);
+
+  // Erase all IDs that do not correspond to installed apps in App Service. Such
+  // IDs could be apps that have been uninstalled but did not have their
+  // preference updated correctly, or the legacy "use_browser" preference. This
+  // prevents attempting to show an overlapping app dialog for an app that
+  // doesn't currently exist.
+  base::EraseIf(app_ids, [proxy](const std::string& app_id) {
+    return !proxy->AppRegistryCache().IsAppInstalled(app_id);
+  });
+  std::move(callback).Run(std::move(app_ids).extract());
+}
+
+void AppManagementPageHandlerChromeOs::SetWindowMode(
+    const std::string& app_id,
+    apps::WindowMode window_mode) {
+  NOTIMPLEMENTED();
+}
+
+void AppManagementPageHandlerChromeOs::SetRunOnOsLoginMode(
+    const std::string& app_id,
+    apps::RunOnOsLoginMode run_on_os_login_mode) {
+  NOTIMPLEMENTED();
+}
+
+void AppManagementPageHandlerChromeOs::ShowDefaultAppAssociationsUi() {
+  NOTIMPLEMENTED();
+}
+
+void AppManagementPageHandlerChromeOs::OpenStorePage(
+    const std::string& app_id) {
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
+  auto* apk_service = ash::ApkWebAppService::Get(profile());
+  proxy->AppRegistryCache().ForOneApp(
+      app_id, [&proxy, &apk_service](const apps::AppUpdate& update) {
+        if (update.InstallSource() == apps::InstallSource::kPlayStore) {
+          std::string package_name = update.PublisherId();
+          if (apk_service->IsWebAppInstalledFromArc(update.AppId())) {
+            package_name =
+                apk_service->GetPackageNameForWebApp(update.AppId()).value();
+          }
+          GURL url("https://play.google.com/store/apps/details?id=" +
+                   package_name);
+          proxy->LaunchAppWithUrl(arc::kPlayStoreAppId, ui::EF_NONE, url,
+                                  apps::LaunchSource::kFromChromeInternal);
+        } else if (update.InstallSource() ==
+                   apps::InstallSource::kChromeWebStore) {
+          GURL url("https://chrome.google.com/webstore/detail/" +
+                   update.AppId());
+          proxy->LaunchAppWithUrl(extensions::kWebStoreAppId, ui::EF_NONE, url,
+                                  apps::LaunchSource::kFromChromeInternal);
+        }
+      });
+}
+
+void AppManagementPageHandlerChromeOs::SetAppLocale(
+    const std::string& app_id,
+    const std::string& locale_tag) {
+  apps::AppServiceProxyFactory::GetForProfile(profile())->SetAppLocale(
+      app_id, locale_tag);
+}
+
+void AppManagementPageHandlerChromeOs::OnPreferredAppChanged(
+    const std::string& app_id,
+    bool is_preferred_app) {
+  NotifyAppChanged(app_id);
+}
+
+void AppManagementPageHandlerChromeOs::OnPreferredAppsListWillBeDestroyed(
+    apps::PreferredAppsListHandle* handle) {
+  preferred_apps_list_handle_observer_.Reset();
+}
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h b/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h
new file mode 100644
index 0000000..72e3076f
--- /dev/null
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h
@@ -0,0 +1,65 @@
+// Copyright 2024 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_WEBUI_APP_MANAGEMENT_APP_MANAGEMENT_PAGE_HANDLER_CHROMEOS_H_
+#define CHROME_BROWSER_UI_WEBUI_APP_MANAGEMENT_APP_MANAGEMENT_PAGE_HANDLER_CHROMEOS_H_
+
+#include <string>
+
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
+#include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
+
+class Profile;
+
+// PageHandler for the ChromeOS App Management page.
+class AppManagementPageHandlerChromeOs
+    : public AppManagementPageHandlerBase,
+      public apps::PreferredAppsListHandle::Observer {
+ public:
+  AppManagementPageHandlerChromeOs(
+      mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+      mojo::PendingRemote<app_management::mojom::Page> page,
+      Profile* profile,
+      AppManagementPageHandlerBase::Delegate& delegate);
+
+  AppManagementPageHandlerChromeOs(const AppManagementPageHandlerChromeOs&) =
+      delete;
+  AppManagementPageHandlerChromeOs& operator=(
+      const AppManagementPageHandlerChromeOs&) = delete;
+
+  ~AppManagementPageHandlerChromeOs() override;
+
+  // app_management::mojom::PageHandler:
+  void SetResizeLocked(const std::string& app_id, bool locked) override;
+  void SetPreferredApp(const std::string& app_id,
+                       bool is_preferred_app) override;
+  void GetOverlappingPreferredApps(
+      const std::string& app_id,
+      GetOverlappingPreferredAppsCallback callback) override;
+  void SetWindowMode(const std::string& app_id,
+                     apps::WindowMode window_mode) override;
+  void SetRunOnOsLoginMode(
+      const std::string& app_id,
+      apps::RunOnOsLoginMode run_on_os_login_mode) override;
+  void ShowDefaultAppAssociationsUi() override;
+  void OpenStorePage(const std::string& app_id) override;
+  void SetAppLocale(const std::string& app_id,
+                    const std::string& locale_tag) override;
+
+  // apps::PreferredAppsListHandle::Observer overrides:
+  void OnPreferredAppChanged(const std::string& app_id,
+                             bool is_preferred_app) override;
+  void OnPreferredAppsListWillBeDestroyed(
+      apps::PreferredAppsListHandle* handle) override;
+
+ private:
+  base::ScopedObservation<apps::PreferredAppsListHandle,
+                          apps::PreferredAppsListHandle::Observer>
+      preferred_apps_list_handle_observer_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_APP_MANAGEMENT_APP_MANAGEMENT_PAGE_HANDLER_CHROMEOS_H_
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_factory.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler_factory.cc
index 0bd5a63..280586a 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler_factory.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_factory.cc
@@ -24,6 +24,12 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h"
+#else
+#include "chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h"
+#endif
+
 AppManagementPageHandlerFactory::AppManagementPageHandlerFactory(
     Profile* profile,
     std::unique_ptr<AppManagementPageHandlerBase::Delegate> delegate)
@@ -43,6 +49,11 @@
     mojo::PendingReceiver<app_management::mojom::PageHandler> receiver) {
   DCHECK(page);
 
-  page_handler_ = std::make_unique<AppManagementPageHandlerBase>(
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  page_handler_ = std::make_unique<AppManagementPageHandlerChromeOs>(
       std::move(receiver), std::move(page), profile_, *delegate_);
+#else
+  page_handler_ = std::make_unique<WebAppSettingsPageHandler>(
+      std::move(receiver), std::move(page), profile_, *delegate_);
+#endif
 }
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler_unittest.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler_unittest.cc
index 9bca01ca..fdc01f75 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler_unittest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
-
 #include <memory>
 #include <string>
 #include <vector>
@@ -37,10 +35,11 @@
 #include "chrome/browser/ash/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ash/apps/apk_web_app_service.h"
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_chromeos.h"
 #include "components/arc/test/fake_intent_helper_instance.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #else
-#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h"
 #include "chrome/common/chrome_features.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
@@ -78,10 +77,14 @@
 
     mojo::PendingReceiver<app_management::mojom::Page> page;
     mojo::Remote<app_management::mojom::PageHandler> handler;
-    handler_ = std::make_unique<AppManagementPageHandlerBase>(
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    handler_ = std::make_unique<AppManagementPageHandlerChromeOs>(
         handler.BindNewPipeAndPassReceiver(),
         page.InitWithNewPipeAndPassRemote(), profile(), *delegate_);
-#if !BUILDFLAG(IS_CHROMEOS)
+#else
+    handler_ = std::make_unique<WebAppSettingsPageHandler>(
+        handler.BindNewPipeAndPassReceiver(),
+        page.InitWithNewPipeAndPassRemote(), profile(), *delegate_);
     auto features_and_params = apps::test::GetFeaturesToEnableLinkCapturingUX(
         /*override_captures_by_default=*/GetParam());
     features_and_params.push_back(
diff --git a/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.cc b/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.cc
new file mode 100644
index 0000000..7611328
--- /dev/null
+++ b/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.cc
@@ -0,0 +1,118 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h"
+
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
+#include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/win/default_apps_util.h"
+#endif
+
+WebAppSettingsPageHandler::WebAppSettingsPageHandler(
+    mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+    mojo::PendingRemote<app_management::mojom::Page> page,
+    Profile* profile,
+    AppManagementPageHandlerBase::Delegate& delegate)
+    : AppManagementPageHandlerBase(std::move(receiver),
+                                   std::move(page),
+                                   profile,
+                                   delegate) {
+  auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
+  registrar_observation_.Observe(&provider->registrar_unsafe());
+}
+
+WebAppSettingsPageHandler::~WebAppSettingsPageHandler() = default;
+
+void WebAppSettingsPageHandler::SetResizeLocked(const std::string& app_id,
+                                                bool locked) {
+  NOTIMPLEMENTED();
+}
+
+void WebAppSettingsPageHandler::SetPreferredApp(const std::string& app_id,
+                                                bool is_preferred_app) {
+  web_app::WebAppProvider* provider =
+      web_app::WebAppProvider::GetForWebApps(profile());
+
+  provider->scheduler().SetAppCapturesSupportedLinksDisableOverlapping(
+      app_id, is_preferred_app, base::DoNothing());
+}
+
+void WebAppSettingsPageHandler::GetOverlappingPreferredApps(
+    const std::string& app_id,
+    GetOverlappingPreferredAppsCallback callback) {
+  web_app::WebAppProvider* provider =
+      web_app::WebAppProvider::GetForWebApps(profile());
+  provider->scheduler().ScheduleCallbackWithResult(
+      "AppManagementPageHandlerBase::GetOverlappingPreferredApps",
+      web_app::AllAppsLockDescription(),
+      base::BindOnce(
+          [](const webapps::AppId& app_id, web_app::AllAppsLock& all_apps_lock,
+             base::Value::Dict& debug_value) {
+            return all_apps_lock.registrar().GetOverlappingAppsMatchingScope(
+                app_id);
+          },
+          app_id),
+      std::move(callback), /*arg_for_shutdown=*/std::vector<std::string>());
+}
+
+void WebAppSettingsPageHandler::SetWindowMode(const std::string& app_id,
+                                              apps::WindowMode window_mode) {
+  auto* provider = web_app::WebAppProvider::GetForLocalAppsUnchecked(profile());
+
+  // Changing window mode is not allowed for isolated web apps.
+  if (provider->registrar_unsafe().IsIsolated(app_id)) {
+    return;
+  }
+
+  apps::AppServiceProxyFactory::GetForProfile(profile())->SetWindowMode(
+      app_id, window_mode);
+}
+
+void WebAppSettingsPageHandler::SetRunOnOsLoginMode(
+    const std::string& app_id,
+    apps::RunOnOsLoginMode run_on_os_login_mode) {
+  apps::AppServiceProxyFactory::GetForProfile(profile())->SetRunOnOsLoginMode(
+      app_id, run_on_os_login_mode);
+}
+
+void WebAppSettingsPageHandler::ShowDefaultAppAssociationsUi() {
+#if BUILDFLAG(IS_WIN)
+  base::win::LaunchDefaultAppsSettingsModernDialog({});
+#else
+  NOTIMPLEMENTED();
+#endif
+}
+
+void WebAppSettingsPageHandler::OpenStorePage(const std::string& app_id) {
+  NOTIMPLEMENTED();
+}
+
+void WebAppSettingsPageHandler::SetAppLocale(const std::string& app_id,
+                                             const std::string& locale_tag) {
+  NOTIMPLEMENTED();
+}
+
+void WebAppSettingsPageHandler::OnAppRegistrarDestroyed() {
+  registrar_observation_.Reset();
+}
+
+void WebAppSettingsPageHandler::OnWebAppFileHandlerApprovalStateChanged(
+    const webapps::AppId& app_id) {
+  NotifyAppChanged(app_id);
+}
+
+void WebAppSettingsPageHandler::OnWebAppUserLinkCapturingPreferencesChanged(
+    const webapps::AppId& app_id,
+    bool is_preferred) {
+  NotifyAppChanged(app_id);
+}
diff --git a/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h b/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h
new file mode 100644
index 0000000..c883cc4
--- /dev/null
+++ b/chrome/browser/ui/webui/app_management/web_app_settings_page_handler.h
@@ -0,0 +1,67 @@
+// Copyright 2024 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_WEBUI_APP_MANAGEMENT_WEB_APP_SETTINGS_PAGE_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_APP_MANAGEMENT_WEB_APP_SETTINGS_PAGE_HANDLER_H_
+
+#include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
+#include "chrome/browser/web_applications/web_app_registrar_observer.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
+
+class Profile;
+
+namespace web_app {
+class WebAppRegistrar;
+}
+
+// PageHandler for the chrome://app-settings page. Connects directly to the
+// WebAppProvider to manage settings for web apps.
+class WebAppSettingsPageHandler : public AppManagementPageHandlerBase,
+                                  public web_app::WebAppRegistrarObserver {
+ public:
+  WebAppSettingsPageHandler(
+      mojo::PendingReceiver<app_management::mojom::PageHandler> receiver,
+      mojo::PendingRemote<app_management::mojom::Page> page,
+      Profile* profile,
+      AppManagementPageHandlerBase::Delegate& delegate);
+
+  WebAppSettingsPageHandler(const WebAppSettingsPageHandler&) = delete;
+  WebAppSettingsPageHandler& operator=(const WebAppSettingsPageHandler&) =
+      delete;
+
+  ~WebAppSettingsPageHandler() override;
+
+  // app_management::mojom::PageHandler:
+  void SetResizeLocked(const std::string& app_id, bool locked) override;
+  void SetPreferredApp(const std::string& app_id,
+                       bool is_preferred_app) override;
+  void GetOverlappingPreferredApps(
+      const std::string& app_id,
+      GetOverlappingPreferredAppsCallback callback) override;
+  void SetWindowMode(const std::string& app_id,
+                     apps::WindowMode window_mode) override;
+  void SetRunOnOsLoginMode(
+      const std::string& app_id,
+      apps::RunOnOsLoginMode run_on_os_login_mode) override;
+  void ShowDefaultAppAssociationsUi() override;
+  void OpenStorePage(const std::string& app_id) override;
+  void SetAppLocale(const std::string& app_id,
+                    const std::string& locale_tag) override;
+
+  // web_app::WebAppRegistrarObserver:
+  void OnAppRegistrarDestroyed() override;
+  void OnWebAppFileHandlerApprovalStateChanged(
+      const webapps::AppId& app_id) override;
+  void OnWebAppUserLinkCapturingPreferencesChanged(const webapps::AppId& app_id,
+                                                   bool is_preferred) override;
+
+ private:
+  base::ScopedObservation<web_app::WebAppRegistrar,
+                          web_app::WebAppRegistrarObserver>
+      registrar_observation_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_APP_MANAGEMENT_WEB_APP_SETTINGS_PAGE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.cc
index f9ecca0..93dd6dd 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics.cc
@@ -145,6 +145,8 @@
               case OfficeDriveOpenErrors::kNoDriveService:
               case OfficeDriveOpenErrors::kDriveAuthenticationNotReady:
               case OfficeDriveOpenErrors::kMeteredConnection:
+              case OfficeDriveOpenErrors::kDisableDrivePreferenceSet:
+              case OfficeDriveOpenErrors::kDriveDisabledForAccountType:
                 break;
               case OfficeDriveOpenErrors::kTimeout:
               case OfficeDriveOpenErrors::kNoMetadata:
@@ -258,6 +260,8 @@
               case OfficeDriveOpenErrors::kMeteredConnection:
               case OfficeDriveOpenErrors::kEmptyAlternateUrl:
               case OfficeDriveOpenErrors::kWaitingForUpload:
+              case OfficeDriveOpenErrors::kDisableDrivePreferenceSet:
+              case OfficeDriveOpenErrors::kDriveDisabledForAccountType:
                 break;
               case OfficeDriveOpenErrors::kSuccess:
                 SetWrongValueLogged(drive_open_error);
@@ -312,6 +316,8 @@
               case OfficeDriveOpenErrors::kMeteredConnection:
               case OfficeDriveOpenErrors::kEmptyAlternateUrl:
               case OfficeDriveOpenErrors::kWaitingForUpload:
+              case OfficeDriveOpenErrors::kDisableDrivePreferenceSet:
+              case OfficeDriveOpenErrors::kDriveDisabledForAccountType:
                 SetWrongValueLogged(drive_open_error);
                 break;
             }
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics_unittest.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics_unittest.cc
index b5adb6af..cfdbbf8 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics_unittest.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_open_metrics_unittest.cc
@@ -311,9 +311,11 @@
   ASSERT_EQ(1, CloudOpenMetricsTest::number_of_dump_calls());
 }
 
-// Tests that the SourceVolume companion metric is set correctly when TaskResult
-// is logged as kFailedToOpen and it is logged consistently.
-TEST_F(CloudOpenMetricsTest, MetricsConsistentWhenTaskResultIsFailedToOpen) {
+// Tests that the OpenErrors companion metric is set correctly when TaskResult
+// is logged as kFailedToOpen and it is logged consistently when opening in
+// OneDrive.
+TEST_F(CloudOpenMetricsTest,
+       MetricsConsistentWhenTaskResultIsFailedToOpenInOneDrive) {
   {
     CloudOpenMetrics cloud_open_metrics(CloudProvider::kOneDrive,
                                         /*file_count=*/1);
@@ -325,9 +327,11 @@
                                 MetricState::kCorrectlyLogged, 1);
 }
 
-// Tests that the SourceVolume companion metric is set correctly when TaskResult
-// is logged as kFailedToOpen and it is logged inconsistently.
-TEST_F(CloudOpenMetricsTest, MetricsInconsistentWhenTaskResultIsFailedToOpen) {
+// Tests that the OpenErrors companion metric is set correctly when TaskResult
+// is logged as kFailedToOpen and it is logged inconsistently when opening in
+// OneDrive.
+TEST_F(CloudOpenMetricsTest,
+       MetricsInconsistentWhenTaskResultIsFailedToOpenInOneDrive) {
   {
     CloudOpenMetrics cloud_open_metrics(CloudProvider::kOneDrive,
                                         /*file_count=*/1);
@@ -339,6 +343,38 @@
   ASSERT_EQ(1, CloudOpenMetricsTest::number_of_dump_calls());
 }
 
+// Tests that the OpenErrors companion metric is set correctly when TaskResult
+// is logged as kFailedToOpen and it is logged consistently when opening in
+// Drive.
+TEST_F(CloudOpenMetricsTest,
+       MetricsConsistentWhenTaskResultIsFailedToOpenInDrive) {
+  {
+    CloudOpenMetrics cloud_open_metrics(CloudProvider::kGoogleDrive,
+                                        /*file_count=*/1);
+    cloud_open_metrics.LogTaskResult(OfficeTaskResult::kFailedToOpen);
+    cloud_open_metrics.LogGoogleDriveOpenError(
+        OfficeDriveOpenErrors::kWaitingForUpload);
+  }
+  histogram_.ExpectUniqueSample(kDriveErrorMetricStateMetricName,
+                                MetricState::kCorrectlyLogged, 1);
+}
+
+// Tests that the OpenErrors companion metric is set correctly when TaskResult
+// is logged as kFailedToOpen and it is logged inconsistently when opening in
+// Drive.
+TEST_F(CloudOpenMetricsTest,
+       MetricsInconsistentWhenTaskResultIsFailedToOpenInDrive) {
+  {
+    CloudOpenMetrics cloud_open_metrics(CloudProvider::kGoogleDrive,
+                                        /*file_count=*/1);
+    cloud_open_metrics.LogTaskResult(OfficeTaskResult::kFailedToOpen);
+    cloud_open_metrics.LogGoogleDriveOpenError(OfficeDriveOpenErrors::kSuccess);
+  }
+  histogram_.ExpectUniqueSample(kDriveErrorMetricStateMetricName,
+                                MetricState::kWrongValueLogged, 1);
+  ASSERT_EQ(1, CloudOpenMetricsTest::number_of_dump_calls());
+}
+
 // Tests that the OpenErrors, UploadResult and TransferRequired companion
 // metrics are set correctly when TaskResult is logged as kOpened and they are
 // logged consistently.
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h
index 298d205..fd7d7bb 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h
@@ -95,7 +95,9 @@
   kMeteredConnection = 11,
   kEmptyAlternateUrl = 12,
   kWaitingForUpload = 13,
-  kMaxValue = kWaitingForUpload,
+  kDisableDrivePreferenceSet = 14,
+  kDriveDisabledForAccountType = 15,
+  kMaxValue = kDriveDisabledForAccountType,
 };
 
 // List of UMA enum values for opening Office files from OneDrive, with the
diff --git a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.cc b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.cc
index 66c7090..c647fad 100644
--- a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.cc
+++ b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.cc
@@ -22,11 +22,16 @@
 // Width of the Fallback dialog as found with the inspector tool.
 const int kWidth = 512;
 
-// Height of the Fallback dialogs for different text lengths as found with the
-// inspector tool.
-const int kOfflineHeight = 264;
-const int kDriveUnavailableHeight = 244;
-const int kMeteredHeight = 264;
+// Exact height of the Fallback dialogs required for different texts (in
+// English) as found with the inspector tool.
+const int kOfflineHeight = 244;
+const int kDisableDrivePreferenceSetHeight = 268;
+const int kDriveUnavailableHeight = 268;
+const int kDriveDisabledForAccountType = 268;
+const int kMeteredHeight = 268;
+
+// Height of a line of text as found with the inspector tool.
+const int kLineHeight = 20;
 
 // Return the task title id for the task represented by the `action_id`.
 int GetTaskTitleId(const std::string& action_id) {
@@ -56,34 +61,53 @@
     const ash::office_fallback::FallbackReason fallback_reason,
     int& title_id,
     int& reason_message_id,
+    bool& include_task_in_reason_message,
     int& instructions_message_id,
     int& width,
     int& height) {
   width = kWidth;
+  include_task_in_reason_message = false;
   switch (fallback_reason) {
     case ash::office_fallback::FallbackReason::kOffline:
+    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
       title_id = IDS_OFFICE_FALLBACK_TITLE_OFFLINE;
       reason_message_id = IDS_OFFICE_FALLBACK_REASON_OFFLINE;
       instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS_OFFLINE;
       height = kOfflineHeight;
       break;
-    case ash::office_fallback::FallbackReason::kDriveDisabled:
-    case ash::office_fallback::FallbackReason::kNoDriveService:
-    case ash::office_fallback::FallbackReason::kDriveAuthenticationNotReady:
-    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
+    case ash::office_fallback::FallbackReason::kDisableDrivePreferenceSet:
       title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
       reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE;
       instructions_message_id =
-          IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_UNAVAILABLE;
-      height = kDriveUnavailableHeight;
+          IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE;
+      height = kDisableDrivePreferenceSetHeight;
+      break;
+    case ash::office_fallback::FallbackReason::kDriveDisabledForAccountType:
+      title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
+      reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_DISABLED_FOR_ACCOUNT;
+      include_task_in_reason_message = true;
+      instructions_message_id =
+          IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT;
+      height = kDriveDisabledForAccountType;
       break;
     case ash::office_fallback::FallbackReason::kMeteredConnection:
       title_id = IDS_OFFICE_FALLBACK_TITLE_METERED;
       reason_message_id = IDS_OFFICE_FALLBACK_REASON_METERED;
+      include_task_in_reason_message = true;
       instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS_METERED;
       height = kMeteredHeight;
       break;
+    case ash::office_fallback::FallbackReason::kDriveDisabled:
+    case ash::office_fallback::FallbackReason::kNoDriveService:
+    case ash::office_fallback::FallbackReason::kDriveFsInterfaceError:
+      title_id = IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE;
+      reason_message_id = IDS_OFFICE_FALLBACK_REASON_DRIVE_UNAVAILABLE;
+      instructions_message_id = IDS_OFFICE_FALLBACK_INSTRUCTIONS;
+      height = kDriveUnavailableHeight;
+      break;
   }
+  // Add extra height to account for translations.
+  height += kLineHeight;
 }
 }  // namespace
 
@@ -131,17 +155,18 @@
   // Get failure specific text to display in dialog.
   int title_id;
   int reason_message_id;
+  bool include_task_in_reason_message;
   int instructions_message_id;
   int width;
   int height;
   GetDialogTextIdsAndSize(fallback_reason, title_id, reason_message_id,
+                          include_task_in_reason_message,
                           instructions_message_id, width, height);
   // TODO(cassycc): Figure out how to add the web_drive to the placeholder in
   // IDS_OFFICE_FALLBACK_TITLE_WEB_DRIVE_UNAVAILABLE.
   const std::string title_text = l10n_util::GetStringFUTF8(title_id, file_name);
   const std::string reason_message =
-      fallback_reason ==
-              ash::office_fallback::FallbackReason::kMeteredConnection
+      include_task_in_reason_message
           ? l10n_util::GetStringUTF8(reason_message_id)
           : l10n_util::GetStringFUTF8(reason_message_id, task_title);
   const std::string instructions_message =
diff --git a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h
index cc9d5f9..32f122d 100644
--- a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h
+++ b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h
@@ -20,6 +20,8 @@
   kDriveAuthenticationNotReady,
   kDriveFsInterfaceError,
   kMeteredConnection,
+  kDisableDrivePreferenceSet,
+  kDriveDisabledForAccountType,
 };
 
 using DialogChoiceCallback =
diff --git a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog_browsertest.cc b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog_browsertest.cc
index 88f2982..d4261be4 100644
--- a/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog_browsertest.cc
@@ -108,13 +108,13 @@
 
 // Test which launches an `OfficeFallbackDialog` which in turn creates an
 // `OfficeFallbackElement`. Tests that the correct title is displayed when the
-// fallback reason is that Drive is unavailable.
+// fallback reason is that Drive authentication is not ready.
 IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
-                       OfficeFallbackDialogWhenDriveUnavailable) {
+                       OfficeFallbackDialogWhenDriveAuthenticationNotReady) {
   // Launch Office Fallback dialog.
   content::WebContents* web_contents =
       LaunchOfficeFallbackDialogAndGetWebContents(
-          files_, FallbackReason::kDriveDisabled,
+          files_, FallbackReason::kDriveAuthenticationNotReady,
           file_manager::file_tasks::kActionIdWebDriveOfficeWord,
           base::DoNothing());
 
@@ -124,11 +124,73 @@
                       ".$('#title').innerText");
   EXPECT_EQ(eval_result.ExtractString(),
             l10n_util::GetStringFUTF8(
-                IDS_OFFICE_FALLBACK_TITLE_DRIVE_UNAVAILABLE,
+                IDS_OFFICE_FALLBACK_TITLE_OFFLINE,
                 files_.front().path().BaseName().LossyDisplayName()));
 }
 
 // Test which launches an `OfficeFallbackDialog` which in turn creates an
+// `OfficeFallbackElement`. Tests that the correct instructions are displayed
+// when the fallback reason is that the disable Drive preference is set.
+IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
+                       OfficeFallbackDialogWhenDisableDrivePreferenceSet) {
+  // Launch Office Fallback dialog.
+  content::WebContents* web_contents =
+      LaunchOfficeFallbackDialogAndGetWebContents(
+          files_, FallbackReason::kDisableDrivePreferenceSet,
+          file_manager::file_tasks::kActionIdWebDriveOfficeWord,
+          base::DoNothing());
+
+  content::EvalJsResult eval_result =
+      content::EvalJs(web_contents,
+                      "document.querySelector('office-fallback')"
+                      ".$('#instructions-message').innerText");
+  EXPECT_EQ(eval_result.ExtractString(),
+            l10n_util::GetStringUTF8(
+                IDS_OFFICE_FALLBACK_INSTRUCTIONS_DISABLE_DRIVE_PREFERENCE));
+}
+
+// Test which launches an `OfficeFallbackDialog` which in turn creates an
+// `OfficeFallbackElement`. Tests that the correct instructions are displayed
+// when the fallback reason is that Drive is unavailable for the account type.
+IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
+                       OfficeFallbackDialogWhenDriveDisabledForAccountType) {
+  // Launch Office Fallback dialog.
+  content::WebContents* web_contents =
+      LaunchOfficeFallbackDialogAndGetWebContents(
+          files_, FallbackReason::kDriveDisabledForAccountType,
+          file_manager::file_tasks::kActionIdWebDriveOfficeWord,
+          base::DoNothing());
+
+  content::EvalJsResult eval_result =
+      content::EvalJs(web_contents,
+                      "document.querySelector('office-fallback')"
+                      ".$('#instructions-message').innerText");
+  EXPECT_EQ(eval_result.ExtractString(),
+            l10n_util::GetStringUTF8(
+                IDS_OFFICE_FALLBACK_INSTRUCTIONS_DRIVE_DISABLED_FOR_ACCOUNT));
+}
+
+// Test which launches an `OfficeFallbackDialog` which in turn creates an
+// `OfficeFallbackElement`. Tests that the correct instructions are displayed
+// when the fallback reason is that Drive has not service.
+IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest,
+                       OfficeFallbackDialogWhenNoDriveService) {
+  // Launch Office Fallback dialog.
+  content::WebContents* web_contents =
+      LaunchOfficeFallbackDialogAndGetWebContents(
+          files_, FallbackReason::kNoDriveService,
+          file_manager::file_tasks::kActionIdWebDriveOfficeWord,
+          base::DoNothing());
+
+  content::EvalJsResult eval_result =
+      content::EvalJs(web_contents,
+                      "document.querySelector('office-fallback')"
+                      ".$('#instructions-message').innerText");
+  EXPECT_EQ(eval_result.ExtractString(),
+            l10n_util::GetStringUTF8(IDS_OFFICE_FALLBACK_INSTRUCTIONS));
+}
+
+// Test which launches an `OfficeFallbackDialog` which in turn creates an
 // `OfficeFallbackElement`. Tests that the cancel button works.
 IN_PROC_BROWSER_TEST_F(OfficeFallbackDialogBrowserTest, ClickCancel) {
   base::RunLoop run_loop;
diff --git a/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.cc b/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.cc
index 2a0f995b..b6e866dd 100644
--- a/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.cc
+++ b/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h"
 #include "base/run_loop.h"
+#include "ui/events/test/event_generator.h"
 
 namespace ash {
 
@@ -77,6 +78,26 @@
                               chrome::GetOSSettingsUrl(subpage)));
 }
 
+// Enters lower-case text into the focused html input element.
+ui::test::InteractiveTestApi::StepBuilder
+DeviceSettingsBaseTest::EnterLowerCaseText(const std::string& text) {
+  return Do([&]() {
+    for (char c : text) {
+      ui::test::EventGenerator(Shell::GetPrimaryRootWindow())
+          .PressKey(static_cast<ui::KeyboardCode>(ui::VKEY_A + (c - 'a')),
+                    ui::EF_NONE, kDeviceId1);
+    }
+  });
+}
+
+ui::test::InteractiveTestApi::StepBuilder
+DeviceSettingsBaseTest::SendKeyPressEvent(ui::KeyboardCode key, int modifier) {
+  return Do([key, modifier]() {
+    ui::test::EventGenerator(Shell::GetPrimaryRootWindow())
+        .PressKey(key, modifier, kDeviceId1);
+  });
+}
+
 void DeviceSettingsBaseTest::SetUpOnMainThread() {
   InteractiveAshTest::SetUpOnMainThread();
 
diff --git a/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h b/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h
index 8ef756d..fcecd1f 100644
--- a/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h
+++ b/chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h
@@ -58,6 +58,14 @@
   void SetPointingStickDevices(
       const std::vector<ui::InputDevice>& pointing_sticks);
 
+  // Enters lower-case text into the focused html input element.
+  ui::test::InteractiveTestApi::StepBuilder EnterLowerCaseText(
+      const std::string& text);
+
+  ui::test::InteractiveTestApi::StepBuilder SendKeyPressEvent(
+      ui::KeyboardCode key,
+      int modifier = ui::EF_NONE);
+
   // Query to pierce through Shadow DOM to find the keyboard.
   const DeepQuery kKeyboardNameQuery{
       "os-settings-ui",
diff --git a/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_modifier_remapping_interactive_uitest.cc b/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_modifier_remapping_interactive_uitest.cc
new file mode 100644
index 0000000..5129e245
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_modifier_remapping_interactive_uitest.cc
@@ -0,0 +1,82 @@
+// Copyright 2024 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/webui/settings/public/constants/routes.mojom-forward.h"
+#include "chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h"
+#include "ui/events/test/event_generator.h"
+
+namespace ash {
+
+namespace {
+
+class DeviceKeyboardModifierRemappingTest : public DeviceSettingsBaseTest {
+ public:
+  // Query to pierce through Shadow DOM to find the touchpad row.
+  const DeepQuery kKeyboardRowQuery{
+      "os-settings-ui",       "os-settings-main",      "main-page-container",
+      "settings-device-page", "#perDeviceKeyboardRow",
+  };
+
+  // Query to pierce through Shadow DOM to find the Settings search box.
+  const DeepQuery kSearchboxQuery{
+      "os-settings-ui", "os-toolbar", "#searchBox", "#search", "#searchInput",
+  };
+
+  // Query to pierce through Shadow DOM to find the Keyboard header.
+  const DeepQuery kCustomizeKeyboardKeysInternalQuery{
+      "os-settings-ui",
+      "os-settings-main",
+      "main-page-container",
+      "settings-device-page",
+      "settings-per-device-keyboard",
+      "settings-per-device-keyboard-subsection",
+      ".remap-keyboard-keys-row-internal",
+  };
+
+  const DeepQuery kCtrlDropdownQuery{
+      "os-settings-ui",       "os-settings-main", "main-page-container",
+      "settings-device-page", "#remap-keys",      "#ctrlKey",
+      "#keyDropdown",         "#dropdownMenu",
+  };
+
+  auto WaitForSearchboxContainsText(const std::string& text) {
+    DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTextFound);
+    StateChange change;
+    change.event = kTextFound;
+    change.where = kSearchboxQuery;
+    change.type = StateChange::Type::kExistsAndConditionTrue;
+    const std::string value_check_function =
+        base::StringPrintf("(e) => { return e.value == '%s';}", text.c_str());
+    change.test_function = value_check_function;
+    return WaitForStateChange(webcontents_id_, change);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(DeviceKeyboardModifierRemappingTest,
+                       KeyboardModifierRemapping) {
+  RunTestSequence(
+      Log("Adding a fake internal keyboard"), SetupInternalKeyboard(),
+      LaunchSettingsApp(chromeos::settings::mojom::kDeviceSectionPath),
+      WaitForElementExists(webcontents_id_, kKeyboardRowQuery),
+      ClickElement(webcontents_id_, kKeyboardRowQuery),
+      WaitForElementTextContains(webcontents_id_, kKeyboardNameQuery,
+                                 "Built-in Keyboard"),
+      ClickElement(webcontents_id_, kCustomizeKeyboardKeysInternalQuery),
+      Log("Remapping the 'Ctrl' key to 'Backspace'"),
+      ExecuteJsAt(webcontents_id_, kCtrlDropdownQuery,
+                  "(el) => {el.selectedIndex = 5; el.dispatchEvent(new "
+                  "Event('change'));}"),
+      ExecuteJsAt(webcontents_id_, kSearchboxQuery,
+                  "(el) => { el.focus(); el.select(); }"),
+      Log("Entering 'redo' into the Settings search box"),
+      EnterLowerCaseText("redo"), WaitForSearchboxContainsText("redo"),
+      Log("Pressing the 'Ctrl' key"),
+      SendKeyPressEvent(ui::KeyboardCode::VKEY_CONTROL),
+      Log("Verifying that the 'Backspace' action was performed and the search "
+          "box now contains the text 'red'"),
+      WaitForSearchboxContainsText("red"));
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_six_pack_keys_interactive_uitest.cc b/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_six_pack_keys_interactive_uitest.cc
index f71327a6..5290ef948 100644
--- a/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_six_pack_keys_interactive_uitest.cc
+++ b/chrome/browser/ui/webui/ash/settings/integration_tests/keyboard_six_pack_keys_interactive_uitest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/webui/settings/public/constants/routes.mojom-forward.h"
 #include "chrome/browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h"
+#include "ui/events/event_constants.h"
 #include "ui/events/test/event_generator.h"
 
 namespace ash {
@@ -14,24 +15,6 @@
 
 class DeviceSettingsSixPackKeysTest : public DeviceSettingsBaseTest {
  public:
-  auto SendKeyPressEvent(ui::KeyboardCode key) {
-    return Do([key]() {
-      ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
-      generator.PressKey(key, ui::EF_NONE, kDeviceId1);
-    });
-  }
-
-  // Enters lower-case text into the focused html input element.
-  auto EnterLowerCaseText(const std::string& text) {
-    return Do([&]() {
-      for (char c : text) {
-        ui::test::EventGenerator(Shell::GetPrimaryRootWindow())
-            .PressKey(static_cast<ui::KeyboardCode>(ui::VKEY_A + (c - 'a')),
-                      ui::EF_NONE, kDeviceId1);
-      }
-    });
-  }
-
   // Query to pierce through Shadow DOM to find the touchpad row.
   const DeepQuery kKeyboardRowQuery{
       "os-settings-ui",       "os-settings-main",      "main-page-container",
@@ -54,10 +37,11 @@
       ".remap-keyboard-keys-row-internal",
   };
 
-  const DeepQuery kCtrlDropdownQuery{
-      "os-settings-ui",       "os-settings-main", "main-page-container",
-      "settings-device-page", "#remap-keys",      "#ctrlKey",
-      "#keyDropdown",         "#dropdownMenu",
+  const DeepQuery kDeleteDropdownQuery{
+      "os-settings-ui",      "os-settings-main",
+      "main-page-container", "settings-device-page",
+      "#remap-keys",         "keyboard-six-pack-key-row:nth-child(1)",
+      "#keyDropdown",        "#dropdownMenu",
   };
 
   auto WaitForSearchboxContainsText(const std::string& text) {
@@ -82,17 +66,19 @@
       WaitForElementTextContains(webcontents_id_, kKeyboardNameQuery,
                                  "Built-in Keyboard"),
       ClickElement(webcontents_id_, kCustomizeKeyboardKeysInternalQuery),
-      Log("Remapping the 'Ctrl' key to 'Backspace'"),
-      ExecuteJsAt(webcontents_id_, kCtrlDropdownQuery,
-                  "(el) => {el.selectedIndex = 5; el.dispatchEvent(new "
+      Log("Remapping the 'Delete' action to 'Alt + Backspace'"),
+      ExecuteJsAt(webcontents_id_, kDeleteDropdownQuery,
+                  "(el) => {el.selectedIndex = 0; el.dispatchEvent(new "
                   "Event('change'));}"),
       ExecuteJsAt(webcontents_id_, kSearchboxQuery,
                   "(el) => { el.focus(); el.select(); }"),
       Log("Entering 'redo' into the Settings search box"),
       EnterLowerCaseText("redo"), WaitForSearchboxContainsText("redo"),
-      Log("Pressing the 'Ctrl' key"),
-      SendKeyPressEvent(ui::KeyboardCode::VKEY_CONTROL),
-      Log("Verifying that the 'Backspace' action was performed and the search "
+      Log("Pressing the 'Left' key"),
+      SendKeyPressEvent(ui::KeyboardCode::VKEY_LEFT),
+      Log("Pressing 'Alt + Backspace' to generate the 'Delete' action"),
+      SendKeyPressEvent(ui::KeyboardCode::VKEY_BACK, ui::EF_ALT_DOWN),
+      Log("Verifying that the 'Delete' action was performed and the search "
           "box now contains the text 'red'"),
       WaitForSearchboxContainsText("red"));
 }
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc b/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
index cea7156..03b0bcc2 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
@@ -502,11 +502,11 @@
            : IDS_SETTINGS_APP_NOTIFICATIONS_DO_NOT_DISTURB_TOGGLE_DESCRIPTION},
       {"appNotificationsLinkToBrowserSettingsDescription",
        IDS_SETTINGS_APP_NOTIFICATIONS_LINK_TO_BROWSER_SETTINGS_DESCRIPTION},
+      {"appNotificationsRowSublabel",
+       IDS_OS_SETTINGS_REVAMP_APP_NOTIFICATIONS_LINK_DESCRIPTION},
       {"appNotificationsCountDescription",
-       kIsRevampEnabled
-           ? IDS_OS_SETTINGS_REVAMP_APP_NOTIFICATIONS_LINK_DESCRIPTION
-           : IDS_SETTINGS_APP_NOTIFICATIONS_SUBLABEL_TEXT},
-      {"appNotificationsDoNotDisturbDescription",
+       IDS_SETTINGS_APP_NOTIFICATIONS_SUBLABEL_TEXT},
+      {"appNotificationsDoNotDisturbEnabledDescription",
        IDS_SETTINGS_APP_NOTIFICATIONS_DND_ENABLED_SUBLABEL_TEXT},
       {"appBadgingToggleLabel", IDS_SETTINGS_APP_BADGING_TOGGLE_LABEL},
       {"appBadgingToggleSublabel", IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL},
diff --git a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.cc b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.cc
index 08af821..78def7b 100644
--- a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.cc
@@ -108,6 +108,9 @@
   source->AddInteger(
       "prerenderStartTimeThreshold",
       features::kNewTabPagePrerenderStartDelayOnMouseHoverByMiliSeconds.Get());
+  source->AddInteger(
+      "preconnectStartTimeThreshold",
+      features::kNewTabPagePreconnectStartDelayOnMouseHoverByMiliSeconds.Get());
 
   // Needed by <cr-most-visited> but not used in
   // chrome://new-tab-page-third-party/.
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 1981aeb..aa25864b 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -3121,10 +3121,6 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    SetUpUserManager(profile());
-#endif
-
     handler_ = std::make_unique<SiteSettingsHandler>(profile());
     handler()->set_web_ui(web_ui());
     handler()->AllowJavascript();
@@ -3169,21 +3165,18 @@
   }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  void SetUpUserManager(TestingProfile* profile) {
-    // On ChromeOS a user account is needed in order to check whether the user
-    // account is affiliated with the device owner for the purposes of applying
-    // enterprise policy.
-    constexpr char kTestUserGaiaId[] = "1111111111";
-    auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>();
-    auto* fake_user_manager_ptr = fake_user_manager.get();
-    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(fake_user_manager));
-
-    auto account_id =
-        AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
-    fake_user_manager_ptr->AddUserWithAffiliation(account_id,
-                                                  /*is_affiliated=*/true);
-    fake_user_manager_ptr->LoginUser(account_id);
+  // On ChromeOS a user account is needed in order to check whether the user
+  // account is affiliated with the device owner for the purposes of applying
+  // enterprise policy.
+  void LogIn(const std::string& email) override {
+    const AccountId account_id = AccountId::FromUserEmail(email);
+    user_manager()->AddUserWithAffiliation(account_id, /*is_affiliated=*/true);
+    ash_test_helper()->test_session_controller_client()->AddUserSession(email);
+    user_manager()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -3218,9 +3211,6 @@
   std::unique_ptr<Browser> browser2_;
   std::unique_ptr<BrowserWindow> window3_;
   std::unique_ptr<Browser> browser3_;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
-#endif
 };
 
 TEST_F(SiteSettingsHandlerInfobarTest, SettingPermissionsTriggersInfobar) {
diff --git a/chrome/browser/usb/web_usb_detector_unittest.cc b/chrome/browser/usb/web_usb_detector_unittest.cc
index 180fc563..6539a9c2 100644
--- a/chrome/browser/usb/web_usb_detector_unittest.cc
+++ b/chrome/browser/usb/web_usb_detector_unittest.cc
@@ -29,13 +29,6 @@
 #include "ui/message_center/public/cpp/notification_delegate.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
-#include "components/user_manager/user_names.h"
-#endif
-
 // These tests are disabled because WebUsbDetector::Initialize is a noop on
 // Windows due to jank and hangs caused by enumerating devices.
 // https://crbug.com/656702
@@ -63,12 +56,6 @@
   ~WebUsbDetectorTest() override = default;
 
   void SetUp() override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    // Inject UserManager to BrowserWithTestWindowTest.
-    // TODO(crbug.com/1494005): Merge into BrowserWithTestWindow.
-    user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::make_unique<ash::FakeChromeUserManager>());
-#endif
     BrowserWithTestWindowTest::SetUp();
 
     BrowserList::SetLastActive(browser());
@@ -86,33 +73,13 @@
   }
 
   void TearDown() override {
-    BrowserWithTestWindowTest::TearDown();
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    user_manager_enabler_.reset();
-#endif
     web_usb_detector_.reset();
+    BrowserWithTestWindowTest::TearDown();
   }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // TODO(crbug.com/1494005): Merge into BrowserWithTestWindowTest.
-  void LogIn(const std::string& email) override {
-    const AccountId account_id = AccountId::FromUserEmail(email);
-    GetFakeUserManager()->AddUser(account_id);
-    GetFakeUserManager()->LoginUser(account_id);
-  }
-#endif
   void Initialize() { web_usb_detector_->Initialize(); }
 
  protected:
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  ash::FakeChromeUserManager* GetFakeUserManager() {
-    return static_cast<ash::FakeChromeUserManager*>(
-        user_manager::UserManager::Get());
-  }
-
-  std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
-#endif
-
   device::FakeUsbDeviceManager device_manager_;
   std::unique_ptr<WebUsbDetector> web_usb_detector_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
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 e461bd74..6b308bc 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
@@ -15,7 +15,6 @@
 #include <type_traits>
 #include <utility>
 
-#include "base/barrier_callback.h"
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/containers/checked_iterators.h"
@@ -29,6 +28,7 @@
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/concurrent_callbacks.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
@@ -1946,28 +1946,20 @@
   // system apps.
   const WebApp* web_app = GetWebApp(params.app_id);
   bool can_multilaunch = !(web_app && web_app->IsSystemApp());
+  base::ConcurrentCallbacks<content::WebContents*> concurrent;
 
-  size_t num_launches = 1;
-  WebAppFileHandlerManager::LaunchInfos file_launch_infos;
   if (can_multilaunch) {
-    file_launch_infos =
+    WebAppFileHandlerManager::LaunchInfos file_launch_infos =
         provider_->os_integration_manager()
             .file_handler_manager()
             .GetMatchingFileHandlerUrls(app_id, params.launch_files);
-    num_launches = file_launch_infos.size();
-  }
-
-  auto launch_complete_barrier = base::BarrierCallback<content::WebContents*>(
-      num_launches, std::move(callback));
-
-  if (can_multilaunch) {
     for (const auto& [url, files] : file_launch_infos) {
       apps::AppLaunchParams params_for_file_launch(
           app_id, params.container, params.disposition, params.launch_source,
           params.display_id, files, nullptr);
       params_for_file_launch.override_url = url;
       LaunchAppWithParams(std::move(params_for_file_launch),
-                          launch_complete_barrier);
+                          concurrent.CreateCallback());
     }
   } else {
     apps::AppLaunchParams params_for_file_launch(
@@ -1981,8 +1973,10 @@
       params_for_file_launch.override_url = GURL(*params.intent->activity_name);
     }
     LaunchAppWithParams(std::move(params_for_file_launch),
-                        launch_complete_barrier);
+                        concurrent.CreateCallback());
   }
+
+  std::move(concurrent).Done(std::move(callback));
 }
 
 void WebAppPublisherHelper::OnLaunchCompleted(
diff --git a/chrome/browser/web_applications/isolated_web_apps/garbage_collect_storage_partitions_command.cc b/chrome/browser/web_applications/isolated_web_apps/garbage_collect_storage_partitions_command.cc
index a00e6f6a..f23d3eab 100644
--- a/chrome/browser/web_applications/isolated_web_apps/garbage_collect_storage_partitions_command.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/garbage_collect_storage_partitions_command.cc
@@ -8,11 +8,11 @@
 #include <string>
 #include <unordered_set>
 
-#include "base/barrier_closure.h"
 #include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/concurrent_closures.h"
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
@@ -49,21 +49,19 @@
 }
 
 void GarbageCollectStoragePartitionsCommand::ResetStorageGarbageCollectPref() {
-  base::OnceClosure callback =
-      base::BindOnce(&GarbageCollectStoragePartitionsCommand::OnPrefReset,
-                     weak_factory_.GetWeakPtr());
-
-  base::RepeatingClosure barrier_closure =
-      base::BarrierClosure(2, std::move(callback));
-
+  base::ConcurrentClosures concurrent;
   // TODO(crbug.com/1477027): change this pref to be stateful instead of
   // resetting to false early.
   profile_->GetPrefs()->SetBoolean(
       prefs::kShouldGarbageCollectStoragePartitions, false);
   // Waits for both prefs to be written to disk before proceeding to prevent
   // repeating crashes.
-  lock_->extensions_manager().ResetStorageGarbageCollectPref(barrier_closure);
-  profile_->GetPrefs()->CommitPendingWrite(barrier_closure);
+  lock_->extensions_manager().ResetStorageGarbageCollectPref(
+      concurrent.CreateClosure());
+  profile_->GetPrefs()->CommitPendingWrite(concurrent.CreateClosure());
+  std::move(concurrent)
+      .Done(base::BindOnce(&GarbageCollectStoragePartitionsCommand::OnPrefReset,
+                           weak_factory_.GetWeakPtr()));
 }
 
 void GarbageCollectStoragePartitionsCommand::OnPrefReset() {
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.cc
index 04321536..9bfb29d 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.cc
@@ -8,12 +8,12 @@
 #include <string>
 #include <vector>
 
-#include "base/barrier_closure.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
+#include "base/functional/concurrent_closures.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
@@ -373,6 +373,12 @@
                      std::move(callback)));
 }
 
+base::OnceClosure&
+WebAppShortcutManager::OnSetCurrentAppShortcutsVersionCallbackForTesting() {
+  static base::NoDestructor<base::OnceClosure> callback;
+  return *callback;
+}
+
 void WebAppShortcutManager::OnIconsRead(
     const webapps::AppId& app_id,
     GetShortcutInfoCallback callback,
@@ -496,25 +502,25 @@
   if (suppress_shortcuts_for_testing_)
     return;
 
-  std::vector<webapps::AppId> app_ids =
-      provider_->registrar_unsafe().GetAppIds();
-  auto done_callback = base::BarrierClosure(
-      app_ids.size() + 1,
-      base::BindOnce(&WebAppShortcutManager::SetCurrentAppShortcutsVersion,
-                     weak_ptr_factory_.GetWeakPtr()));
+  base::ConcurrentClosures concurrent;
 
-  for (const auto& app_id : app_ids) {
+  for (const auto& app_id : provider_->registrar_unsafe().GetAppIds()) {
     UpdateShortcuts(app_id, /*old_name=*/{},
-                    base::IgnoreArgs<Result>(done_callback));
+                    base::IgnoreArgs<Result>(concurrent.CreateClosure()));
   }
 
   UpdateShortcutsForAllAppsCallback update_callback =
       GetUpdateShortcutsForAllAppsCallback();
   if (update_callback) {
-    update_callback.Run(profile_, done_callback);
+    update_callback.Run(profile_, concurrent.CreateClosure());
   } else {
-    done_callback.Run();
+    concurrent.CreateClosure().Run();
   }
+
+  std::move(concurrent)
+      .Done(
+          base::BindOnce(&WebAppShortcutManager::SetCurrentAppShortcutsVersion,
+                         weak_ptr_factory_.GetWeakPtr()));
 }
 
 void WebAppShortcutManager::SetCurrentAppShortcutsVersion() {
@@ -522,6 +528,11 @@
                                    kCurrentAppShortcutsVersion);
   profile_->GetPrefs()->SetString(prefs::kAppShortcutsArch,
                                   CurrentAppShortcutsArch());
+
+  if (base::OnceClosure& callback =
+          OnSetCurrentAppShortcutsVersionCallbackForTesting()) {
+    std::move(callback).Run();
+  }
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.h b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.h
index 9e9f4d42..6b7fa1e 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.h
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager.h
@@ -132,6 +132,8 @@
   static void SetUpdateShortcutsForAllAppsCallback(
       UpdateShortcutsForAllAppsCallback callback);
 
+  static base::OnceClosure& OnSetCurrentAppShortcutsVersionCallbackForTesting();
+
  private:
   void OnIconsRead(const webapps::AppId& app_id,
                    GetShortcutInfoCallback callback,
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager_mac_unittest.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager_mac_unittest.cc
index ce940a6..0d73446 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_manager_mac_unittest.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_manager_mac_unittest.cc
@@ -163,7 +163,13 @@
   // Make sure the updated shortcuts version is not persisted to prefs until
   // after we signal completion of updating.
   EXPECT_EQ(0, profile()->GetPrefs()->GetInteger(prefs::kAppShortcutsVersion));
-  std::move(done_update_callback_).Run();
+  {
+    base::RunLoop run_loop;
+    WebAppShortcutManager::OnSetCurrentAppShortcutsVersionCallbackForTesting() =
+        run_loop.QuitClosure();
+    std::move(done_update_callback_).Run();
+    run_loop.Run();
+  }
   EXPECT_NE(0, profile()->GetPrefs()->GetInteger(prefs::kAppShortcutsVersion));
 
   // Verify shortcut was rebuild, and shortcuts weren't created for the second
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index e3f892a..806a1928 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -10,8 +10,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/barrier_callback.h"
-#include "base/barrier_closure.h"
 #include "base/check_deref.h"
 #include "base/containers/contains.h"
 #include "base/containers/flat_map.h"
@@ -20,6 +18,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/concurrent_closures.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
@@ -160,6 +159,10 @@
                      weak_ptr_factory_.GetWeakPtr(), enable_pwa_support));
 }
 
+void WebAppPolicyManager::Shutdown() {
+  weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
 void WebAppPolicyManager::ReinstallPlaceholderAppIfNecessary(
     const GURL& url,
     ExternallyManagedAppManager::OnceInstallCallback on_complete) {
@@ -453,25 +456,25 @@
   // login and force unregistration, it is still safe, since both functions
   // invoke commands, so the Run on OS login will always be scheduled before the
   // force unregistration, and execution will be synchronous.
-  auto policy_settings_applied_callback = base::BarrierClosure(
-      /*num_closures=*/2,
-      base::BindOnce(&WebAppPolicyManager::OnSyncPolicySettingsCommandsComplete,
-                     weak_ptr_factory_.GetWeakPtr()));
-  ApplyRunOnOsLoginPolicySettings(policy_settings_applied_callback);
-  ApplyForceOSUnregistrationPolicySettings(policy_settings_applied_callback);
+  base::ConcurrentClosures concurrent;
+  ApplyRunOnOsLoginPolicySettings(concurrent.CreateClosure());
+  ApplyForceOSUnregistrationPolicySettings(concurrent.CreateClosure());
+  std::move(concurrent)
+      .Done(base::BindOnce(
+          &WebAppPolicyManager::OnSyncPolicySettingsCommandsComplete,
+          weak_ptr_factory_.GetWeakPtr()));
 }
 
 void WebAppPolicyManager::ApplyRunOnOsLoginPolicySettings(
     base::OnceClosure policy_settings_applied_callback) {
-  std::vector<webapps::AppId> app_ids_to_sync =
-      provider_->registrar_unsafe().GetAppIds();
-  auto callback_for_sync_commands = base::BarrierClosure(
-      app_ids_to_sync.size(), std::move(policy_settings_applied_callback));
+  base::ConcurrentClosures concurrent;
   WebAppProvider* provider = WebAppProvider::GetForLocalAppsUnchecked(profile_);
-  for (const webapps::AppId& app_id : app_ids_to_sync) {
+  for (const webapps::AppId& app_id :
+       provider_->registrar_unsafe().GetAppIds()) {
     provider->scheduler().SyncRunOnOsLoginMode(app_id,
-                                               callback_for_sync_commands);
+                                               concurrent.CreateClosure());
   }
+  std::move(concurrent).Done(std::move(policy_settings_applied_callback));
 }
 
 void WebAppPolicyManager::ApplyForceOSUnregistrationPolicySettings(
@@ -481,7 +484,9 @@
     return;
   }
 
-  base::flat_set<webapps::AppId> app_ids_for_force_unregistration;
+  base::ConcurrentClosures concurrent;
+  SynchronizeOsOptions options;
+  options.force_unregister_os_integration = true;
   for (const auto& [manifest_string, setting] : settings_by_url_) {
     const GURL manifest_id = GURL(manifest_string);
     if (!manifest_id.is_valid()) {
@@ -495,24 +500,12 @@
     }
 
     if (setting.force_unregister_os_integration) {
-      app_ids_for_force_unregistration.insert(app_id);
+      provider_->scheduler().SynchronizeOsIntegration(
+          app_id, concurrent.CreateClosure(), options);
     }
   }
 
-  if (app_ids_for_force_unregistration.empty()) {
-    std::move(policy_settings_applied_callback).Run();
-    return;
-  }
-
-  SynchronizeOsOptions options;
-  options.force_unregister_os_integration = true;
-  auto callback_for_synchronize_complete =
-      base::BarrierClosure(app_ids_for_force_unregistration.size(),
-                           std::move(policy_settings_applied_callback));
-  for (const auto& app_id : app_ids_for_force_unregistration) {
-    provider_->scheduler().SynchronizeOsIntegration(
-        app_id, callback_for_synchronize_complete, options);
-  }
+  std::move(concurrent).Done(std::move(policy_settings_applied_callback));
 }
 
 ExternalInstallOptions WebAppPolicyManager::ParseInstallPolicyEntry(
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index b3b8f0d..6c61e458 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -72,6 +72,7 @@
   // `policy_settings_and_force_installs_applied_` waits for the first
   // `SynchronizeInstalledApps` to finish if it's triggered on `Start`.
   void Start(base::OnceClosure policy_settings_and_force_installs_applied);
+  void Shutdown();
 
   void ReinstallPlaceholderAppIfNecessary(
       const GURL& url,
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
index 677b3246..7247f82 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
@@ -1342,11 +1342,13 @@
   MockAppRegistrarObserver mock_observer;
   app_registrar().AddObserver(&mock_observer);
 
-  base::RunLoop loop;
-  policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
-      loop.QuitClosure());
-  SetWebAppSettingsListPref(profile(), kWebAppSettingInitialConfiguration);
-  loop.Run();
+  {
+    base::RunLoop loop;
+    policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
+        loop.QuitClosure());
+    SetWebAppSettingsListPref(profile(), kWebAppSettingInitialConfiguration);
+    loop.Run();
+  }
 
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy(kWindowedUrl),
             RunOnOsLoginPolicy::kBlocked);
@@ -1355,7 +1357,14 @@
             RunOnOsLoginPolicy::kAllowed);
   EXPECT_EQ(1, mock_observer.GetOnWebAppSettingsPolicyChangedCalledCount());
 
-  SetWebAppSettingsListPref(profile(), kWebAppSettingWithDefaultConfiguration);
+  {
+    base::RunLoop loop;
+    policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
+        loop.QuitClosure());
+    SetWebAppSettingsListPref(profile(),
+                              kWebAppSettingWithDefaultConfiguration);
+    loop.Run();
+  }
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy(kWindowedUrl),
             RunOnOsLoginPolicy::kRunWindowed);
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy(kTabbedUrl), RunOnOsLoginPolicy::kAllowed);
@@ -1415,11 +1424,14 @@
   MockAppRegistrarObserver mock_observer;
   app_registrar().AddObserver(&mock_observer);
 
-  base::RunLoop settings_loop;
-  policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
-      settings_loop.QuitClosure());
-  SetWebAppSettingsListPref(profile(), kWebAppSettingWithDefaultConfiguration);
-  settings_loop.Run();
+  {
+    base::RunLoop settings_loop;
+    policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
+        settings_loop.QuitClosure());
+    SetWebAppSettingsListPref(profile(),
+                              kWebAppSettingWithDefaultConfiguration);
+    settings_loop.Run();
+  }
 
   EXPECT_EQ(1, mock_observer.GetOnWebAppSettingsPolicyChangedCalledCount());
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy(kWindowedUrl),
@@ -1430,14 +1442,18 @@
   EXPECT_EQ(GetUrlRunOnOsLoginPolicy("http://foo.example"),
             RunOnOsLoginPolicy::kBlocked);
 
-  // Now add two sites, one that opens in a window and one that opens in a tab.
-  base::Value::List list;
-  list.Append(GetWindowedItem());
-  list.Append(GetTabbedItem());
-
-  profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
-                                 std::move(list));
-  WaitForAppsToSynchronize();
+  {
+    base::RunLoop loop;
+    policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
+        loop.QuitClosure());
+    // Now add two sites, one that opens in a window and one that opens in a
+    // tab.
+    profile()->GetPrefs()->SetList(
+        prefs::kWebAppInstallForceList,
+        base::Value::List().Append(GetWindowedItem()).Append(GetTabbedItem()));
+    loop.Run();
+  }
+  // WaitForAppsToSynchronize();
 
   provider()->command_manager().AwaitAllCommandsCompleteForTesting();
 
diff --git a/chrome/browser/web_applications/preinstalled_web_app_manager.cc b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
index ebab2be..0acdfb4 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_manager.cc
+++ b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
@@ -10,7 +10,6 @@
 #include <string>
 #include <utility>
 
-#include "base/barrier_closure.h"
 #include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
@@ -19,6 +18,7 @@
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/concurrent_closures.h"
 #include "base/json/json_file_value_serializer.h"
 #include "base/json/json_reader.h"
 #include "base/memory/scoped_refptr.h"
@@ -727,14 +727,12 @@
     return;
   }
 
-  int num_barriers_issued = 2;
-  base::RepeatingClosure barrier_closure = base::BarrierClosure(
-      num_barriers_issued, std::move(load_and_synchronize));
-  device_data_initialized_event_->Post(barrier_closure);
-
+  base::ConcurrentClosures concurrent;
+  device_data_initialized_event_->Post(concurrent.CreateClosure());
   // Make sure ExtensionSystem is ready to know if default apps new installation
   // will be performed.
-  extensions::OnExtensionSystemReady(profile_, barrier_closure);
+  extensions::OnExtensionSystemReady(profile_, concurrent.CreateClosure());
+  std::move(concurrent).Done(std::move(load_and_synchronize));
 }
 
 void PreinstalledWebAppManager::Load(ConsumeInstallOptions callback) {
diff --git a/chrome/browser/web_applications/test/fake_web_app_provider.cc b/chrome/browser/web_applications/test/fake_web_app_provider.cc
index 651681f..e1ac744f 100644
--- a/chrome/browser/web_applications/test/fake_web_app_provider.cc
+++ b/chrome/browser/web_applications/test/fake_web_app_provider.cc
@@ -327,6 +327,7 @@
   }
   if (install_manager_)
     install_manager_->Shutdown();
+  web_app_policy_manager_->Shutdown();
   if (icon_manager_)
     icon_manager_->Shutdown();
   if (install_finalizer_)
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 49557aa..05eb0b6 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -10,12 +10,12 @@
 #include <ostream>
 #include <utility>
 
-#include "base/barrier_closure.h"
 #include "base/check.h"
 #include "base/check_is_test.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/concurrent_closures.h"
 #include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
@@ -314,6 +314,7 @@
   manifest_update_manager_->Shutdown();
   iwa_update_manager_->Shutdown();
   install_manager_->Shutdown();
+  web_app_policy_manager_->Shutdown();
   icon_manager_->Shutdown();
   install_finalizer_->Shutdown();
   registrar_->Shutdown();
@@ -441,31 +442,15 @@
       sync_bridge_.get());
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-  // Note: This does not wait for the call from the ChromeOS
-  // SystemWebAppManager, which is a separate keyed service.
-#if BUILDFLAG(IS_CHROMEOS)
-  const int num_barrier_calls = 3;
-#else
-  const int num_barrier_calls = 2;
-#endif  // BUILDFLAG(IS_CHROMEOS)
-  base::RepeatingClosure external_manager_barrier = base::BarrierClosure(
-      num_barrier_calls,
-      base::BindOnce(
-          [](base::WeakPtr<WebAppProvider> provider) {
-            if (!provider)
-              return;
-            provider->on_external_managers_synchronized_.Signal();
-          },
-          AsWeakPtr()));
+  base::ConcurrentClosures concurrent;
 
   base::OnceClosure on_web_app_policy_manager_done_callback =
-      external_manager_barrier;
-
 #if BUILDFLAG(IS_CHROMEOS)
-  on_web_app_policy_manager_done_callback =
       base::BindOnce(&WebAppRunOnOsLoginManager::Start,
                      web_app_run_on_os_login_manager_->GetWeakPtr())
-          .Then(external_manager_barrier);
+          .Then(concurrent.CreateClosure());
+#else
+      concurrent.CreateClosure();
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
   registrar_->Start();
@@ -473,7 +458,7 @@
   icon_manager_->Start();
   translation_manager_->Start();
   install_manager_->Start();
-  preinstalled_web_app_manager_->Start(external_manager_barrier);
+  preinstalled_web_app_manager_->Start(concurrent.CreateClosure());
   web_app_policy_manager_->Start(
       std::move(on_web_app_policy_manager_done_callback));
   isolated_web_app_installation_manager_->Start();
@@ -485,9 +470,21 @@
   generated_icon_fix_manager_->Start();
   command_manager_->Start();
 #if BUILDFLAG(IS_CHROMEOS)
-  isolated_web_app_policy_manager_->Start(external_manager_barrier);
+  isolated_web_app_policy_manager_->Start(concurrent.CreateClosure());
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+  // Note: This does not wait for the call from the ChromeOS
+  // SystemWebAppManager, which is a separate keyed service.
+  std::move(concurrent)
+      .Done(base::BindOnce(
+          [](base::WeakPtr<WebAppProvider> provider) {
+            if (!provider) {
+              return;
+            }
+            provider->on_external_managers_synchronized_.Signal();
+          },
+          AsWeakPtr()));
+
   on_registry_ready_.Signal();
   is_registry_ready_ = true;
 }
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index 416e552..7fc7f7e 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1707306295-0bd73d0e5d0995d2acd72facfe27d62f9acb0fa2.profdata
+chrome-chromeos-amd64-generic-main-1707350387-b81c34bb7277714ae9511fed8527263c18b9db82.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 7bf567a..2da1b69 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1707328797-1c3efd3955b677a6239ff03320de90c9ee7a2b4c.profdata
+chrome-linux-main-1707350387-c53afe4b59be5e777079ad7ac205233b1e1164b5.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 299221a..53c6bfd 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1707343088-770b4a8ca743711cf1a1eab1f3fa27965e9c83f9.profdata
+chrome-mac-arm-main-1707371451-dec039a3dbf2292385be97878aca13b4e4ae2ec3.profdata
diff --git a/chrome/services/speech/BUILD.gn b/chrome/services/speech/BUILD.gn
index 0f27b93..4e4f114 100644
--- a/chrome/services/speech/BUILD.gn
+++ b/chrome/services/speech/BUILD.gn
@@ -46,6 +46,10 @@
       "cros_speech_recognition_recognizer_impl.cc",
       "cros_speech_recognition_recognizer_impl.h",
     ]
+    deps += [
+        "//chromeos/services/machine_learning/public/mojom",
+    ]
+
   }
 }
 
@@ -53,12 +57,16 @@
   source_set("unit_tests") {
     testonly = true
 
-    sources = [ "audio_source_fetcher_unittest.cc" ]
+    sources = [
+      "audio_source_fetcher_unittest.cc",
+      "cros_speech_recognition_recognizer_impl_test.cc",
+    ]
 
     deps = [
       ":lib",
       "//base",
       "//base/test:test_support",
+      "//chromeos/services/machine_learning/public/mojom",
       "//media/mojo/common",
       "//mojo/public/cpp/bindings",
       "//services/audio/public/cpp:test_support",
diff --git a/chrome/services/speech/cros_speech_recognition_recognizer_impl.cc b/chrome/services/speech/cros_speech_recognition_recognizer_impl.cc
index 69d511f..2e75e22b 100644
--- a/chrome/services/speech/cros_speech_recognition_recognizer_impl.cc
+++ b/chrome/services/speech/cros_speech_recognition_recognizer_impl.cc
@@ -77,6 +77,28 @@
   cros_soda_client_ = std::make_unique<soda::CrosSodaClient>();
 }
 
+chromeos::machine_learning::mojom::SodaMultilangConfigPtr
+CrosSpeechRecognitionRecognizerImpl::AddLiveCaptionLanguagesToConfig(
+    const std::string& primary_language_name,
+    const base::flat_map<std::string, base::FilePath>& config_paths,
+    const std::vector<std::string>& live_caption_languages) {
+  auto multi_lang_config =
+      chromeos::machine_learning::mojom::SodaMultilangConfig::New();
+
+  for (const auto& config_path : config_paths) {
+    if (config_path.first == primary_language_name) {
+      continue;
+    } else if (!base::Contains(live_caption_languages, config_path.first)) {
+      VLOG(1) << "Skipping multilang on captions of " << config_path.first
+              << " as it is not listed as a live caption language.";
+      continue;
+    }
+    multi_lang_config->locale_to_language_pack_map[config_path.first] =
+        config_path.second.value();
+  }
+  return multi_lang_config;
+}
+
 void CrosSpeechRecognitionRecognizerImpl::
     SendAudioToSpeechRecognitionServiceInternal(
         media::mojom::AudioDataS16Ptr buffer) {
@@ -109,22 +131,9 @@
     if (options_->recognition_mode ==
             media::mojom::SpeechRecognitionMode::kCaption &&
         base::FeatureList::IsEnabled(media::kLiveCaptionMultiLanguage)) {
-      auto live_caption_languages = speech::GetLiveCaptionEnabledLanguages();
-      auto multi_lang_config =
-          chromeos::machine_learning::mojom::SodaMultilangConfig::New();
-
-      for (const auto& config_path : config_paths()) {
-        if (config_path.first == primary_language_name()) {
-          continue;
-        } else if (!base::Contains(live_caption_languages, config_path.first)) {
-          VLOG(1) << "Skipping multilang on captions of " << config_path.first
-                  << " as it is not listed as a live caption language.";
-          continue;
-        }
-        multi_lang_config->locale_to_language_pack_map[config_path.first] =
-            config_path.second.value();
-      }
-      config->multi_lang_config = std::move(multi_lang_config);
+      config->multi_lang_config = AddLiveCaptionLanguagesToConfig(
+          primary_language_name(), config_paths(),
+          speech::GetLiveCaptionEnabledLanguages());
     }
 
     config->enable_formatting =
diff --git a/chrome/services/speech/cros_speech_recognition_recognizer_impl.h b/chrome/services/speech/cros_speech_recognition_recognizer_impl.h
index aaba24c..fcce2f1 100644
--- a/chrome/services/speech/cros_speech_recognition_recognizer_impl.h
+++ b/chrome/services/speech/cros_speech_recognition_recognizer_impl.h
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/services/speech/speech_recognition_recognizer_impl.h"
+#include "chromeos/services/machine_learning/public/mojom/soda.mojom.h"
 #include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -54,6 +55,12 @@
 
   void MarkDone() override;
 
+  static chromeos::machine_learning::mojom::SodaMultilangConfigPtr
+  AddLiveCaptionLanguagesToConfig(
+      const std::string& primary_language_name,
+      const base::flat_map<std::string, base::FilePath>& config_paths,
+      const std::vector<std::string>& live_caption_languages);
+
  private:
   std::unique_ptr<soda::CrosSodaClient> cros_soda_client_;
 
diff --git a/chrome/services/speech/cros_speech_recognition_recognizer_impl_test.cc b/chrome/services/speech/cros_speech_recognition_recognizer_impl_test.cc
new file mode 100644
index 0000000..3875928
--- /dev/null
+++ b/chrome/services/speech/cros_speech_recognition_recognizer_impl_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 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/services/speech/cros_speech_recognition_recognizer_impl.h"
+
+#include "chromeos/services/machine_learning/public/mojom/soda.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class CrosSpeechRecognitionRecognizerImplTest : public testing::Test {
+  void SetUp() override {}
+};
+
+TEST_F(CrosSpeechRecognitionRecognizerImplTest, EmptyLangs) {
+  chromeos::machine_learning::mojom::SodaMultilangConfigPtr expected =
+      chromeos::machine_learning::mojom::SodaMultilangConfig::New();
+  auto actual = speech::CrosSpeechRecognitionRecognizerImpl::
+      AddLiveCaptionLanguagesToConfig(
+          "en-US", base::flat_map<std::string, base::FilePath>(),
+          {"en-US", "en-AU"});
+  EXPECT_EQ(expected, actual);
+}
+
+TEST_F(CrosSpeechRecognitionRecognizerImplTest, FilledLangs) {
+  chromeos::machine_learning::mojom::SodaMultilangConfigPtr expected =
+      chromeos::machine_learning::mojom::SodaMultilangConfig::New();
+  base::flat_map<std::string, base::FilePath> config_paths;
+  config_paths["en-AU"] = base::FilePath::FromASCII("/fake/path/aus");
+  config_paths["en-US"] = base::FilePath::FromASCII("/fake/path/usa");
+  config_paths["es-US"] = base::FilePath::FromASCII("/fake/path/espusa");
+  config_paths["es-ES"] = base::FilePath::FromASCII("/fake/path/espesp");
+  config_paths["fr-FR"] = base::FilePath::FromASCII("/fake/path/frafra");
+
+  auto actual = speech::CrosSpeechRecognitionRecognizerImpl::
+      AddLiveCaptionLanguagesToConfig("en-US", config_paths,
+                                      {"en-US", "es-US", "fr-FR"});
+  expected->locale_to_language_pack_map["es-US"] = "/fake/path/espusa";
+  expected->locale_to_language_pack_map["fr-FR"] = "/fake/path/frafra";
+  EXPECT_EQ(expected, actual);
+}
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fe0157ae..cfefc45 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2357,6 +2357,7 @@
       "../browser/metrics/variations/variations_http_headers_browsertest.cc",
       "../browser/metrics/variations/variations_safe_mode_browsertest.cc",
       "../browser/metrics/variations/variations_safe_mode_end_to_end_browsertest.cc",
+      "../browser/metrics/variations/variations_service_browsertest.cc",
       "../browser/navigation_predictor/anchor_element_preloader_browsertest.cc",
       "../browser/navigation_predictor/navigation_predictor_browsertest.cc",
       "../browser/navigation_predictor/navigation_predictor_preconnect_client_browsertest.cc",
@@ -4284,6 +4285,7 @@
         "//components/supervised_user/core/browser/proto",
         "//components/supervised_user/core/common",
         "//components/supervised_user/core/common:test_utils",
+        "//components/variations/service",
       ]
     }
 
@@ -8402,6 +8404,7 @@
       "../browser/ui/ash/network/network_portal_signin_controller_unittest.cc",
       "../browser/ui/ash/network/network_state_notifier_unittest.cc",
       "../browser/ui/ash/network/tether_notification_presenter_unittest.cc",
+      "../browser/ui/ash/picker/picker_client_impl_unittest.cc",
       "../browser/ui/ash/projector/projector_client_impl_unittest.cc",
       "../browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc",
       "../browser/ui/ash/projector/projector_utils_unittest.cc",
@@ -11083,6 +11086,7 @@
           "../browser/ui/webui/ash/settings/integration_tests/add_new_keyboard_interactive_uitest.cc",
           "../browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.cc",
           "../browser/ui/webui/ash/settings/integration_tests/device_settings_base_test.h",
+          "../browser/ui/webui/ash/settings/integration_tests/keyboard_modifier_remapping_interactive_uitest.cc",
           "../browser/ui/webui/ash/settings/integration_tests/keyboard_six_pack_keys_interactive_uitest.cc",
           "../browser/ui/webui/ash/settings/integration_tests/mouse_scroll_acceleration_interactive_uitest.cc",
           "../browser/ui/webui/ash/settings/integration_tests/open_keyboard_subpage_interactive_uitest.cc",
diff --git a/chrome/test/base/browser_with_test_window_test.cc b/chrome/test/base/browser_with_test_window_test.cc
index dca7565..e307011 100644
--- a/chrome/test/base/browser_with_test_window_test.cc
+++ b/chrome/test/base/browser_with_test_window_test.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/ash/crosapi/idle_service_ash.h"
 #include "chrome/browser/ash/crosapi/test_crosapi_dependency_registry.h"
+#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
 #include "components/user_manager/fake_user_manager.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_manager.h"
@@ -69,11 +70,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (!user_manager::UserManager::IsInitialized()) {
-    auto user_manager = std::make_unique<user_manager::FakeUserManager>(
-        g_browser_process->local_state());
-    user_manager_ = user_manager.get();
-    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
-        std::move(user_manager));
+    user_manager_.Reset(std::make_unique<user_manager::FakeUserManager>(
+        g_browser_process->local_state()));
   }
   ash_test_helper_.SetUp();
 #endif
@@ -106,6 +104,9 @@
   std::string profile_name = GetDefaultProfileName();
 #if BUILDFLAG(IS_CHROMEOS)
   LogIn(profile_name);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  SwitchActiveUser(profile_name);
+#endif
 #endif
   profile_ = CreateProfile(profile_name);
 
@@ -155,7 +156,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash_test_helper_.TearDown();
   test_views_delegate_.reset();
-  user_manager_ = nullptr;
+  user_manager_.Reset();
 #elif defined(TOOLKIT_VIEWS)
   views_test_helper_.reset();
 #endif
@@ -221,9 +222,13 @@
 
 TestingProfile* BrowserWithTestWindowTest::CreateProfile(
     const std::string& profile_name) {
-  return profile_manager_->CreateTestingProfile(
+  auto* profile = profile_manager_->CreateTestingProfile(
       profile_name, /*prefs=*/nullptr, /*user_name=*/std::u16string(),
       /*avatar_id=*/0, GetTestingFactories());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  OnUserProfileCreated(profile_name, profile);
+#endif
+  return profile;
 }
 
 void BrowserWithTestWindowTest::DeleteProfile(const std::string& profile_name) {
@@ -268,11 +273,51 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 void BrowserWithTestWindowTest::LogIn(const std::string& email) {
-  // TODO(crbug/1494005): Log in a regular user by default.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const AccountId account_id = AccountId::FromUserEmail(email);
+  user_manager_->AddUser(account_id);
+  ash_test_helper()->test_session_controller_client()->AddUserSession(email);
+  user_manager_->UserLoggedIn(
+      account_id,
+      user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+      /*browser_restart=*/false,
+      /*is_child=*/false);
+#endif
 }
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+void BrowserWithTestWindowTest::OnUserProfileCreated(const std::string& email,
+                                                     Profile* profile) {
+  AccountId account_id = AccountId::FromUserEmail(email);
+  ash::AnnotatedAccountId::Set(profile, account_id);
+  // Do not use the member directly, because another UserManager instance
+  // may be injected.
+  auto* user_manager = user_manager::UserManager::Get();
+  user_manager->OnUserProfileCreated(account_id, profile->GetPrefs());
+  auto observation =
+      std::make_unique<base::ScopedObservation<Profile, ProfileObserver>>(this);
+  observation->Observe(profile);
+  profile_observations_.push_back(std::move(observation));
+}
+
+void BrowserWithTestWindowTest::SwitchActiveUser(const std::string& email) {
+  ash_test_helper()->test_session_controller_client()->SwitchActiveUser(
+      AccountId::FromUserEmail(email));
+}
+
+void BrowserWithTestWindowTest::OnProfileWillBeDestroyed(Profile* profile) {
+  CHECK(
+      base::EraseIf(profile_observations_, [profile](const auto& observation) {
+        return observation->IsObservingSource(profile);
+      }));
+  const AccountId* account_id = ash::AnnotatedAccountId::Get(profile);
+  CHECK(account_id);
+  // Do not use the member directly, because another UserManager instance
+  // may be injected.
+  user_manager::UserManager::Get()->OnUserProfileWillBeDestroyed(*account_id);
+}
+
 ash::ScopedCrosSettingsTestHelper*
 BrowserWithTestWindowTest::GetCrosSettingsHelper() {
   return &cros_settings_test_helper_;
diff --git a/chrome/test/base/browser_with_test_window_test.h b/chrome/test/base/browser_with_test_window_test.h
index 4d1fa37..03f9bd4 100644
--- a/chrome/test/base/browser_with_test_window_test.h
+++ b/chrome/test/base/browser_with_test_window_test.h
@@ -6,12 +6,14 @@
 #define CHROME_TEST_BASE_BROWSER_WITH_TEST_WINDOW_TEST_H_
 
 #include <memory>
+#include <vector>
 
 #include "base/compiler_specific.h"
 #include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/performance_manager/test_support/test_user_performance_tuning_manager_environment.h"
+#include "chrome/browser/profiles/profile_observer.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/test_browser_window.h"
 #include "chrome/test/base/testing_profile.h"
@@ -28,6 +30,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/test/ash_test_helper.h"
 #include "ash/test/ash_test_views_delegate.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
 #include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
 #include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
@@ -82,7 +85,7 @@
 //
 // Subclasses must invoke BrowserWithTestWindowTest::SetUp as it is responsible
 // for creating the various objects of this class.
-class BrowserWithTestWindowTest : public testing::Test {
+class BrowserWithTestWindowTest : public testing::Test, public ProfileObserver {
  public:
   // Trait which requests construction of a hosted app.
   struct HostedApp {};
@@ -149,7 +152,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::AshTestHelper* ash_test_helper() { return &ash_test_helper_; }
-  user_manager::FakeUserManager* user_manager() { return user_manager_; }
+  user_manager::FakeUserManager* user_manager() { return user_manager_.Get(); }
 #endif
 
   // The context to help determine desktop type when creating new Widgets.
@@ -227,6 +230,16 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Handles the post-process for the newly created Profile.
+  // Expected to be called on customizing CreateProfile for ash.
+  virtual void OnUserProfileCreated(const std::string& email, Profile* profile);
+
+  // Switches the active user to the one specified by the email.
+  virtual void SwitchActiveUser(const std::string& email);
+
+  // ProfileObserver:
+  void OnProfileWillBeDestroyed(Profile* profile) override;
+
   ash::ScopedCrosSettingsTestHelper* GetCrosSettingsHelper();
   ash::StubInstallAttributes* GetInstallAttributes();
 #endif
@@ -249,8 +262,11 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
-  raw_ptr<user_manager::FakeUserManager> user_manager_ = nullptr;
-  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+  user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
+      user_manager_;
+  std::vector<
+      std::unique_ptr<base::ScopedObservation<Profile, ProfileObserver>>>
+      profile_observations_;
   std::unique_ptr<crosapi::CrosapiManager> manager_;
   std::unique_ptr<ash::KioskChromeAppManager> kiosk_chrome_app_manager_;
 #endif
diff --git a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.ts b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.ts
index 74d8284..2d8f2530 100644
--- a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.ts
+++ b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.ts
@@ -7,15 +7,16 @@
 import {fakeFirmwareUpdates} from 'chrome://accessory-update/fake_data.js';
 import {FakeUpdateController} from 'chrome://accessory-update/fake_update_controller.js';
 import {FakeUpdateProvider} from 'chrome://accessory-update/fake_update_provider.js';
-import {FirmwareUpdate, UpdateState} from 'chrome://accessory-update/firmware_update.mojom-webui.js';
-import {FirmwareUpdateAppElement} from 'chrome://accessory-update/firmware_update_app.js';
+import {UpdateState} from 'chrome://accessory-update/firmware_update.mojom-webui.js';
+import type {FirmwareUpdate} from 'chrome://accessory-update/firmware_update.mojom-webui.js';
+import type {FirmwareUpdateAppElement} from 'chrome://accessory-update/firmware_update_app.js';
 import {FirmwareUpdateDialogElement} from 'chrome://accessory-update/firmware_update_dialog.js';
 import {getUpdateProvider, setUpdateControllerForTesting, setUpdateProviderForTesting} from 'chrome://accessory-update/mojo_interface_provider.js';
-import {UpdateCardElement} from 'chrome://accessory-update/update_card.js';
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
-import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
+import type {UpdateCardElement} from 'chrome://accessory-update/update_card.js';
 import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
 import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
+import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {mojoString16ToString} from 'chrome://resources/js/mojo_type_util.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
@@ -337,37 +338,4 @@
     assertTrue(!!fakeUpdate);
     assertTrue(getUpdateDialog().open);
   });
-
-  test('UpdatesCSSWhenIsJellyEnabledForFirmwareAppSet', async () => {
-    const linkEl = document.createElement('link');
-    const disabledUrl = 'chrome://resources/chromeos/colors/cros_styles.css';
-    linkEl.href = disabledUrl;
-    document.head.appendChild(linkEl);
-
-    // Setup for jelly disabled.
-    loadTimeData.overrideValues({
-      isJellyEnabledForFirmwareUpdate: false,
-    });
-    initializePage();
-    await flushTasks();
-
-    assertTrue(linkEl.href.includes(disabledUrl), 'Has cros_styles');
-
-    // Clear app element.
-    page?.remove();
-    await flushTasks();
-
-    // Setup for jelly disabled.
-    loadTimeData.overrideValues({
-      isJellyEnabledForFirmwareUpdate: true,
-    });
-    initializePage();
-    await flushTasks();
-
-    const enabledUrl = 'chrome://theme/colors.css';
-    assertTrue(linkEl.href.includes(enabledUrl), 'Has theme/colors');
-
-    // Clean up.
-    document.head.removeChild(linkEl);
-  });
 });
diff --git a/chrome/test/data/webui/chromeos/print_management/print_management_test.ts b/chrome/test/data/webui/chromeos/print_management/print_management_test.ts
index 0f4ea88..297c9aa 100644
--- a/chrome/test/data/webui/chromeos/print_management/print_management_test.ts
+++ b/chrome/test/data/webui/chromeos/print_management/print_management_test.ts
@@ -5,14 +5,14 @@
 import 'chrome://print-management/print_management.js';
 import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';
 
-import {IronIconElement} from '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import type {IronIconElement} from '//resources/polymer/v3_0/iron-icon/iron-icon.js';
 import {setMetadataProviderForTesting, setPrintManagementHandlerForTesting} from 'chrome://print-management/mojo_interface_provider.js';
-import {PrintJobEntryElement} from 'chrome://print-management/print_job_entry.js';
-import {PrintManagementElement} from 'chrome://print-management/print_management.js';
+import type {PrintJobEntryElement} from 'chrome://print-management/print_job_entry.js';
+import type {PrintManagementElement} from 'chrome://print-management/print_management.js';
 import {PrinterSetupInfoElement} from 'chrome://print-management/printer_setup_info.js';
-import {ActivePrintJobInfo, ActivePrintJobState, CompletedPrintJobInfo, LaunchSource, PrinterErrorCode, PrintingMetadataProviderInterface, PrintJobCompletionStatus, PrintJobInfo, PrintJobsObserverRemote} from 'chrome://print-management/printing_manager.mojom-webui.js';
-import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
-import {assert} from 'chrome://resources/js/assert.js';
+import {ActivePrintJobState, LaunchSource, PrinterErrorCode, PrintJobCompletionStatus} from 'chrome://print-management/printing_manager.mojom-webui.js';
+import type {ActivePrintJobInfo, CompletedPrintJobInfo, PrintingMetadataProviderInterface, PrintJobInfo, PrintJobsObserverRemote} from 'chrome://print-management/printing_manager.mojom-webui.js';
+import type {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.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';
@@ -911,41 +911,6 @@
     verifyPrintJobs(expectedHistoryList, getHistoryPrintJobEntries(page!));
   });
 
-  test('IsJellyEnabledForPrintManagementUpdatesCSS', async () => {
-    const disabledUrl = 'chrome://resources/chromeos/colors/cros_styles.css';
-    const linkEl = document.createElement('link');
-    linkEl.href = disabledUrl;
-    document.head.appendChild(linkEl);
-
-    // Setup for disabled test.
-    loadTimeData.overrideValues({
-      isJellyEnabledForPrintManagement: false,
-    });
-
-    await initializePrintManagementApp([]);
-
-    assertTrue(linkEl.href.includes(disabledUrl));
-
-    // Clean up element.
-    page?.remove();
-    page = null;
-    assert(window.trustedTypes);
-    document.body.innerHTML = window.trustedTypes.emptyHTML;
-
-    // Setup for enabled test.
-    loadTimeData.overrideValues({
-      isJellyEnabledForPrintManagement: true,
-    });
-
-    await initializePrintManagementApp([]);
-
-    const enabledUrl = 'chrome://theme/colors.css';
-    assertTrue(linkEl.href.includes(enabledUrl));
-
-    // Clean up test element.
-    document.head.removeChild(linkEl);
-  });
-
   // Verify 'manage printers' button in header does not show when setup
   // assistance flag is off.
   test('HeaderManagePrinterButton_HiddenWhenFlagOff', async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/date_time_page/timezone_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/date_time_page/timezone_subpage_test.ts
index 989f6ee..f80d690 100644
--- a/chrome/test/data/webui/settings/chromeos/date_time_page/timezone_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/date_time_page/timezone_subpage_test.ts
@@ -8,40 +8,62 @@
 import {CrSettingsPrefs, GeolocationAccessLevel, Router, routes} from 'chrome://os-settings/os_settings.js';
 import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
 import {assert} from 'chrome://resources/js/assert.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
-suite('<timezone-subpage>', function() {
-  let timezoneSubpage: TimezoneSubpageElement;
+let timezoneSubpage: TimezoneSubpageElement;
 
-  setup(async function() {
-    const prefElement = document.createElement('settings-prefs');
-    document.body.appendChild(prefElement);
+async function init(): Promise<void> {
+  const prefElement = document.createElement('settings-prefs');
+  document.body.appendChild(prefElement);
 
-    await CrSettingsPrefs.initialized;
-    timezoneSubpage = document.createElement('timezone-subpage');
-    timezoneSubpage.prefs = {
-      ...prefElement.prefs,
-      ash: {
-        user: {
-          geolocation_access_level: {
-            key: 'ash.user.geolocation_access_level',
-            type: chrome.settingsPrivate.PrefType.NUMBER,
-            value: GeolocationAccessLevel.ALLOWED,
-          },
+  await CrSettingsPrefs.initialized;
+  timezoneSubpage = document.createElement('timezone-subpage');
+  timezoneSubpage.prefs = {
+    ...prefElement.prefs,
+    ash: {
+      user: {
+        geolocation_access_level: {
+          key: 'ash.user.geolocation_access_level',
+          type: chrome.settingsPrivate.PrefType.NUMBER,
+          value: GeolocationAccessLevel.ALLOWED,
         },
       },
-    };
+    },
+  };
 
-    document.body.appendChild(timezoneSubpage);
+  document.body.appendChild(timezoneSubpage);
+  await flushTasks();
+}
+
+function testTeardown() {
+  timezoneSubpage.remove();
+  CrSettingsPrefs.resetForTesting();
+  Router.getInstance().resetRouteForTesting();
+}
+
+suite('<timezone-subpage> with logged-in user', () => {
+  setup(async () => {
+    await init();
   });
 
-  teardown(function() {
-    timezoneSubpage.remove();
-    CrSettingsPrefs.resetForTesting();
-    Router.getInstance().resetRouteForTesting();
+  teardown(() => {
+    testTeardown();
+  });
+
+  test('timezone radio group is enabled', async () => {
+    // Enable automatic timezone.
+    timezoneSubpage.setPrefValue(
+        'generated.resolve_timezone_by_geolocation_on_off', true);
+    await flushTasks();
+
+    const timezoneRadioGroup =
+        timezoneSubpage.shadowRoot!.querySelector<SettingsRadioGroupElement>(
+            '#timeZoneRadioGroup');
+    assert(timezoneRadioGroup);
+    assertFalse(timezoneRadioGroup.disabled);
   });
 
   test('Timezone autodetect by geolocation radio', async () => {
@@ -53,13 +75,13 @@
     // Resolve timezone by geolocation is on.
     timezoneSubpage.setPrefValue(
         'generated.resolve_timezone_by_geolocation_on_off', true);
-    flush();
+    await flushTasks();
     assertEquals('true', timezoneRadioGroup.selected);
 
     // Resolve timezone by geolocation is off.
     timezoneSubpage.setPrefValue(
         'generated.resolve_timezone_by_geolocation_on_off', false);
-    flush();
+    await flushTasks();
     assertEquals('false', timezoneRadioGroup.selected);
 
     // Set timezone autodetect on by clicking the 'on' radio.
@@ -115,7 +137,6 @@
         timezoneSubpage.setPrefValue(
             'generated.resolve_timezone_by_geolocation_on_off', true);
 
-
         // Geolocation is allowed by default, the warning text should be hidden.
         assertFalse(isVisible(
             timezoneSubpage.shadowRoot!.querySelector('#warningText')));
@@ -124,7 +145,31 @@
         timezoneSubpage.setPrefValue(
             'ash.user.geolocation_access_level',
             GeolocationAccessLevel.DISALLOWED);
-        flush();
+        await flushTasks();
         assertTrue(!!timezoneSubpage.shadowRoot!.querySelector('#warningText'));
       });
 });
+
+suite('<timezone-subpage> with guest user', () => {
+  setup(async () => {
+    loadTimeData.overrideValues({isGuest: true});
+    await init();
+  });
+
+  teardown(() => {
+    testTeardown();
+  });
+
+  test('timezone radio group is disabled', async () => {
+    // Enable automatic timezone.
+    timezoneSubpage.setPrefValue(
+        'generated.resolve_timezone_by_geolocation_on_off', true);
+    await flushTasks();
+
+    const timezoneRadioGroup =
+        timezoneSubpage.shadowRoot!.querySelector<SettingsRadioGroupElement>(
+            '#timeZoneRadioGroup');
+    assert(timezoneRadioGroup);
+    assertTrue(timezoneRadioGroup.disabled);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_apps_page/os_apps_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_apps_page/os_apps_page_test.ts
index 44f6b810..abfc45ed 100644
--- a/chrome/test/data/webui/settings/chromeos/os_apps_page/os_apps_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_apps_page/os_apps_page_test.ts
@@ -300,20 +300,37 @@
       flush();
     });
 
+    function queryAppNotificationsRow(): CrLinkRowElement|null {
+      return appsPage.shadowRoot!.querySelector<CrLinkRowElement>(
+          '#appNotificationsRow');
+    }
+
     if (isRevampWayfindingEnabled) {
       test('App notification row displays helpful description', async () => {
-        const rowLink = appsPage.shadowRoot!.querySelector<CrLinkRowElement>(
-            '#appNotificationsRow');
+        const rowLink = queryAppNotificationsRow();
         assertTrue(!!rowLink);
         assertTrue(isVisible(rowLink));
         assertEquals(
             'Manage app notifications, Do Not Disturb, and app badging',
             rowLink.subLabel);
       });
+
+      test(
+          'App notification row has same sublabel when Do Not Disturb is on',
+          async () => {
+            appsPage.set('isDndEnabled_', true);
+            await flushTasks();
+
+            const rowLink = queryAppNotificationsRow();
+            assertTrue(!!rowLink);
+            assertTrue(isVisible(rowLink));
+            assertEquals(
+                'Manage app notifications, Do Not Disturb, and app badging',
+                rowLink.subLabel);
+          });
     } else {
       test('App notification row displays number of apps', async () => {
-        const rowLink = appsPage.shadowRoot!.querySelector<CrLinkRowElement>(
-            '#appNotificationsRow');
+        const rowLink = queryAppNotificationsRow();
         assertTrue(!!rowLink);
         assertTrue(isVisible(rowLink));
         // Test default is to have 0 apps.
@@ -341,6 +358,16 @@
         await flushTasks();
         assertEquals('1 apps', rowLink.subLabel);
       });
+
+      test('App notification row shows when Do Not Disturb is on', async () => {
+        appsPage.set('isDndEnabled_', true);
+        await flushTasks();
+
+        const rowLink = queryAppNotificationsRow();
+        assertTrue(!!rowLink);
+        assertTrue(isVisible(rowLink));
+        assertEquals('Do Not Disturb enabled', rowLink.subLabel);
+      });
     }
 
     test('Manage isolated web apps row', () => {
diff --git a/chromeos/components/kiosk/kiosk_test_utils.cc b/chromeos/components/kiosk/kiosk_test_utils.cc
index cb59d8c..e57fe83 100644
--- a/chromeos/components/kiosk/kiosk_test_utils.cc
+++ b/chromeos/components/kiosk/kiosk_test_utils.cc
@@ -20,13 +20,13 @@
 
 namespace chromeos {
 
-void SetUpFakeKioskSession() {
+void SetUpFakeKioskSession(const std::string& email) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   CHECK(user_manager::UserManager::Get());
 
   auto* user_manager = static_cast<user_manager::UserManagerBase*>(
       user_manager::UserManager::Get());
-  auto account_id = AccountId::FromUserEmail("example@example.com");
+  auto account_id = AccountId::FromUserEmail(email);
   auto* user = user_manager->AddKioskAppUserForTesting(
       account_id,
       user_manager::FakeUserManager::GetFakeUsernameHash(account_id));
diff --git a/chromeos/components/kiosk/kiosk_test_utils.h b/chromeos/components/kiosk/kiosk_test_utils.h
index 2e8cbd62..699052b 100644
--- a/chromeos/components/kiosk/kiosk_test_utils.h
+++ b/chromeos/components/kiosk/kiosk_test_utils.h
@@ -5,15 +5,17 @@
 #ifndef CHROMEOS_COMPONENTS_KIOSK_KIOSK_TEST_UTILS_H_
 #define CHROMEOS_COMPONENTS_KIOSK_KIOSK_TEST_UTILS_H_
 
+#include <string>
+
 namespace chromeos {
 
 // Sets up a fake kiosk session for unit tests.
 // Make sure to enable `UserManagerBase` to be returned from
 // `UserManager::Get()` prior to calling this function.
-extern void SetUpFakeKioskSession();
-
-// Tears down a fake kiosk session for unit tests.
-extern void TearDownFakeKioskSession();
+// TODO(b/40286020): remove the default parameter. That is only for transition
+// purpose.
+extern void SetUpFakeKioskSession(
+    const std::string& email = "example@example.com");
 
 }  // namespace chromeos
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 84bcb1c..cb0b4835 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -220,7 +220,12 @@
              "RoundedWindows",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables a content cache for FileSystemProvider extensions.
+// Enables CloudFileSystem for FileSystemProvider extensions.
+BASE_FEATURE(kFileSystemProviderCloudFileSystem,
+             "FileSystemProviderCloudFileSystem",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Enables a content cache in CloudFileSystem for FileSystemProvider extensions.
 BASE_FEATURE(kFileSystemProviderContentCache,
              "FileSystemProviderContentCache",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -312,8 +317,15 @@
   return base::FeatureList::IsEnabled(kEssentialSearch);
 }
 
+bool IsFileSystemProviderCloudFileSystemEnabled() {
+  return base::FeatureList::IsEnabled(kFileSystemProviderCloudFileSystem);
+}
+
 bool IsFileSystemProviderContentCacheEnabled() {
-  return base::FeatureList::IsEnabled(kFileSystemProviderContentCache);
+  // The `ContentCache` will be owned by the `CloudFileSystem`. Thus, the
+  // `FileSystemProviderCloudFileSystem` flag has to be enabled too.
+  return IsFileSystemProviderCloudFileSystemEnabled() &&
+         base::FeatureList::IsEnabled(kFileSystemProviderContentCache);
 }
 
 bool IsIWAForTelemetryExtensionAPIEnabled() {
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 0a41962..d77863ad 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -89,6 +89,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kMicrosoftOneDriveIntegrationForEnterprise);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+BASE_DECLARE_FEATURE(kFileSystemProviderCloudFileSystem);
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kFileSystemProviderContentCache);
 
 // Keep alphabetized.
@@ -114,6 +116,8 @@
 bool IsDeskProfilesEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsEssentialSearchEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+bool IsFileSystemProviderCloudFileSystemEnabled();
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 bool IsFileSystemProviderContentCacheEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 bool IsIWAForTelemetryExtensionAPIEnabled();
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index ecfd839..378107ca 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -1129,8 +1129,8 @@
 // parameters here. If a new parameter is added and its value is only known
 // after the user has logged in, please update BrowserPostLoginParams as well.
 //
-// Next version: 75
-// Next id: 75
+// Next version: 77
+// Next id: 77
 [Stable, RenamedFrom="crosapi.mojom.LacrosInitParams"]
 struct BrowserInitParams {
   // This is ash-chrome's version of the Crosapi interface. This is used by
@@ -1574,6 +1574,12 @@
   // If true, "Compose" will be disabled. Only applies on chromeos devices.
   [MinVersion=75]
   bool should_disable_chrome_compose_on_chromeos@75;
+
+  // The seed that is used to randomize the limited entropy synthetic trial. The
+  // group assignment of this trial needs to be the same between Lacros and ash
+  // chrome.
+  [MinVersion=76]
+  uint64 limited_entropy_synthetic_trial_seed@76;
 };
 
 // BrowserPostLoginParams is the subset of parameters in BrowserInitParams
diff --git a/chromeos/startup/browser_params_proxy.cc b/chromeos/startup/browser_params_proxy.cc
index 00a4d7ec..f82aae8 100644
--- a/chromeos/startup/browser_params_proxy.cc
+++ b/chromeos/startup/browser_params_proxy.cc
@@ -162,6 +162,10 @@
   return BrowserInitParams::Get()->entropy_source;
 }
 
+uint64_t BrowserParamsProxy::LimitedEntropySyntheticTrialSeed() const {
+  return BrowserInitParams::Get()->limited_entropy_synthetic_trial_seed;
+}
+
 uint64_t BrowserParamsProxy::UkmClientId() const {
   return BrowserInitParams::Get()->ukm_client_id;
 }
diff --git a/chromeos/startup/browser_params_proxy.h b/chromeos/startup/browser_params_proxy.h
index 1e1a9bf..961e542 100644
--- a/chromeos/startup/browser_params_proxy.h
+++ b/chromeos/startup/browser_params_proxy.h
@@ -77,6 +77,8 @@
 
   const std::optional<std::string>& MetricsServiceClientId() const;
 
+  uint64_t LimitedEntropySyntheticTrialSeed() const;
+
   const crosapi::mojom::EntropySourcePtr& EntropySource() const;
 
   uint64_t UkmClientId() const;
diff --git a/clank b/clank
index cbd13ba..ab0b951 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit cbd13ba3774974cb20cd7dbaf2acc4860fe6d0f4
+Subproject commit ab0b95197af40620e76f2054c376e0068d43a968
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index c919529f..35f8cc71 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -1361,13 +1361,20 @@
 
   GetHitTestMask(mask);
 
-  SkMatrix matrix;
   const float scale = GetScale();
-  // `matrix` should be on Widget space, so use `origin_` as an origin instead
-  // of the root surface origin.
-  matrix.setScaleTranslate(SkFloatToScalar(1.0f / scale),
-                           SkFloatToScalar(1.0f / scale), origin_.x(),
-                           origin_.y());
+
+  // `mask` should be in the Widget's coordinates, but the above
+  // GetHitTestMask() call returns the mask in the root_surface's coordinates.
+  // We need to offset the difference.
+  auto widget_bounds = widget_->GetWindowBoundsInScreen().origin();
+  auto root_surface_bounds =
+      root_surface()->window()->GetBoundsInScreen().origin();
+  auto offset = root_surface_bounds - widget_bounds.OffsetFromOrigin();
+
+  SkMatrix matrix;
+  matrix.setScaleTranslate(
+      SkFloatToScalar(1.0f / scale), SkFloatToScalar(1.0f / scale),
+      SkIntToScalar(offset.x()), SkIntToScalar(offset.y()));
   mask->transform(matrix);
 }
 
diff --git a/components/exo/shell_surface_base.h b/components/exo/shell_surface_base.h
index 8c91421..6401e14d0 100644
--- a/components/exo/shell_surface_base.h
+++ b/components/exo/shell_surface_base.h
@@ -465,6 +465,7 @@
 
   raw_ptr<views::Widget> widget_ = nullptr;
   bool movement_disabled_ = false;
+  // This value is in the screen coordinates.
   gfx::Point origin_;
 
   // Container Window Id (see ash/public/cpp/shell_window_ids.h)
diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc
index 84719ad..571d2e24 100644
--- a/components/exo/shell_surface_unittest.cc
+++ b/components/exo/shell_surface_unittest.cc
@@ -4499,4 +4499,16 @@
   surface2->RemoveSurfaceObserver(&observer2);
 }
 
+TEST_F(ShellSurfaceTest, GetWidgetHitTestMask) {
+  auto shell_surface = test::ShellSurfaceBuilder({256, 256})
+                           .SetOrigin({100, 100})
+                           .BuildShellSurface();
+
+  EXPECT_TRUE(shell_surface->WidgetHasHitTestMask());
+  SkPath mask;
+  shell_surface->GetWidgetHitTestMask(&mask);
+  // Returned HitMask should be in the widget local coordinates.
+  EXPECT_EQ(SkRect::MakeLTRB(0, 0, 256, 256), mask.getBounds());
+}
+
 }  // namespace exo
diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn
index a8db7f94..3561960 100644
--- a/components/exo/wayland/BUILD.gn
+++ b/components/exo/wayland/BUILD.gn
@@ -21,6 +21,8 @@
     "client_tracker.h",
     "content_type.cc",
     "content_type.h",
+    "output_configuration_change.cc",
+    "output_configuration_change.h",
     "output_controller.cc",
     "output_controller.h",
     "output_metrics.cc",
@@ -82,6 +84,8 @@
     "xdg_shell.h",
     "zaura_output_manager.cc",
     "zaura_output_manager.h",
+    "zaura_output_manager_v2.cc",
+    "zaura_output_manager_v2.h",
     "zaura_shell.cc",
     "zaura_shell.h",
     "zcr_alpha_compositing.cc",
@@ -152,6 +156,7 @@
     "//chromeos/ui/base",
     "//chromeos/ui/frame",
     "//components/exo",
+    "//components/exo/wayland/protocol:aura_output_management_protocol",
     "//components/exo/wayland/protocol:aura_shell_protocol",
     "//components/exo/wayland/protocol:chrome_color_management_protocol",
     "//components/exo/wayland/protocol:overlay_prioritizer_protocol",
@@ -307,6 +312,7 @@
     "wayland_positioner_unittest.cc",
     "wl_data_device_manager_unittest.cc",
     "zaura_output_manager_unittest.cc",
+    "zaura_output_manager_v2_unittest.cc",
     "zaura_shell_unittest.cc",
     "zcr_remote_shell_impl_unittest.cc",
     "zcr_remote_shell_unittest.cc",
@@ -324,6 +330,7 @@
     "//components/exo",
     "//components/exo:test_support",
     "//components/exo/wayland/fuzzer:unit_tests",
+    "//components/exo/wayland/protocol:aura_output_management_protocol",
     "//components/exo/wayland/protocol:aura_shell_protocol",
     "//components/exo/wayland/protocol:overlay_prioritizer_protocol",
     "//components/exo/wayland/protocol:surface_augmenter_protocol",
@@ -373,6 +380,7 @@
   ]
 
   public_deps = [
+    "//components/exo/wayland/protocol:aura_output_management_protocol",
     "//components/exo/wayland/protocol:aura_shell_protocol",
     "//components/exo/wayland/protocol:chrome_color_management_protocol",
     "//components/exo/wayland/protocol:overlay_prioritizer_protocol",
diff --git a/components/exo/wayland/clients/client_helper.cc b/components/exo/wayland/clients/client_helper.cc
index 04f0aa4e8..dfe952c 100644
--- a/components/exo/wayland/clients/client_helper.cc
+++ b/components/exo/wayland/clients/client_helper.cc
@@ -56,6 +56,7 @@
 DEFAULT_DELETER(struct wp_presentation_feedback,
                 wp_presentation_feedback_destroy)
 DEFAULT_DELETER(zaura_output_manager, zaura_output_manager_destroy)
+DEFAULT_DELETER(zaura_output_manager_v2, zaura_output_manager_v2_destroy)
 DEFAULT_DELETER(zaura_shell, zaura_shell_destroy)
 DEFAULT_DELETER(zaura_surface, zaura_surface_destroy)
 DEFAULT_DELETER(zaura_toplevel, zaura_toplevel_destroy)
diff --git a/components/exo/wayland/clients/client_helper.h b/components/exo/wayland/clients/client_helper.h
index e03b509..ece29c0 100644
--- a/components/exo/wayland/clients/client_helper.h
+++ b/components/exo/wayland/clients/client_helper.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_EXO_WAYLAND_CLIENTS_CLIENT_HELPER_H_
 
 #include <alpha-compositing-unstable-v1-client-protocol.h>
+#include <aura-output-management-client-protocol.h>
 #include <aura-shell-client-protocol.h>
 #include <chrome-color-management-client-protocol.h>
 #include <content-type-v1-client-protocol.h>
@@ -86,6 +87,7 @@
 DEFAULT_DELETER_FDECL(wp_presentation)
 DEFAULT_DELETER_FDECL(struct wp_presentation_feedback)
 DEFAULT_DELETER_FDECL(zaura_output_manager)
+DEFAULT_DELETER_FDECL(zaura_output_manager_v2)
 DEFAULT_DELETER_FDECL(zaura_shell)
 DEFAULT_DELETER_FDECL(zaura_surface)
 DEFAULT_DELETER_FDECL(zaura_toplevel)
diff --git a/components/exo/wayland/clients/globals.cc b/components/exo/wayland/clients/globals.cc
index 29546b9..bed1096 100644
--- a/components/exo/wayland/clients/globals.cc
+++ b/components/exo/wayland/clients/globals.cc
@@ -28,6 +28,10 @@
                      uint32_t version) {
   Globals* globals = static_cast<Globals*>(data);
 
+  if (globals->observer_for_testing_) {
+    globals->observer_for_testing_->OnRegistryGlobal(id, interface, version);
+  }
+
 #define BIND(interface_type, global_member)                        \
   if (strcmp(interface, #interface_type) == 0) {                   \
     globals->global_member.reset(                                  \
@@ -57,6 +61,7 @@
   BIND(wp_presentation, presentation)
   BIND(zaura_shell, aura_shell)
   BIND(zaura_output_manager, aura_output_manager)
+  BIND(zaura_output_manager_v2, aura_output_manager_v2)
   BIND(zwp_linux_dmabuf_v1, linux_dmabuf)
   BIND(wl_subcompositor, subcompositor)
   BIND(zcr_color_manager_v1, color_manager)
@@ -81,6 +86,11 @@
 
 void RegistryRemover(void* data, wl_registry* registry, uint32_t id) {
   LOG(WARNING) << "Got a registry losing event for " << id;
+
+  Globals* globals = static_cast<Globals*>(data);
+  if (globals->observer_for_testing_) {
+    globals->observer_for_testing_->OnRegistryGlobalRemove(id);
+  }
 }
 
 wl_registry_listener registry_listener = {RegistryHandler, RegistryRemover};
diff --git a/components/exo/wayland/clients/globals.h b/components/exo/wayland/clients/globals.h
index 0ef31e9..3a6cdeb 100644
--- a/components/exo/wayland/clients/globals.h
+++ b/components/exo/wayland/clients/globals.h
@@ -48,6 +48,19 @@
   void Init(wl_display* display,
             base::flat_map<std::string, uint32_t> in_requested_versions);
 
+  // TestObserver is an interface that observes certain events for testing
+  // purposes.
+  class TestObserver {
+   public:
+    virtual void OnRegistryGlobal(uint32_t id,
+                                  const char* interface,
+                                  uint32_t version) = 0;
+    virtual void OnRegistryGlobalRemove(uint32_t id) = 0;
+  };
+  void set_observer_for_testing(TestObserver* observer) {
+    observer_for_testing_ = observer;
+  }
+
   std::unique_ptr<wl_registry> registry;
 
   std::vector<Object<wl_output>> outputs;
@@ -61,6 +74,7 @@
   Object<zaura_shell> aura_shell;
   std::vector<Object<zaura_output>> aura_outputs;
   Object<zaura_output_manager> aura_output_manager;
+  Object<zaura_output_manager_v2> aura_output_manager_v2;
   Object<xdg_wm_base> xdg_wm_base;
   Object<zwp_fullscreen_shell_v1> fullscreen_shell;
   Object<zwp_input_timestamps_manager_v1> input_timestamps_manager;
@@ -77,6 +91,8 @@
   Object<wl_data_device_manager> data_device_manager;
 
   base::flat_map<std::string, uint32_t> requested_versions;
+
+  raw_ptr<TestObserver> observer_for_testing_;
 };
 
 }  // namespace exo::wayland::clients
diff --git a/components/exo/wayland/clients/test/client_version_test.cc b/components/exo/wayland/clients/test/client_version_test.cc
index 05c96aa1..b10298e 100644
--- a/components/exo/wayland/clients/test/client_version_test.cc
+++ b/components/exo/wayland/clients/test/client_version_test.cc
@@ -5,6 +5,7 @@
 #include "components/exo/wayland/clients/test/client_version_test.h"
 
 #include <alpha-compositing-unstable-v1-client-protocol.h>
+#include <aura-output-management-server-protocol.h>
 #include <aura-shell-server-protocol.h>
 #include <chrome-color-management-server-protocol.h>
 #include <content-type-v1-server-protocol.h>
@@ -65,6 +66,7 @@
   std::unique_ptr<wl_seat> wl_seat;
   std::unique_ptr<wp_presentation> wp_presentation;
   std::unique_ptr<zaura_output_manager> zaura_output_manager;
+  std::unique_ptr<zaura_output_manager_v2> zaura_output_manager_v2;
   std::unique_ptr<zaura_shell> zaura_shell;
   std::unique_ptr<zwp_linux_dmabuf_v1> zwp_linux_dmabuf_v1;
   std::unique_ptr<wl_subcompositor> wl_subcompositor;
@@ -162,6 +164,7 @@
           REGISTRY_CALLBACK(wl_seat, wl_seat),
           REGISTRY_CALLBACK(wp_presentation, wp_presentation),
           REGISTRY_CALLBACK(zaura_output_manager, zaura_output_manager),
+          REGISTRY_CALLBACK(zaura_output_manager_v2, zaura_output_manager_v2),
           REGISTRY_CALLBACK(zaura_shell, zaura_shell),
           REGISTRY_CALLBACK(zwp_linux_dmabuf_v1, zwp_linux_dmabuf_v1),
           REGISTRY_CALLBACK(wl_subcompositor, wl_subcompositor),
diff --git a/components/exo/wayland/output_configuration_change.cc b/components/exo/wayland/output_configuration_change.cc
new file mode 100644
index 0000000..23f0e0b7
--- /dev/null
+++ b/components/exo/wayland/output_configuration_change.cc
@@ -0,0 +1,19 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/exo/wayland/output_configuration_change.h"
+
+namespace exo::wayland {
+
+OutputConfigurationChange::OutputConfigurationChange() = default;
+
+OutputConfigurationChange::OutputConfigurationChange(
+    OutputConfigurationChange&& other) = default;
+
+OutputConfigurationChange& OutputConfigurationChange::operator=(
+    OutputConfigurationChange&& other) = default;
+
+OutputConfigurationChange::~OutputConfigurationChange() = default;
+
+}  // namespace exo::wayland
diff --git a/components/exo/wayland/output_configuration_change.h b/components/exo/wayland/output_configuration_change.h
new file mode 100644
index 0000000..e3f72a7
--- /dev/null
+++ b/components/exo/wayland/output_configuration_change.h
@@ -0,0 +1,44 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_EXO_WAYLAND_OUTPUT_CONFIGURATION_CHANGE_H_
+#define COMPONENTS_EXO_WAYLAND_OUTPUT_CONFIGURATION_CHANGE_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+
+namespace exo::wayland {
+
+class WaylandDisplayOutput;
+
+using WaylandOutputList = std::vector<raw_ptr<const WaylandDisplayOutput>>;
+
+// Pairs the changed output with a bitvector of DisplayMetric changes.
+using WaylandOutputChangedList =
+    std::vector<std::pair<raw_ptr<const WaylandDisplayOutput>, uint32_t>>;
+
+// Encapsulates an atomic change to the output configuration tracked by Exo.
+struct OutputConfigurationChange {
+  OutputConfigurationChange();
+  OutputConfigurationChange(OutputConfigurationChange&& other);
+  OutputConfigurationChange& operator=(OutputConfigurationChange&& other);
+  OutputConfigurationChange(const OutputConfigurationChange&) = delete;
+  OutputConfigurationChange& operator=(const OutputConfigurationChange&) =
+      delete;
+  ~OutputConfigurationChange();
+
+  // New outputs for user-visible displays added to the system.
+  WaylandOutputList added_outputs;
+
+  // Outputs for displays removed from the system's configuration.
+  WaylandOutputList removed_outputs;
+
+  // Existing outputs updated to reflect updated display metrics.
+  WaylandOutputChangedList changed_outputs;
+};
+
+}  // namespace exo::wayland
+
+#endif  // COMPONENTS_EXO_WAYLAND_OUTPUT_CONFIGURATION_CHANGE_H_
diff --git a/components/exo/wayland/output_controller.cc b/components/exo/wayland/output_controller.cc
index 80ea963..63c90c65 100644
--- a/components/exo/wayland/output_controller.cc
+++ b/components/exo/wayland/output_controller.cc
@@ -4,14 +4,19 @@
 
 #include "components/exo/wayland/output_controller.h"
 
+#include <aura-output-management-server-protocol.h>
 #include <secure-output-unstable-v1-server-protocol.h>
 #include <wayland-server-core.h>
 #include <xdg-output-unstable-v1-server-protocol.h>
 
 #include "ash/shell.h"
+#include "base/containers/contains.h"
+#include "base/ranges/algorithm.h"
+#include "components/exo/wayland/output_configuration_change.h"
 #include "components/exo/wayland/wayland_display_output.h"
 #include "components/exo/wayland/wl_output.h"
 #include "components/exo/wayland/zaura_output_manager.h"
+#include "components/exo/wayland/zaura_output_manager_v2.h"
 #include "components/exo/wayland/zcr_secure_output.h"
 #include "components/exo/wayland/zxdg_output_manager.h"
 #include "ui/display/display.h"
@@ -20,12 +25,16 @@
 namespace exo::wayland {
 
 OutputController::OutputController(Delegate* delegate) : delegate_(delegate) {
-  // aura_output_manager needs to be registered before the wl_output globals to
-  // ensure clients can bind to the aura_output_manager before any wl_outputs.
-  // This is necessary to ensure aura_output_manager can send relevant output
-  // events immediately after an output is bound to the client and before the
-  // data in these events might be needed by the client.
+  // Clients need to bind aura output manager globals before binding any of the
+  // wl_output globals. This is necessary to ensure clients won't receive events
+  // for outputs before receiving events for the aura output manager.
   wl_display* display = delegate_->GetWaylandDisplay();
+  aura_output_manager_v2_ =
+      std::make_unique<AuraOutputManagerV2>(base::BindRepeating(
+          &OutputController::GetActiveOutputs, base::Unretained(this)));
+  wl_global_create(display, &zaura_output_manager_v2_interface,
+                   kZAuraOutputManagerV2Version, aura_output_manager_v2_.get(),
+                   bind_aura_output_manager_v2);
   wl_global_create(display, &zaura_output_manager_interface,
                    kZAuraOutputManagerVersion, nullptr,
                    bind_aura_output_manager);
@@ -63,6 +72,20 @@
     outputs_.insert(std::make_pair(added_display.id(), std::move(output)));
   }
 
+  for (const auto& change : configuration_change.display_metrics_changes) {
+    if (auto* wayland_display_output =
+            GetWaylandDisplayOutput(change.display->id())) {
+      wayland_display_output->SendDisplayMetricsChanges(change.display.get(),
+                                                        change.changed_metrics);
+    }
+  }
+
+  // Propagate display configuration changes to aura output manager clients.
+  const bool needs_done =
+      ProcessDisplayChangesForAuraOutputManager(configuration_change);
+
+  // Remove outputs after propagating config changes as the WaylandDisplayOutput
+  // object may be needed during change notification propagation.
   for (const display::Display& removed_display :
        configuration_change.removed_displays) {
     // There should always be at least one display tracked by Exo.
@@ -74,12 +97,8 @@
     output.release()->OnDisplayRemoved();
   }
 
-  for (const auto& change : configuration_change.display_metrics_changes) {
-    if (auto* wayland_display_output =
-            GetWaylandDisplayOutput(change.display->id())) {
-      wayland_display_output->SendDisplayMetricsChanges(change.display.get(),
-                                                        change.changed_metrics);
-    }
+  if (needs_done) {
+    aura_output_manager_v2_->SendDone();
   }
 
   UpdateActivatedDisplayIfNecessary();
@@ -111,6 +130,45 @@
   return iter == outputs_.end() ? nullptr : iter->second.get();
 }
 
+WaylandOutputList OutputController::GetActiveOutputs() const {
+  WaylandOutputList output_list;
+  base::ranges::transform(outputs_, std::back_inserter(output_list),
+                          [](auto& pair) { return pair.second.get(); });
+  return output_list;
+}
+
+bool OutputController::ProcessDisplayChangesForAuraOutputManager(
+    const DisplayConfigurationChange& configuration_change) {
+  OutputConfigurationChange output_config_change;
+
+  base::ranges::transform(
+      configuration_change.added_displays,
+      std::back_inserter(output_config_change.added_outputs),
+      [this](const display::Display& display) {
+        return GetWaylandDisplayOutput(display.id());
+      });
+  base::ranges::transform(
+      configuration_change.removed_displays,
+      std::back_inserter(output_config_change.removed_outputs),
+      [this](const display::Display& display) {
+        return GetWaylandDisplayOutput(display.id());
+      });
+  for (const DisplayMetricsChange& change :
+       configuration_change.display_metrics_changes) {
+    // Added displays may appear in both added and changed lists, ensure added
+    // outputs are not represented in both added and changed lists.
+    if (!base::Contains(configuration_change.added_displays,
+                        change.display.get())) {
+      output_config_change.changed_outputs.emplace_back(
+          GetWaylandDisplayOutput(change.display->id()),
+          change.changed_metrics);
+    }
+  }
+
+  return aura_output_manager_v2_->OnDidProcessDisplayChanges(
+      output_config_change);
+}
+
 void OutputController::UpdateActivatedDisplayIfNecessary() {
   const int64_t current_active_display_id =
       display::Screen::GetScreen()->GetDisplayForNewWindows().id();
diff --git a/components/exo/wayland/output_controller.h b/components/exo/wayland/output_controller.h
index 19919b0..5ae1b4f07 100644
--- a/components/exo/wayland/output_controller.h
+++ b/components/exo/wayland/output_controller.h
@@ -7,6 +7,7 @@
 
 #include "ash/shell_observer.h"
 #include "base/containers/flat_map.h"
+#include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/display_manager_observer.h"
@@ -17,6 +18,7 @@
 
 namespace exo::wayland {
 
+class AuraOutputManagerV2;
 class WaylandDisplayOutput;
 
 // Responsible for keeping output state consistent with system display state and
@@ -26,6 +28,8 @@
  public:
   using DisplayOutputMap =
       base::flat_map<int64_t, std::unique_ptr<WaylandDisplayOutput>>;
+  using WaylandOutputList = std::vector<raw_ptr<const WaylandDisplayOutput>>;
+
   class Delegate {
    public:
     virtual ~Delegate() = default;
@@ -55,6 +59,15 @@
   // the `display_id`.
   WaylandDisplayOutput* GetWaylandDisplayOutput(int64_t display_id);
 
+  // Returns a list of the current active outputs tracked by the server.
+  WaylandOutputList GetActiveOutputs() const;
+
+  // Sends the necessary events for a configuration change to clients of the the
+  // aura output manager. Returns true if update events were sent and a done is
+  // needed.
+  bool ProcessDisplayChangesForAuraOutputManager(
+      const DisplayConfigurationChange& configuration_change);
+
   // Updates exo to align with the system's currently active window.
   void UpdateActivatedDisplayIfNecessary();
 
@@ -62,6 +75,7 @@
   int64_t dispatched_activated_display_id_ = display::kInvalidDisplayId;
 
   DisplayOutputMap outputs_;
+  std::unique_ptr<AuraOutputManagerV2> aura_output_manager_v2_;
 
   // The delegate will strictly outlive the controller.
   const raw_ptr<Delegate> delegate_;
diff --git a/components/exo/wayland/output_metrics.cc b/components/exo/wayland/output_metrics.cc
index 789032f5..025e035 100644
--- a/components/exo/wayland/output_metrics.cc
+++ b/components/exo/wayland/output_metrics.cc
@@ -107,6 +107,10 @@
   scale = std::ceil(display.device_scale_factor());
 }
 
+OutputMetrics::OutputMetrics(const OutputMetrics&) = default;
+
+OutputMetrics& OutputMetrics::operator=(const OutputMetrics&) = default;
+
 OutputMetrics::~OutputMetrics() = default;
 
 }  // namespace exo::wayland
diff --git a/components/exo/wayland/output_metrics.h b/components/exo/wayland/output_metrics.h
index f33f272..bb8d3d9 100644
--- a/components/exo/wayland/output_metrics.h
+++ b/components/exo/wayland/output_metrics.h
@@ -24,6 +24,8 @@
 // Metrics for wl_output and supported extensions.
 struct OutputMetrics {
   explicit OutputMetrics(const display::Display& display);
+  OutputMetrics(const OutputMetrics&);
+  OutputMetrics& operator=(const OutputMetrics&);
   virtual ~OutputMetrics();
 
   //////////////////////////////////////////////////////////////////////////////
diff --git a/components/exo/wayland/protocol/BUILD.gn b/components/exo/wayland/protocol/BUILD.gn
index 297d76e..3217be3 100644
--- a/components/exo/wayland/protocol/BUILD.gn
+++ b/components/exo/wayland/protocol/BUILD.gn
@@ -3,6 +3,10 @@
 # found in the LICENSE file.
 import("//third_party/wayland/wayland_protocol.gni")
 
+wayland_protocol("aura_output_management_protocol") {
+  sources = [ "aura-output-management.xml" ]
+}
+
 wayland_protocol("aura_shell_protocol") {
   sources = [ "aura-shell.xml" ]
 }
diff --git a/components/exo/wayland/protocol/aura-output-management.xml b/components/exo/wayland/protocol/aura-output-management.xml
new file mode 100644
index 0000000..7427ead5
--- /dev/null
+++ b/components/exo/wayland/protocol/aura-output-management.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="aura_output_management">
+
+  <copyright>
+    Copyright 2024 The Chromium Authors
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zaura_output_manager_v2" version="1">
+    <description summary="aura output manager singleton">
+      A global responsible for propagating atomic output configuration changes
+      to clients. An output configuration is a union of added / removed outputs
+      and updated output metrics.
+
+      Added outputs arrive at the client as wl_registry.global events, removed
+      outputs as wl_registry.global_remove events and updated output metrics as
+      a sequence of events defined on the zaura_output_manager_v2 interface.
+      This is followed by the manager's done event which signals the end of the
+      transaction.
+
+      The change should be processed by clients in a way that transitions from
+      one output configuration state to another without exposing the
+      intermediate output state to the rest of the application.
+
+      Participating servers should emit the zaura_output_manager_v2 global
+      before any wl_output globals in the sequence of wl_registry.global events.
+
+      Participating clients should bind the zaura_output_manager_v2 before any
+      wl_output globals. This ordering is required to ensure clients receive all
+      necessary output configuration information before receiving any further
+      server events that may reference bound wl_outputs.
+
+      Clients can expect that all events comprising a configuration change are
+      sent synchronously, one after the other, before any other server events
+      that leverage the output as an event param.
+    </description>
+
+    <event name="done" since="1">
+      <description
+          summary="sent all information about the output configuration change">
+        This event is sent after all added, changed and removed output events
+        for a given wl_output have been dispatched to the client.
+      </description>
+    </event>
+
+    <event name="display_id" since="1">
+      <description summary="advertise the output's display id">
+        This event describes the 64bit display id assigned to each display by
+        ChromeOS. The value is opaque and should not be interpreted.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="display_id_hi" type="uint" />
+      <arg name="display_id_lo" type="uint" />
+    </event>
+
+    <event name="logical_position" since="1">
+      <description
+          summary="position of the output within the global compositor space">
+        The position event describes the location of the wl_output within the
+        global compositor space.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="x" type="int"
+          summary="x position within the global compositor space" />
+      <arg name="y" type="int"
+          summary="y position within the global compositor space" />
+    </event>
+
+    <event name="logical_size" since="1">
+      <description summary="size of the output in the global compositor space">
+        The logical_size event describes the logical size of the output in the
+        global compositor space.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="width" type="int" summary="width in global compositor space" />
+      <arg name="height" type="int"
+          summary="height in global compositor space" />
+    </event>
+
+    <event name="physical_size" since="1">
+      <description summary="size of the output in pixels">
+        The physical resolution of the display in pixels. The value should not
+        include any overscan insets or display rotation, except for any panel
+        orientation adjustment.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="width" type="int"
+          summary="width in global compositor space" />
+      <arg name="height" type="int"
+          summary="height in global compositor space" />
+    </event>
+
+    <event name="work_area_insets" since="1">
+      <description summary="advertise the work area insets for the output">
+        This event describes the work area insets for the output in logical
+        screen coordinates, from which the work area can be calculated.
+
+        The event is sent when binding to the output object and subsequently as
+        output state changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="top" type="int" />
+      <arg name="left" type="int" />
+      <arg name="bottom" type="int" />
+      <arg name="right" type="int" />
+    </event>
+
+    <event name="device_scale_factor" since="1">
+      <description summary="advertise device scale factor for the output">
+        The scale factor of the output device. We reinterpret_cast the float
+        scale factor into a 32-bit uint and later cast back into a float. This
+        is because wayland does not support native transport of floats. As
+        different CPU architectures may use different endian representations for
+        IEEE 754 floats, this protocol implicitly assumes that the caller and
+        receiver are the same machine.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="device_scale_factor" type="uint"
+          summary="display scale factor, in float format" />
+    </event>
+
+    <event name="logical_transform" since="1">
+      <description summary="logical transform of the output">
+        This event describes the logical transform for the output. Whereas
+        panel transform corresponds to the display's panel rotation, the logical
+        transform corresponds to the display's logical rotation.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="transform" type="int" enum="wl_output.transform"
+        summary="transform that maps framebuffer to output" />
+    </event>
+
+    <event name="panel_transform" since="1">
+      <description summary="panel transform of the output">
+        This event describes the panel transform for the output, which is the
+        associated display's panel rotation.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="transform" type="int" enum="wl_output.transform"
+        summary="transform that maps framebuffer to output" />
+    </event>
+
+    <event name="name" since="1">
+      <description summary="human-readable name of this output">
+        The name is a UTF-8 string with no convention defined for its contents.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="name" type="string" summary="output name" />
+    </event>
+
+    <event name="description" since="1">
+      <description summary="human-readable description of this output">
+        The description is a UTF-8 string with no convention defined for its
+        contents.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="description" type="string" summary="output description" />
+    </event>
+
+    <event name="overscan_insets" since="1">
+      <description summary="advertise the overscan insets for the output">
+        This event describes the overscan insets for the output in physical
+        pixels.
+
+        The event is sent immediately after the wl_registry.global event for the
+        output and subsequently in latter display configuration changes.
+      </description>
+      <arg name="output_name" type="uint" />
+      <arg name="top" type="int" />
+      <arg name="left" type="int" />
+      <arg name="bottom" type="int" />
+      <arg name="right" type="int" />
+    </event>
+
+    <event name="activated" since="1">
+      <description summary="target display for new windows">
+        Notifies that this output is now active output. It is typically used as
+        a target when a new window is created without specific bounds.
+      </description>
+      <arg name="output_name" type="uint" />
+    </event>
+  </interface>
+</protocol>
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index e17d093..70b533ca 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -1471,6 +1471,9 @@
 
   <interface name="zaura_output_manager" version="3">
     <description summary="aura shell interface to the output manager">
+      [Deprecated] Deprecated since M122. See the zaura_output_manager_v2
+      interface.
+
       A global responsible for ensuring clients have a complete view of a given
       output's state immediately following the bind of wl_output, and
       subsequently as needed.
diff --git a/components/exo/wayland/test/test_client.h b/components/exo/wayland/test/test_client.h
index a628ac3b..53e51fad 100644
--- a/components/exo/wayland/test/test_client.h
+++ b/components/exo/wayland/test/test_client.h
@@ -64,6 +64,9 @@
   zaura_output_manager* aura_output_manager() {
     return globals().aura_output_manager.get();
   }
+  zaura_output_manager_v2* aura_output_manager_v2() {
+    return globals().aura_output_manager_v2.get();
+  }
   xdg_wm_base* xdg_wm_base() { return globals().xdg_wm_base.get(); }
   zwp_fullscreen_shell_v1* fullscreen_shell() {
     return globals().fullscreen_shell.get();
diff --git a/components/exo/wayland/zaura_output_manager_v2.cc b/components/exo/wayland/zaura_output_manager_v2.cc
new file mode 100644
index 0000000..35aeecb9
--- /dev/null
+++ b/components/exo/wayland/zaura_output_manager_v2.cc
@@ -0,0 +1,180 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/exo/wayland/zaura_output_manager_v2.h"
+
+#include <aura-output-management-server-protocol.h>
+#include <wayland-server-core.h>
+
+#include <memory>
+
+#include "base/bit_cast.h"
+#include "components/exo/wayland/output_metrics.h"
+#include "components/exo/wayland/server_util.h"
+#include "components/exo/wayland/wayland_display_observer.h"
+#include "components/exo/wayland/wayland_display_output.h"
+#include "ui/display/display.h"
+
+namespace exo::wayland {
+
+void bind_aura_output_manager_v2(wl_client* client,
+                                 void* data,
+                                 uint32_t version,
+                                 uint32_t id) {
+  wl_resource* outout_manager_resouce =
+      wl_resource_create(client, &zaura_output_manager_v2_interface,
+                         std::min(version, kZAuraOutputManagerV2Version), id);
+
+  auto user_data = std::make_unique<AuraOutputManagerV2::UserData>(
+      static_cast<AuraOutputManagerV2*>(data), outout_manager_resouce);
+  SetImplementation(outout_manager_resouce, nullptr, std::move(user_data));
+}
+
+AuraOutputManagerV2::UserData::UserData(AuraOutputManagerV2* output_manager,
+                                        wl_resource* outout_manager_resouce)
+    : output_manager_(output_manager->GetWeakPtr()),
+      outout_manager_resouce_(outout_manager_resouce) {
+  output_manager_->Register(outout_manager_resouce);
+}
+
+AuraOutputManagerV2::UserData::~UserData() {
+  if (output_manager_) {
+    output_manager_->Unregister(outout_manager_resouce_.get());
+  }
+}
+
+AuraOutputManagerV2::AuraOutputManagerV2(
+    ActiveOutputGetter active_output_getter)
+    : active_output_getter_(std::move(active_output_getter)) {}
+
+AuraOutputManagerV2::~AuraOutputManagerV2() = default;
+
+bool AuraOutputManagerV2::OnDidProcessDisplayChanges(
+    const OutputConfigurationChange& configuration_change) {
+  // A done event is required if any outputs have been added or removed.
+  bool needs_done = !configuration_change.added_outputs.empty() ||
+                    !configuration_change.removed_outputs.empty();
+
+  // Send metrics for any newly-added displays.
+  for (const WaylandDisplayOutput* added_output :
+       configuration_change.added_outputs) {
+    static constexpr uint32_t kAllDisplayChanges = 0xFFFFFFFF;
+    SendOutputMetrics(*added_output, kAllDisplayChanges);
+  }
+
+  // Send metrics for any existing updated displays.
+  for (const auto& change_pair : configuration_change.changed_outputs) {
+    needs_done |= SendOutputMetrics(*change_pair.first, change_pair.second);
+  }
+
+  return needs_done;
+}
+
+void AuraOutputManagerV2::SendOutputActivated(
+    const WaylandDisplayOutput& output) {
+  for (wl_resource* manager_resource : manager_resources_) {
+    const uint32_t output_name = wl_global_get_name(
+        output.global(), wl_resource_get_client(manager_resource));
+    zaura_output_manager_v2_send_activated(manager_resource, output_name);
+  }
+}
+
+void AuraOutputManagerV2::SendDone() {
+  for (wl_resource* manager_resource : manager_resources_) {
+    zaura_output_manager_v2_send_done(manager_resource);
+  }
+}
+
+void AuraOutputManagerV2::Register(wl_resource* manager_resource) {
+  // When a client first binds to the global the current active global outputs
+  // and their metrics make up the first configuration change.
+  manager_resources_.insert(manager_resource);
+  for (const WaylandDisplayOutput* output : active_output_getter_.Run()) {
+    SendOutputMetricsForClient(*output, manager_resource);
+  }
+  SendDone();
+}
+
+void AuraOutputManagerV2::Unregister(wl_resource* manager_resource) {
+  manager_resources_.erase(manager_resource);
+}
+
+base::WeakPtr<AuraOutputManagerV2> AuraOutputManagerV2::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+bool AuraOutputManagerV2::SendOutputMetrics(const WaylandDisplayOutput& output,
+                                            uint32_t changed_metrics) {
+  if (!(changed_metrics &
+        (display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
+         display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
+         display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR |
+         display::DisplayObserver::DISPLAY_METRIC_ROTATION))) {
+    return false;
+  }
+
+  for (wl_resource* manager_resource : manager_resources_) {
+    SendOutputMetricsForClient(output, manager_resource);
+  }
+  return true;
+}
+
+void AuraOutputManagerV2::SendOutputMetricsForClient(
+    const WaylandDisplayOutput& output,
+    wl_resource* manager_resource) {
+  const uint32_t output_name = wl_global_get_name(
+      output.global(), wl_resource_get_client(manager_resource));
+  const OutputMetrics& output_metrics = output.metrics();
+
+  const ui::wayland::WaylandDisplayIdPair& display_id =
+      output_metrics.display_id;
+  zaura_output_manager_v2_send_display_id(manager_resource, output_name,
+                                          display_id.high, display_id.low);
+
+  const auto& logical_origin = output_metrics.logical_origin;
+  zaura_output_manager_v2_send_logical_position(
+      manager_resource, output_name, logical_origin.x(), logical_origin.y());
+
+  const auto& logical_size = output_metrics.logical_size;
+  zaura_output_manager_v2_send_logical_size(manager_resource, output_name,
+                                            logical_size.width(),
+                                            logical_size.height());
+
+  const auto& physical_size = output_metrics.physical_size_px;
+  if (output_metrics.mode_flags & WL_OUTPUT_MODE_CURRENT) {
+    zaura_output_manager_v2_send_physical_size(manager_resource, output_name,
+                                               physical_size.width(),
+                                               physical_size.height());
+  }
+
+  const auto& insets = output_metrics.logical_insets;
+  zaura_output_manager_v2_send_work_area_insets(
+      manager_resource, output_name, insets.top(), insets.left(),
+      insets.bottom(), insets.right());
+
+  const auto& overscan = output_metrics.physical_overscan_insets;
+  zaura_output_manager_v2_send_overscan_insets(
+      manager_resource, output_name, overscan.top(), overscan.left(),
+      overscan.bottom(), overscan.right());
+
+  // The float value is bit_cast<> into a uint32_t. It must later be cast back
+  // into a float. This is because wayland does not support native transport of
+  // floats. As different CPU architectures may use different endian
+  // representations for IEEE 754 floats, this implicitly assumes that the
+  // caller and receiver are the same machine.
+  zaura_output_manager_v2_send_device_scale_factor(
+      manager_resource, output_name,
+      base::bit_cast<uint32_t>(output_metrics.device_scale_factor));
+
+  zaura_output_manager_v2_send_logical_transform(
+      manager_resource, output_name, output_metrics.logical_transform);
+
+  zaura_output_manager_v2_send_panel_transform(manager_resource, output_name,
+                                               output_metrics.panel_transform);
+
+  zaura_output_manager_v2_send_description(manager_resource, output_name,
+                                           output_metrics.description.c_str());
+}
+
+}  // namespace exo::wayland
diff --git a/components/exo/wayland/zaura_output_manager_v2.h b/components/exo/wayland/zaura_output_manager_v2.h
new file mode 100644
index 0000000..82a2671
--- /dev/null
+++ b/components/exo/wayland/zaura_output_manager_v2.h
@@ -0,0 +1,95 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_EXO_WAYLAND_ZAURA_OUTPUT_MANAGER_V2_H_
+#define COMPONENTS_EXO_WAYLAND_ZAURA_OUTPUT_MANAGER_V2_H_
+
+#include <unordered_set>
+
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/exo/wayland/output_configuration_change.h"
+
+struct wl_resource;
+struct wl_client;
+
+namespace exo::wayland {
+
+class WaylandDisplayOutput;
+
+inline constexpr uint32_t kZAuraOutputManagerV2Version = 1;
+
+void bind_aura_output_manager_v2(wl_client* client,
+                                 void* data,
+                                 uint32_t version,
+                                 uint32_t id);
+
+// This class is responsible for propagating information about display
+// configuration state changes atomically to participating clients.
+class AuraOutputManagerV2 {
+ public:
+  using ActiveOutputGetter = base::RepeatingCallback<WaylandOutputList()>;
+
+  // Used to manage the lifetime of client-bound handles to the aura output
+  // manager.
+  class UserData {
+   public:
+    UserData(AuraOutputManagerV2* output_manager,
+             wl_resource* outout_manager_resouce);
+    ~UserData();
+
+   private:
+    const base::WeakPtr<AuraOutputManagerV2> output_manager_;
+    const raw_ptr<wl_resource> outout_manager_resouce_;
+  };
+
+  explicit AuraOutputManagerV2(ActiveOutputGetter active_output_getter);
+  AuraOutputManagerV2(const AuraOutputManagerV2&) = delete;
+  AuraOutputManagerV2& operator=(const AuraOutputManagerV2&) = delete;
+  virtual ~AuraOutputManagerV2();
+
+  // Called when the system's display configuration has changed. Returns true if
+  // changes were propagated to clients and a done event is necessary to
+  // complete the transaction.
+  bool OnDidProcessDisplayChanges(
+      const OutputConfigurationChange& configuration_change);
+
+  // Dispatches the activated event to all bound clients for the global
+  // `output`.
+  void SendOutputActivated(const WaylandDisplayOutput& output);
+
+  // Notifies clients of the end of the display configuration change
+  // transaction.
+  void SendDone();
+
+  // Called by UserData when the wrapped resource is created and destroyed
+  // respectively.
+  void Register(wl_resource* manager_resource);
+  void Unregister(wl_resource* manager_resource);
+
+  base::WeakPtr<AuraOutputManagerV2> GetWeakPtr();
+
+ private:
+  // Dispatches output metrics conditional on `changed_metrics` to all bound
+  // clients for the global `output`. Returns true if updates were dispatched.
+  bool SendOutputMetrics(const WaylandDisplayOutput& output,
+                         uint32_t changed_metrics);
+
+  // Dispatches output metrics to a specific client bound to the output manager.
+  void SendOutputMetricsForClient(const WaylandDisplayOutput& output,
+                                  wl_resource* manager_resource);
+
+  // A set of resources for clients bound to the aura output manager global.
+  std::unordered_set<wl_resource*> manager_resources_;
+
+  // Gets the currently active outputs tracked by the server.
+  const ActiveOutputGetter active_output_getter_;
+
+  base::WeakPtrFactory<AuraOutputManagerV2> weak_factory_{this};
+};
+
+}  // namespace exo::wayland
+
+#endif  // COMPONENTS_EXO_WAYLAND_ZAURA_OUTPUT_MANAGER_V2_H_
diff --git a/components/exo/wayland/zaura_output_manager_v2_unittest.cc b/components/exo/wayland/zaura_output_manager_v2_unittest.cc
new file mode 100644
index 0000000..4ba1538
--- /dev/null
+++ b/components/exo/wayland/zaura_output_manager_v2_unittest.cc
@@ -0,0 +1,387 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/exo/wayland/zaura_output_manager_v2.h"
+
+#include "base/bit_cast.h"
+#include "components/exo/wayland/output_controller_test_api.h"
+#include "components/exo/wayland/output_metrics.h"
+#include "components/exo/wayland/server_util.h"
+#include "components/exo/wayland/test/test_client.h"
+#include "components/exo/wayland/test/wayland_server_test.h"
+#include "components/exo/wayland/wayland_display_output.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace exo::wayland {
+
+namespace {
+
+using ::testing::_;
+using ::testing::ExpectationSet;
+using ::testing::Mock;
+using ::testing::NiceMock;
+using ::testing::StrEq;
+
+class MockGlobalsObserver : public clients::Globals::TestObserver {
+ public:
+  MOCK_METHOD(void,
+              OnRegistryGlobal,
+              (uint32_t id, const char* interface, uint32_t version),
+              (override));
+  MOCK_METHOD(void, OnRegistryGlobalRemove, (uint32_t id), (override));
+};
+
+class MockAuraOutputManagerListener {
+ public:
+  static void OnDone(void* data, zaura_output_manager_v2* output_manager) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnDone();
+  }
+  static void OnDisplayId(void* data,
+                          zaura_output_manager_v2* output_manager,
+                          uint32_t output_name,
+                          uint32_t display_id_hi,
+                          uint32_t display_id_lo) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnDisplayId(
+        output_name, display_id_hi, display_id_lo);
+  }
+  static void OnLogicalPosition(void* data,
+                                zaura_output_manager_v2* output_manager,
+                                uint32_t output_name,
+                                int32_t x,
+                                int32_t y) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalPosition(
+        output_name, x, y);
+  }
+  static void OnLogicalSize(void* data,
+                            zaura_output_manager_v2* output_manager,
+                            uint32_t output_name,
+                            int32_t width,
+                            int32_t height) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalSize(
+        output_name, width, height);
+  }
+  static void OnPhysicalSize(void* data,
+                             zaura_output_manager_v2* output_manager,
+                             uint32_t output_name,
+                             int32_t width,
+                             int32_t height) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnPhysicalSize(
+        output_name, width, height);
+  }
+  static void OnInsets(void* data,
+                       zaura_output_manager_v2* output_manager,
+                       uint32_t output_name,
+                       int32_t top,
+                       int32_t left,
+                       int32_t bottom,
+                       int32_t right) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnInsets(
+        output_name, top, left, bottom, right);
+  }
+  static void OnDeviceScaleFactor(void* data,
+                                  zaura_output_manager_v2* output_manager,
+                                  uint32_t output_name,
+                                  uint32_t scale_as_uint) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnDeviceScaleFactor(
+        output_name, scale_as_uint);
+  }
+  static void OnLogicalTransform(void* data,
+                                 zaura_output_manager_v2* output_manager,
+                                 uint32_t output_name,
+                                 int32_t transform) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalTransform(
+        output_name, transform);
+  }
+  static void OnPanelTransform(void* data,
+                               zaura_output_manager_v2* output_manager,
+                               uint32_t output_name,
+                               int32_t transform) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnPanelTransform(
+        output_name, transform);
+  }
+  static void OnName(void* data,
+                     zaura_output_manager_v2* output_manager,
+                     uint32_t output_name,
+                     const char* name) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnName(output_name,
+                                                                  name);
+  }
+  static void OnDescription(void* data,
+                            zaura_output_manager_v2* output_manager,
+                            uint32_t output_name,
+                            const char* description) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnDescription(
+        output_name, description);
+  }
+  static void OnOverscanInsets(void* data,
+                               zaura_output_manager_v2* output_manager,
+                               uint32_t output_name,
+                               int32_t top,
+                               int32_t left,
+                               int32_t bottom,
+                               int32_t right) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnOverscanInsets(
+        output_name, top, left, bottom, right);
+  }
+  static void OnActivated(void* data,
+                          zaura_output_manager_v2* output_manager,
+                          uint32_t output_name) {
+    static_cast<MockAuraOutputManagerListener*>(data)->MockOnActivated(
+        output_name);
+  }
+
+  MOCK_METHOD(void, MockOnDone, ());
+  MOCK_METHOD(void,
+              MockOnDisplayId,
+              (uint32_t output_name,
+               uint32_t display_id_hi,
+               uint32_t display_id_lo));
+  MOCK_METHOD(void,
+              MockOnLogicalPosition,
+              (uint32_t output_name, int32_t x, int32_t y));
+  MOCK_METHOD(void,
+              MockOnLogicalSize,
+              (uint32_t output_name, int32_t width, int32_t height));
+  MOCK_METHOD(void,
+              MockOnPhysicalSize,
+              (uint32_t output_name, int32_t width, int32_t height));
+  MOCK_METHOD(void,
+              MockOnInsets,
+              (uint32_t output_name,
+               int32_t top,
+               int32_t left,
+               int32_t bottom,
+               int32_t right));
+  MOCK_METHOD(void,
+              MockOnDeviceScaleFactor,
+              (uint32_t output_name, uint32_t scale_as_uint));
+  MOCK_METHOD(void,
+              MockOnLogicalTransform,
+              (uint32_t output_name, int32_t transform));
+  MOCK_METHOD(void,
+              MockOnPanelTransform,
+              (uint32_t output_name, int32_t transform));
+  MOCK_METHOD(void, MockOnName, (uint32_t output_name, const char* name));
+  MOCK_METHOD(void,
+              MockOnDescription,
+              (uint32_t output_name, const char* description));
+  MOCK_METHOD(void,
+              MockOnOverscanInsets,
+              (uint32_t output_name,
+               int32_t top,
+               int32_t left,
+               int32_t bottom,
+               int32_t right));
+  MOCK_METHOD(void, MockOnActivated, (uint32_t output_name));
+};
+
+// Server test asserting clients receive the events specified by the
+// aura_output_manager_v2 interface in the order expected.
+class AuraOutputManagerV2Test : public test::WaylandServerTest {
+ public:
+  // test::WaylandServerTest:
+  std::unique_ptr<test::TestClient> InitOnClientThread() override {
+    auto test_client = test::WaylandServerTest::InitOnClientThread();
+
+    test_client->globals().set_observer_for_testing(&mock_globals_observer_);
+
+    static constexpr zaura_output_manager_v2_listener
+        zaura_output_manager_v2_listener = {
+            &MockAuraOutputManagerListener::OnDone,
+            &MockAuraOutputManagerListener::OnDisplayId,
+            &MockAuraOutputManagerListener::OnLogicalPosition,
+            &MockAuraOutputManagerListener::OnLogicalSize,
+            &MockAuraOutputManagerListener::OnPhysicalSize,
+            &MockAuraOutputManagerListener::OnInsets,
+            &MockAuraOutputManagerListener::OnDeviceScaleFactor,
+            &MockAuraOutputManagerListener::OnLogicalTransform,
+            &MockAuraOutputManagerListener::OnPanelTransform,
+            &MockAuraOutputManagerListener::OnName,
+            &MockAuraOutputManagerListener::OnDescription,
+            &MockAuraOutputManagerListener::OnOverscanInsets,
+            &MockAuraOutputManagerListener::OnActivated};
+    zaura_output_manager_v2_add_listener(test_client->aura_output_manager_v2(),
+                                         &zaura_output_manager_v2_listener,
+                                         &mock_aura_output_manager_);
+
+    return test_client;
+  }
+
+ protected:
+  void ExpectMetrics(uint32_t output_name,
+                     const OutputMetrics& metrics,
+                     ExpectationSet& expectations) {
+    expectations +=
+        EXPECT_CALL(mock_aura_output_manager_,
+                    MockOnDisplayId(output_name, metrics.display_id.high,
+                                    metrics.display_id.low));
+    expectations += EXPECT_CALL(
+        mock_aura_output_manager_,
+        MockOnLogicalPosition(output_name, metrics.logical_origin.x(),
+                              metrics.logical_origin.y()));
+    expectations +=
+        EXPECT_CALL(mock_aura_output_manager_,
+                    MockOnLogicalSize(output_name, metrics.logical_size.width(),
+                                      metrics.logical_size.height()));
+    expectations += EXPECT_CALL(
+        mock_aura_output_manager_,
+        MockOnPhysicalSize(output_name, metrics.physical_size_px.width(),
+                           metrics.physical_size_px.height()));
+    expectations +=
+        EXPECT_CALL(mock_aura_output_manager_,
+                    MockOnInsets(output_name, metrics.logical_insets.top(),
+                                 metrics.logical_insets.left(),
+                                 metrics.logical_insets.bottom(),
+                                 metrics.logical_insets.right()));
+    expectations += EXPECT_CALL(
+        mock_aura_output_manager_,
+        MockOnDeviceScaleFactor(output_name, base::bit_cast<uint32_t>(
+                                                 metrics.device_scale_factor)));
+    expectations += EXPECT_CALL(
+        mock_aura_output_manager_,
+        MockOnLogicalTransform(output_name, metrics.logical_transform));
+    expectations +=
+        EXPECT_CALL(mock_aura_output_manager_,
+                    MockOnPanelTransform(output_name, metrics.panel_transform));
+    expectations +=
+        EXPECT_CALL(mock_aura_output_manager_,
+                    MockOnOverscanInsets(
+                        output_name, metrics.physical_overscan_insets.top(),
+                        metrics.physical_overscan_insets.left(),
+                        metrics.physical_overscan_insets.bottom(),
+                        metrics.physical_overscan_insets.right()));
+  }
+
+  NiceMock<MockAuraOutputManagerListener> mock_aura_output_manager_;
+  NiceMock<MockGlobalsObserver> mock_globals_observer_;
+};
+
+}  // namespace
+
+TEST_F(AuraOutputManagerV2Test, ActiveOutputMetricsUpdate) {
+  // Start with a single display and round-trip with client to clear the event
+  // queue.
+  UpdateDisplay("800x600");
+  PostToClientAndWait([] {});
+
+  const auto* screen = display::Screen::GetScreen();
+  ASSERT_EQ(1u, screen->GetAllDisplays().size());
+
+  const int64_t primary_id = screen->GetAllDisplays()[0].id();
+  auto* output_controller = server_->output_controller_for_testing();
+  OutputControllerTestApi output_controller_test_api(*output_controller);
+  const WaylandDisplayOutput* primary_output =
+      output_controller_test_api.GetWaylandDisplayOutput(primary_id);
+  const uint32_t primary_output_name =
+      wl_global_get_name(primary_output->global(), client_resource_.get());
+
+  // Update the display, expect to see updated metrics only followed by done.
+  OutputMetrics metrics = primary_output->metrics();
+  metrics.physical_size_px.SetSize(1200, 800);
+  metrics.logical_size.SetSize(1200, 800);
+
+  ExpectationSet expected_events;
+  expected_events += EXPECT_CALL(mock_globals_observer_,
+                                 OnRegistryGlobal(_, StrEq("wl_output"), _))
+                         .Times(0);
+  expected_events +=
+      EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
+  ExpectMetrics(primary_output_name, metrics, expected_events);
+  EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
+
+  UpdateDisplay("1200x800");
+  PostToClientAndWait([] {});
+
+  Mock::VerifyAndClearExpectations(&mock_aura_output_manager_);
+  Mock::VerifyAndClearExpectations(&mock_globals_observer_);
+
+  // Subsequent updates should send new updates as expected.
+  metrics = primary_output->metrics();
+  metrics.physical_size_px.SetSize(1600, 1200);
+  metrics.logical_size.SetSize(1600, 1200);
+
+  ExpectationSet new_expected_events;
+  new_expected_events += EXPECT_CALL(mock_globals_observer_,
+                                     OnRegistryGlobal(_, StrEq("wl_output"), _))
+                             .Times(0);
+  new_expected_events +=
+      EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
+  ExpectMetrics(primary_output_name, metrics, new_expected_events);
+  EXPECT_CALL(mock_aura_output_manager_, MockOnDone())
+      .After(new_expected_events);
+
+  UpdateDisplay("1600x1200");
+  PostToClientAndWait([] {});
+}
+
+TEST_F(AuraOutputManagerV2Test, ActiveOutputsAdded) {
+  // Start with a single display and round-trip with client to clear the event
+  // queue.
+  UpdateDisplay("800x600");
+  const auto* screen = display::Screen::GetScreen();
+  ASSERT_EQ(1u, screen->GetAllDisplays().size());
+  PostToClientAndWait([] {});
+
+  // Add two new displays to the configuration, events for two new outputs and
+  // their corresponding metrics should be propagated to clients.
+  ExpectationSet expected_events;
+  expected_events += EXPECT_CALL(mock_globals_observer_,
+                                 OnRegistryGlobal(_, StrEq("wl_output"), _))
+                         .Times(2);
+  expected_events +=
+      EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
+  expected_events +=
+      EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, 1200, 800));
+  expected_events +=
+      EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, 1600, 1200));
+  EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
+
+  UpdateDisplay("800x600,1200x800,1600x1200");
+  ASSERT_EQ(3u, screen->GetAllDisplays().size());
+  PostToClientAndWait([] {});
+}
+
+TEST_F(AuraOutputManagerV2Test, ActiveOutputsRemoved) {
+  // Start multiple displays and round-trip with client to clear the event
+  // queue.
+  UpdateDisplay("800x600,1200x800,1600x1200");
+  const auto* screen = display::Screen::GetScreen();
+  ASSERT_EQ(3u, screen->GetAllDisplays().size());
+  PostToClientAndWait([] {});
+
+  const int64_t secondary_id = screen->GetAllDisplays()[1].id();
+  const int64_t tertiary_id = screen->GetAllDisplays()[2].id();
+
+  auto* output_controller = server_->output_controller_for_testing();
+  OutputControllerTestApi output_controller_test_api(*output_controller);
+  const uint64_t secondary_output_name = wl_global_get_name(
+      output_controller_test_api.GetWaylandDisplayOutput(secondary_id)
+          ->global(),
+      client_resource_.get());
+  const uint64_t tertiary_output_name = wl_global_get_name(
+      output_controller_test_api.GetWaylandDisplayOutput(tertiary_id)->global(),
+      client_resource_.get());
+
+  // Remove two displays from the configuration, events for the two removals
+  // should be propagated to clients.
+  ExpectationSet expected_events;
+  expected_events += EXPECT_CALL(mock_globals_observer_,
+                                 OnRegistryGlobal(_, StrEq("wl_output"), _))
+                         .Times(0);
+  expected_events += EXPECT_CALL(mock_globals_observer_,
+                                 OnRegistryGlobalRemove(secondary_output_name));
+  expected_events += EXPECT_CALL(mock_globals_observer_,
+                                 OnRegistryGlobalRemove(tertiary_output_name));
+  expected_events +=
+      EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, _, _))
+          .Times(0);
+  EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
+
+  UpdateDisplay("800x600");
+  ASSERT_EQ(1u, screen->GetAllDisplays().size());
+  PostToClientAndWait([] {});
+}
+
+}  // namespace exo::wayland
diff --git a/components/global_media_controls/public/views/media_item_ui_list_view.cc b/components/global_media_controls/public/views/media_item_ui_list_view.cc
index 83cf7ff0..c1acbac3 100644
--- a/components/global_media_controls/public/views/media_item_ui_list_view.cc
+++ b/components/global_media_controls/public/views/media_item_ui_list_view.cc
@@ -49,10 +49,10 @@
   ClipHeightTo(0, should_clip_height ? kMediaListMaxHeight
                                      : std::numeric_limits<int>::max());
 
-  SetVerticalScrollBar(
-      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
-  SetHorizontalScrollBar(
-      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/true));
+  SetVerticalScrollBar(std::make_unique<views::OverlayScrollBar>(
+      views::ScrollBar::Orientation::kVertical));
+  SetHorizontalScrollBar(std::make_unique<views::OverlayScrollBar>(
+      views::ScrollBar::Orientation::kHorizontal));
 }
 
 MediaItemUIListView::~MediaItemUIListView() = default;
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
index 319fba9c..cb8fe81 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -15,134 +15,25 @@
 #include "base/strings/string_number_conversions.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/reporting/util/encrypted_reporting_json_keys.h"
-#include "net/base/backoff_entry.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace policy {
 
-namespace {
-
-// Generate new backoff entry.
-std::unique_ptr<::net::BackoffEntry> GetBackoffEntry(
-    ::reporting::Priority priority) {
-  // Retry policy for SECURITY queue.
-  static const ::net::BackoffEntry::Policy kSecurityUploadBackoffPolicy = {
-      // Number of initial errors to ignore before applying
-      // exponential back-off rules.
-      /*num_errors_to_ignore=*/0,
-
-      // Initial delay is 10 seconds.
-      /*initial_delay_ms=*/10 * 1000,
-
-      // Factor by which the waiting time will be multiplied.
-      /*multiply_factor=*/2,
-
-      // Fuzzing percentage.
-      /*jitter_factor=*/0.1,
-
-      // Maximum delay is 1 minute.
-      /*maximum_backoff_ms=*/1 * 60 * 1000,
-
-      // It's up to the caller to reset the backoff time.
-      /*entry_lifetime_ms=*/-1,
-
-      /*always_use_initial_delay=*/true,
-  };
-  // Retry policy for all other queues, including initial key delivery.
-  static const ::net::BackoffEntry::Policy kDefaultUploadBackoffPolicy = {
-      // Number of initial errors to ignore before applying
-      // exponential back-off rules.
-      /*num_errors_to_ignore=*/0,
-
-      // Initial delay is 10 seconds.
-      /*initial_delay_ms=*/10 * 1000,
-
-      // Factor by which the waiting time will be multiplied.
-      /*multiply_factor=*/2,
-
-      // Fuzzing percentage.
-      /*jitter_factor=*/0.1,
-
-      // Maximum delay is 24 hours.
-      /*maximum_backoff_ms=*/24 * 60 * 60 * 1000,
-
-      // It's up to the caller to reset the backoff time.
-      /*entry_lifetime_ms=*/-1,
-
-      /*always_use_initial_delay=*/true,
-  };
-  // Maximum backoff is set per priority. Current proposal is to set SECURITY
-  // events to be backed off only slightly: max delay is set to 1 minute.
-  // For all other priorities max delay is set to 24 hours.
-  auto backoff_entry = std::make_unique<::net::BackoffEntry>(
-      priority == ::reporting::SECURITY ? &kSecurityUploadBackoffPolicy
-                                        : &kDefaultUploadBackoffPolicy);
-  return backoff_entry;
-}
-
-// State of single priority queue uploads.
-// It is a singleton, protected implicitly by the fact that all relevant
-// EncryptedReportingJobConfiguration actions are called on the sequenced task
-// runner.
-struct UploadState {
-  // Highest sequence id that has been posted for upload.
-  int64_t last_sequence_id;
-  // Generation id that has been posted for upload.
-  int64_t last_generation_id;
-
-  // Time when the next request will be allowed.
-  // This is essentially the cache value of the backoff->GetReleaseTime().
-  // When the time is reached, one request is allowed, backoff is updated as if
-  // the request failed, and the new release time is cached.
-  base::TimeTicks earliest_retry_timestamp;
-
-  // Current backoff entry for this prioririty.
-  std::unique_ptr<::net::BackoffEntry> backoff_entry;
-};
-// Map of all the queues states.
-using UploadStateMap = base::flat_map<::reporting::Priority, UploadState>;
-
-UploadStateMap* state_map() {
-  static base::NoDestructor<UploadStateMap> map;
-  return map.get();
-}
-
-UploadState* AccessState(::reporting::Priority priority,
-                         int64_t generation_id,
-                         int64_t sequence_id) {
-  auto state_it = state_map()->find(priority);
-  if (state_it == state_map()->end() ||
-      state_it->second.last_generation_id != generation_id) {
-    // This priority pops up for the first time or (rare case) generation has
-    // changed. Record new state and allow upload.
-    state_it = state_map()
-                   ->insert_or_assign(
-                       priority,
-                       UploadState{.last_sequence_id = sequence_id,
-                                   .last_generation_id = generation_id,
-                                   .backoff_entry = GetBackoffEntry(priority)})
-                   .first;
-    state_it->second.earliest_retry_timestamp =
-        state_it->second.backoff_entry->GetReleaseTime();
-  }
-  return &state_it->second;
-}
-
-}  // namespace
-
 EncryptedReportingJobConfiguration::EncryptedReportingJobConfiguration(
     scoped_refptr<network::SharedURLLoaderFactory> factory,
     DMAuth auth_data,
     const std::string& server_url,
     base::Value::Dict merging_payload,
     CloudPolicyClient* cloud_policy_client,
+    UploadResponseCallback response_cb,
     UploadCompleteCallback complete_cb)
     : ReportingJobConfigurationBase(TYPE_UPLOAD_ENCRYPTED_REPORT,
                                     factory,
                                     std::move(auth_data),
                                     server_url,
                                     std::move(complete_cb)),
-      is_device_managed_(cloud_policy_client != nullptr) {
+      is_device_managed_(cloud_policy_client != nullptr),
+      response_cb_(std::move(response_cb)) {
   if (is_device_managed_) {
     // Payload for managed device
     InitializePayloadWithDeviceInfo(cloud_policy_client->dm_token(),
@@ -153,45 +44,6 @@
   }
   // Merge it into the base class payload.
   payload_.Merge(std::move(merging_payload));
-  // Retrieve priorities and figure out maximum sequence id for each.
-  // Payload is expected to be correctly formed, any malformed piece is ignored.
-  // TODO(b/214040103): if batching is enabled, multiple priorities may be
-  // found. Before that, each payload can only have no more than one, and the
-  // highest sequence id comes from the last record.
-  // TODO(b/232455728): if test_request_payload is moved to components/
-  // we would be able to use it here.
-  const auto* const encrypted_record_list =
-      payload_.FindList(reporting::json_keys::kEncryptedRecordList);
-  // If there are no records, assume UNDEFINED priority and seq_id = -1.
-  priority_ = ::reporting::UNDEFINED_PRIORITY;
-  generation_id_ = -1;
-  sequence_id_ = -1;
-  if (encrypted_record_list != nullptr && !encrypted_record_list->empty()) {
-    record_count_ = encrypted_record_list->size();
-    const auto sequence_information_it =
-        std::prev(encrypted_record_list->cend());
-    const auto* const sequence_information =
-        sequence_information_it->GetDict().FindDict(
-            reporting::json_keys::kSequenceInformation);
-    if (sequence_information != nullptr) {
-      const auto maybe_priority =
-          sequence_information->FindInt(reporting::json_keys::kPriority);
-      auto* const generation_id_ptr =
-          sequence_information->FindString(reporting::json_keys::kGenerationId);
-      auto* const sequence_id_ptr =
-          sequence_information->FindString(reporting::json_keys::kSequencingId);
-      if (maybe_priority.has_value() &&
-          ::reporting::Priority_IsValid(maybe_priority.value())) {
-        priority_ = static_cast<::reporting::Priority>(maybe_priority.value());
-      }
-      if (generation_id_ptr != nullptr) {
-        base::StringToInt64(*generation_id_ptr, &generation_id_);
-      }
-      if (sequence_id_ptr != nullptr) {
-        base::StringToInt64(*sequence_id_ptr, &sequence_id_);
-      }
-    }
-  }
 }
 
 EncryptedReportingJobConfiguration::~EncryptedReportingJobConfiguration() {
@@ -221,59 +73,6 @@
   context_ = std::move(context);
 }
 
-base::TimeDelta EncryptedReportingJobConfiguration::WhenIsAllowedToProceed()
-    const {
-  // Now pick up the state.
-  const auto* const state =
-      AccessState(priority_, generation_id_, sequence_id_);
-  // If there are no records, allow upload (it will not overload the server).
-  if (record_count_ == 0u) {
-    return base::TimeDelta();  // 0 - allowed right away.
-  }
-  // Use and update previously recorded state, base upload decision on it.
-  if (state->last_sequence_id > sequence_id_) {
-    // Sequence id decreased, the upload is outdated, reject it forever.
-    return base::TimeDelta::Max();
-  }
-  if (state->last_sequence_id < sequence_id_) {
-    // Sequence id increased, keep validating.
-    switch (priority_) {
-      case ::reporting::SECURITY:
-        // For SECURITY events the request is allowed.
-        return base::TimeDelta();  // 0 - allowed right away.
-      default: {
-        // For all other priorities we will act like in case of request’s
-        // last_sequence_id is == last_sequence_id above - observing the
-        // backoff time expiration.
-      }
-    }
-  }
-  // Allow upload only if earliest retry time has passed.
-  // Return delta till the allowed time - if positive, upload is going to be
-  // rejected.
-  return state->earliest_retry_timestamp -
-         state->backoff_entry->GetTimeTicksNow();
-}
-
-void EncryptedReportingJobConfiguration::CancelNotAllowedJob() {
-  std::move(callback_).Run(
-      /*job=*/nullptr, DeviceManagementStatus::DM_STATUS_REQUEST_FAILED,
-      /*response_code=*/DeviceManagementService::kTooManyRequests,
-      /*response_body=*/std::nullopt);
-}
-
-void EncryptedReportingJobConfiguration::AccountForAllowedJob() {
-  auto* const state = AccessState(priority_, generation_id_, sequence_id_);
-  // Update state to reflect highest sequence_id_ (we never allow upload with
-  // lower sequence_id_).
-  state->last_sequence_id = sequence_id_;
-  // Calculate delay as exponential backoff (based on the retry_count).
-  // Update backoff under assumption that this request fails.
-  // If it is responded successfully, we will reset it.
-  state->backoff_entry->InformOfRequest(/*succeeded=*/false);
-  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
-}
-
 DeviceManagementService::Job::RetryMethod
 EncryptedReportingJobConfiguration::ShouldRetry(
     int response_code,
@@ -287,26 +86,10 @@
     int net_error,
     int response_code,
     const std::string& response_body) {
-  // Analyze the net error and update upload state for possible future retries.
-  auto* const state = AccessState(priority_, generation_id_, sequence_id_);
-  if (net_error != ::net::OK) {
-    // Network error
-  } else if (response_code >= 400 && response_code <= 499 &&
-             response_code != 409 /* Overlapping seq_id ranges detected */) {
-    // Permanent error code returned by server, impose artificial 24h backoff.
-    state->backoff_entry->SetCustomReleaseTime(
-        state->backoff_entry->GetTimeTicksNow() + base::Days(1));
-  }
-  // For all other cases keep the currently set retry time.
-  // In case of success, inform backoff entry about that.
-  if (net_error == ::net::OK &&
-      response_code == DeviceManagementService::kSuccess) {
-    state->backoff_entry->InformOfRequest(/*succeeded=*/true);
-  }
-  // Cache earliest retry time based on the current backoff entry.
-  state->earliest_retry_timestamp = state->backoff_entry->GetReleaseTime();
-
-  // Then deliver response and status by making a call to the base class.
+  // Delegate net error and response code for further analysis that may
+  // affect retries and back-off.
+  std::move(response_cb_).Run(net_error, response_code);
+  // Then forward response to the base class.
   ReportingJobConfigurationBase::OnURLLoadComplete(
       job, net_error, response_code, response_body);
 }
@@ -331,10 +114,4 @@
           reporting::json_keys::kRequestId, reporting::json_keys::kSource}};
   return *kTopLevelKeyAllowList;
 }
-
-// static
-void EncryptedReportingJobConfiguration::ResetUploadsStateForTest() {
-  state_map()->clear();
-}
-
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
index e5ce866..b28dde9 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -85,12 +85,16 @@
 class POLICY_EXPORT EncryptedReportingJobConfiguration
     : public ReportingJobConfigurationBase {
  public:
+  using UploadResponseCallback =
+      base::OnceCallback<void(int /*net_error*/, int /*response_code*/)>;
+
   EncryptedReportingJobConfiguration(
       scoped_refptr<network::SharedURLLoaderFactory> factory,
       DMAuth auth_data,
       const std::string& server_url,
       base::Value::Dict merging_payload,
       CloudPolicyClient* cloud_policy_client,
+      UploadResponseCallback response_cb,
       UploadCompleteCallback complete_cb);
   ~EncryptedReportingJobConfiguration() override;
 
@@ -101,26 +105,12 @@
   // fields (check reporting::GetContext for specifics).
   void UpdateContext(base::Value::Dict context);
 
-  // Checks the new job against the history, determines how soon the upload will
-  // be allowed. Returns positive value if not allowed, and 0 or negative
-  // otherwise.
-  base::TimeDelta WhenIsAllowedToProceed() const;
-
-  // Account for the job, that was allowed to proceed.
-  void AccountForAllowedJob();
-
-  // Cancels the job, that was not allowed to proceed.
-  void CancelNotAllowedJob();
-
   // Callback to process error codes and, in case of success, response body.
   void OnURLLoadComplete(DeviceManagementService::Job* job,
                          int net_error,
                          int response_code,
                          const std::string& response_body) override;
 
-  // Test-only method that resets collected uploads state.
-  static void ResetUploadsStateForTest();
-
  protected:
   void UpdatePayloadBeforeGetInternal() override;
 
@@ -135,11 +125,7 @@
   static const base::flat_set<std::string>& GetTopLevelKeyAllowList();
   const bool is_device_managed_;
 
-  // Parameters populated from the payload_.
-  ::reporting::Priority priority_;
-  int64_t generation_id_{-1};
-  int64_t sequence_id_{-1};
-  size_t record_count_{0u};
+  UploadResponseCallback response_cb_;
 };
 
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
index 10caefc..b768c19 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
@@ -25,6 +25,7 @@
 #include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/reporting/util/encrypted_reporting_json_keys.h"
 #include "components/version_info/version_info.h"
+#include "net/http/http_status_code.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -35,8 +36,10 @@
 
 using ::testing::_;
 using ::testing::ByRef;
+using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::Ge;
+using ::testing::IsEmpty;
 using ::testing::IsNull;
 using ::testing::MockFunction;
 using ::testing::NotNull;
@@ -184,15 +187,17 @@
                                            DeviceManagementStatus code,
                                            int response_code,
                                            std::optional<base::Value::Dict>)>;
+  using MockUploadResponseCb =
+      MockFunction<void(int /*net_error*/, int /*response_code*/)>;
   struct TestUpload {
     std::unique_ptr<EncryptedReportingJobConfiguration> configuration;
     std::unique_ptr<StrictMock<MockCompleteCb>> completion_cb;
+    std::unique_ptr<StrictMock<MockUploadResponseCb>> upload_response_cb;
     base::Value::Dict response;
     DeviceManagementService::Job job;
   };
 
   void SetUp() override {
-    EncryptedReportingJobConfiguration::ResetUploadsStateForTest();
     shared_url_loader_factory_ =
         base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
             &url_loader_factory_);
@@ -208,11 +213,16 @@
             reporting::json_keys::kSequenceInformation),
         std::nullopt);
     test_upload.completion_cb = std::make_unique<StrictMock<MockCompleteCb>>();
+    test_upload.upload_response_cb =
+        std::make_unique<StrictMock<MockUploadResponseCb>>();
     test_upload.configuration =
         std::make_unique<EncryptedReportingJobConfiguration>(
             shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
             kServerUrl, RequestPayloadBuilder().AddRecord(record_value).Build(),
             &client_,
+            base::BindOnce(
+                &MockUploadResponseCb::Call,
+                base::Unretained(test_upload.upload_response_cb.get())),
             base::BindOnce(&MockCompleteCb::Call,
                            base::Unretained(test_upload.completion_cb.get())));
     return test_upload;
@@ -251,12 +261,13 @@
     return context;
   }
 
-  void GetRecordList(EncryptedReportingJobConfiguration* configuration,
-                     base::Value::List** record_list) {
+  base::Value::List* GetRecordList(
+      EncryptedReportingJobConfiguration* configuration) {
     base::Value* const payload = GetPayload(configuration);
-    *record_list =
+    auto* const record_list =
         payload->GetDict().FindList(reporting::json_keys::kEncryptedRecordList);
-    ASSERT_TRUE(*record_list);
+    EXPECT_THAT(record_list, NotNull());
+    return record_list;
   }
 
   bool GetAttachEncryptionSettings(
@@ -329,6 +340,7 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, RequestPayloadBuilder().Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
   auto* payload = GetPayload(&configuration);
   const base::Value::Dict& payload_dict = payload->GetDict();
@@ -370,6 +382,7 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::NoAuth(), kServerUrl,
       RequestPayloadBuilder().Build(), /*cloud_policy_client=*/nullptr,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
   auto* payload = GetPayload(&configuration);
   ASSERT_THAT(payload, NotNull());
@@ -420,16 +433,14 @@
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, RequestPayloadBuilder().AddRecord(record_value).Build(),
       &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-  EXPECT_EQ(record_list->size(), 1u);
-  EXPECT_EQ((*record_list)[0], record_value);
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, ElementsAre(Eq(ByRef(record_value))));
 
-  std::string* encrypted_wrapped_record =
-      (*record_list)[0].GetDict().FindString(
-          reporting::json_keys::kEncryptedWrappedRecord);
+  auto* const encrypted_wrapped_record = (*record_list)[0].GetDict().FindString(
+      reporting::json_keys::kEncryptedWrappedRecord);
   ASSERT_THAT(encrypted_wrapped_record, NotNull());
 
   std::string decoded_record;
@@ -453,17 +464,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_EQ(record_list->size(), records.size());
-
-  size_t counter = 0;
-  for (const auto& record : records) {
-    EXPECT_EQ((*record_list)[counter++], record);
-  }
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, Eq(ByRef(records)));
 
   EXPECT_FALSE(GetAttachEncryptionSettings(&configuration));
 }
@@ -478,12 +483,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_TRUE(record_list->empty());
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, IsEmpty());
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
 }
@@ -504,17 +508,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_EQ(record_list->size(), records.size());
-
-  size_t counter = 0;
-  for (const auto& record : records) {
-    EXPECT_EQ((*record_list)[counter++], record);
-  }
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, Eq(ByRef(records)));
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
 }
@@ -528,12 +526,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_TRUE(record_list->empty());
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, IsEmpty());
 
   EXPECT_TRUE(VerifyConfigurationFileVersion(&configuration));
 }
@@ -547,12 +544,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_TRUE(record_list->empty());
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, IsEmpty());
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
   EXPECT_TRUE(VerifyConfigurationFileVersion(&configuration));
@@ -576,17 +572,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_EQ(record_list->size(), records.size());
-
-  size_t counter = 0;
-  for (const auto& record : records) {
-    EXPECT_EQ((*record_list)[counter++], record);
-  }
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, Eq(ByRef(records)));
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
   EXPECT_TRUE(VerifyConfigurationFileVersion(&configuration));
@@ -601,12 +591,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_TRUE(record_list->empty());
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, IsEmpty());
 
   EXPECT_TRUE(VerifySourceIsTast(&configuration));
 }
@@ -622,12 +611,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_TRUE(record_list->empty());
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, IsEmpty());
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
   EXPECT_TRUE(VerifyConfigurationFileVersion(&configuration));
@@ -653,17 +641,11 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, builder.Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
-  base::Value::List* record_list = nullptr;
-  GetRecordList(&configuration, &record_list);
-
-  EXPECT_EQ(record_list->size(), records.size());
-
-  size_t counter = 0;
-  for (const auto& record : records) {
-    EXPECT_EQ((*record_list)[counter++], record);
-  }
+  auto* const record_list = GetRecordList(&configuration);
+  EXPECT_THAT(*record_list, Eq(ByRef(records)));
 
   EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
   EXPECT_TRUE(VerifyConfigurationFileVersion(&configuration));
@@ -677,6 +659,7 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, RequestPayloadBuilder().Build(), &client_,
+      /*response_cb=*/base::DoNothing(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
 
   const std::string kTestKey = "device.name";
@@ -728,6 +711,8 @@
                                           DeviceManagementService::kSuccess,
                                           Eq(ByRef(upload.response))))
       .Times(1);
+  EXPECT_CALL(*upload.upload_response_cb, Call(Eq(net::OK), Eq(net::HTTP_OK)))
+      .Times(1);
 
   const std::string kTestString = "device.clientId";
   const std::string kTestInt = "1701-A";
@@ -746,171 +731,24 @@
   EXPECT_CALL(completion_cb, Call(&job, DM_STATUS_REQUEST_FAILED, _,
                                   testing::Eq(std::nullopt)))
       .Times(1);
+  StrictMock<MockUploadResponseCb> upload_response_cb;
+  EXPECT_CALL(upload_response_cb, Call(Eq(net::ERR_CONNECTION_RESET), _))
+      .Times(1);
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, RequestPayloadBuilder().Build(), &client_,
+      base::BindOnce(&MockUploadResponseCb::Call,
+                     base::Unretained(&upload_response_cb)),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
   configuration.OnURLLoadComplete(&job, net::ERR_CONNECTION_RESET,
                                   0 /* ignored */, "");
 }
-
-TEST_F(EncryptedReportingJobConfigurationTest,
-       IdenticalUploadRetriesThrottled) {
-  const size_t kTotalRetries = 10;
-  const std::string kEncryptedWrappedRecord = "TEST_INFO";
-  base::Value record_value =
-      GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
-
-  base::TimeDelta expected_delay_after = base::Seconds(10);
-  for (size_t i = 0; i < kTotalRetries; ++i) {
-    auto upload = CreateTestUpload(record_value);
-    // Expect upload to fail with a temporary error, to justify a retry.
-    EXPECT_CALL(*upload.completion_cb,
-                Call(&upload.job, DM_STATUS_TEMPORARY_UNAVAILABLE,
-                     DeviceManagementService::kServiceUnavailable,
-                     Eq(ByRef(upload.response))))
-        .Times(1);
-
-    auto allowed_delay = upload.configuration->WhenIsAllowedToProceed();
-    if (i == 0) {
-      // First upload allowed immediately.
-      EXPECT_FALSE(allowed_delay.is_positive());
-    } else {
-      // Further uploads allowed with delay.
-      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
-      // Double the expectation for the next retry.
-      expected_delay_after *= 2;
-      // Move forward to allow.
-      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
-      EXPECT_TRUE(upload.configuration->WhenIsAllowedToProceed().is_positive());
-      task_environment_.FastForwardBy(base::Seconds(1));
-    }
-
-    EXPECT_FALSE(upload.configuration->WhenIsAllowedToProceed().is_positive());
-    upload.configuration->AccountForAllowedJob();
-    // Process temporary error response code.
-    upload.configuration->OnURLLoadComplete(
-        &upload.job, net::OK, DeviceManagementService::kServiceUnavailable,
-        ResponseValueBuilder::CreateResponseString(upload.response));
-  }
-}
-
-TEST_F(EncryptedReportingJobConfigurationTest, UploadsSequenceThrottled) {
-  const size_t kTotalRetries = 10;
-  const std::string kEncryptedWrappedRecord = "TEST_INFO";
-
-  std::vector<TestUpload> uploads;
-  base::TimeDelta expected_delay_after = base::Seconds(10);
-  for (size_t i = 0; i < kTotalRetries; ++i) {
-    // Create new record with next seq id.
-    base::Value record_value =
-        GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
-
-    uploads.emplace_back(CreateTestUpload(record_value));
-    auto allowed_delay = uploads.back().configuration->WhenIsAllowedToProceed();
-    if (i == 0) {
-      EXPECT_FALSE(allowed_delay.is_positive());
-      // Next retry not before 10 sec.
-    } else {
-      EXPECT_THAT(allowed_delay, Ge(expected_delay_after));
-      // Double the expectation for the next upload.
-      expected_delay_after *= 2;
-      // Move forward to allow.
-      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
-      EXPECT_TRUE(
-          uploads.back().configuration->WhenIsAllowedToProceed().is_positive());
-      task_environment_.FastForwardBy(base::Seconds(1));
-    }
-
-    EXPECT_FALSE(
-        uploads.back().configuration->WhenIsAllowedToProceed().is_positive());
-    uploads.back().configuration->AccountForAllowedJob();
-  }
-
-  // Now complete all created uploads.
-  for (auto& upload : uploads) {
-    EXPECT_CALL(*upload.completion_cb, Call(&upload.job, DM_STATUS_SUCCESS,
-                                            DeviceManagementService::kSuccess,
-                                            Eq(ByRef(upload.response))))
-        .Times(1);
-    upload.configuration->OnURLLoadComplete(
-        &upload.job, net::OK, DeviceManagementService::kSuccess,
-        ResponseValueBuilder::CreateResponseString(upload.response));
-  }
-}
-
-TEST_F(EncryptedReportingJobConfigurationTest,
-       SecurityUploadsSequenceNotThrottled) {
-  const size_t kTotalRetries = 10;
-  const std::string kEncryptedWrappedRecord = "TEST_INFO";
-
-  std::vector<TestUpload> uploads;
-  for (size_t i = 0; i < kTotalRetries; ++i) {
-    // Create new record with next seq id.
-    base::Value record_value = GenerateSingleRecord(
-        kEncryptedWrappedRecord, ::reporting::Priority::SECURITY);
-
-    uploads.emplace_back(CreateTestUpload(record_value));
-    auto allowed_delay = uploads.back().configuration->WhenIsAllowedToProceed();
-    EXPECT_FALSE(allowed_delay.is_positive());
-    uploads.back().configuration->AccountForAllowedJob();
-  }
-
-  // Now complete all created uploads.
-  for (auto& upload : uploads) {
-    EXPECT_CALL(*upload.completion_cb, Call(&upload.job, DM_STATUS_SUCCESS,
-                                            DeviceManagementService::kSuccess,
-                                            Eq(ByRef(upload.response))))
-        .Times(1);
-    upload.configuration->OnURLLoadComplete(
-        &upload.job, net::OK, DeviceManagementService::kSuccess,
-        ResponseValueBuilder::CreateResponseString(upload.response));
-  }
-}
-
-TEST_F(EncryptedReportingJobConfigurationTest, FailedUploadsSequenceThrottled) {
-  const size_t kTotalRetries = 10;
-  const std::string kEncryptedWrappedRecord = "TEST_INFO";
-
-  for (size_t i = 0; i < kTotalRetries; ++i) {
-    // Create new record with next seq id.
-    base::Value record_value =
-        GenerateSingleRecord(kEncryptedWrappedRecord, kPriority);
-
-    auto upload = CreateTestUpload(record_value);
-
-    EXPECT_CALL(*upload.completion_cb,
-                Call(&upload.job, DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID,
-                     DeviceManagementService::kInvalidAuthCookieOrDMToken,
-                     Eq(ByRef(upload.response))))
-        .Times(1);
-
-    auto allowed_delay = upload.configuration->WhenIsAllowedToProceed();
-    if (i == 0) {
-      // The very first upload is allowed.
-      EXPECT_FALSE(allowed_delay.is_positive());
-    } else {
-      EXPECT_THAT(allowed_delay, Ge(base::Days(1)));
-      // Move forward to allow.
-      task_environment_.FastForwardBy(allowed_delay - base::Seconds(1));
-      EXPECT_TRUE(upload.configuration->WhenIsAllowedToProceed().is_positive());
-      task_environment_.FastForwardBy(base::Seconds(1));
-    }
-
-    EXPECT_FALSE(upload.configuration->WhenIsAllowedToProceed().is_positive());
-    upload.configuration->AccountForAllowedJob();
-    upload.configuration->OnURLLoadComplete(
-        &upload.job, net::OK,
-        DeviceManagementService::kInvalidAuthCookieOrDMToken,
-        ResponseValueBuilder::CreateResponseString(upload.response));
-  }
-}
-
 TEST_F(EncryptedReportingJobConfigurationTest, ManagedDeviceUmaName) {
   // Non-null cloud policy client indicates device is unmanaged.
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
-      kServerUrl, RequestPayloadBuilder().Build(), &client_, base::DoNothing());
+      kServerUrl, RequestPayloadBuilder().Build(), &client_, base::DoNothing(),
+      base::DoNothing());
 
   EXPECT_EQ(configuration.GetUmaName(),
             "Browser.ERP.ManagedUploadEncryptedReport");
@@ -921,7 +759,7 @@
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
       kServerUrl, RequestPayloadBuilder().Build(),
-      /*cloud_policy_client=*/nullptr, base::DoNothing());
+      /*cloud_policy_client=*/nullptr, base::DoNothing(), base::DoNothing());
 
   EXPECT_EQ(configuration.GetUmaName(),
             "Browser.ERP.UnmanagedUploadEncryptedReport");
@@ -942,7 +780,8 @@
 
   EncryptedReportingJobConfiguration configuration(
       shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
-      kServerUrl, std::move(request), &client_, base::DoNothing());
+      kServerUrl, std::move(request), &client_, base::DoNothing(),
+      base::DoNothing());
 
   std::optional<base::Value> payload =
       base::JSONReader::Read(configuration.GetPayload());
diff --git a/components/safe_browsing/content/browser/async_check_tracker.cc b/components/safe_browsing/content/browser/async_check_tracker.cc
index 3073a78..6970028 100644
--- a/components/safe_browsing/content/browser/async_check_tracker.cc
+++ b/components/safe_browsing/content/browser/async_check_tracker.cc
@@ -17,6 +17,16 @@
 
 using security_interstitials::UnsafeResource;
 
+// The threshold that will trigger a cleanup on
+// `committed_navigation_timestamps_`.
+constexpr int kNavigationTimestampsSizeThreshold = 10000;
+
+// Navigation timestamps that are older than this interval are considered
+// expired and may be cleaned up. This interval must be much larger than the
+// life time of UrlCheckerOnSB so that IsMainPageLoadPending returns the correct
+// result when the check completes.
+constexpr base::TimeDelta kNavigationTimestampExpiration = base::Seconds(180);
+
 }  // namespace
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(AsyncCheckTracker);
@@ -67,7 +77,9 @@
                                      scoped_refptr<BaseUIManager> ui_manager)
     : content::WebContentsUserData<AsyncCheckTracker>(*web_contents),
       content::WebContentsObserver(web_contents),
-      ui_manager_(std::move(ui_manager)) {}
+      ui_manager_(std::move(ui_manager)),
+      navigation_timestamps_size_threshold_(
+          kNavigationTimestampsSizeThreshold) {}
 
 AsyncCheckTracker::~AsyncCheckTracker() {
   DeletePendingCheckers(/*excluded_navigation_id=*/std::nullopt);
@@ -145,6 +157,13 @@
     // AsyncCheckTracker is created) and then a prerendered navigation starts
     // on the same WebContents.
     committed_navigation_timestamps_[navigation_id] = base::TimeTicks::Now();
+    if (committed_navigation_timestamps_.size() >
+        navigation_timestamps_size_threshold_) {
+      content::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(&AsyncCheckTracker::DeleteExpiredNavigationTimestamps,
+                         GetWeakPtr()));
+    }
   }
   base::UmaHistogramCounts10000(
       "SafeBrowsing.AsyncCheck.CommittedNavigationIdsSize",
@@ -245,10 +264,23 @@
   }
 }
 
+void AsyncCheckTracker::DeleteExpiredNavigationTimestamps() {
+  base::EraseIf(committed_navigation_timestamps_,
+                [&](const auto& id_timestamp_pair) {
+                  return base::TimeTicks::Now() - id_timestamp_pair.second >
+                         kNavigationTimestampExpiration;
+                });
+}
+
 size_t AsyncCheckTracker::PendingCheckersSizeForTesting() {
   return pending_checkers_.size();
 }
 
+void AsyncCheckTracker::SetNavigationTimestampsSizeThresholdForTesting(
+    size_t threshold) {
+  navigation_timestamps_size_threshold_ = threshold;
+}
+
 void AsyncCheckTracker::SetOnAllCheckersCompletedForTesting(
     base::OnceClosure callback) {
   on_all_checkers_completed_callback_for_testing_ = std::move(callback);
diff --git a/components/safe_browsing/content/browser/async_check_tracker.h b/components/safe_browsing/content/browser/async_check_tracker.h
index 26b1a80..382c8ef 100644
--- a/components/safe_browsing/content/browser/async_check_tracker.h
+++ b/components/safe_browsing/content/browser/async_check_tracker.h
@@ -42,11 +42,17 @@
   // yet committed). Note that a main frame hit may not be pending, eg. 1)
   // client side detection happens after the load is committed, or 2) async Safe
   // Browsing check is enabled.
+  // Caveat: This class only tracks committed navigation ids for a
+  // certain period, so this function may not return the correct result if the
+  // navigation associated with the `resource` is too old.
   static bool IsMainPageLoadPending(
       const security_interstitials::UnsafeResource& resource);
 
   // Returns the timestamp when the navigation associated with `resource` is
   // committed. Returns nullopt if the navigation has not committed.
+  // Caveat: This class only tracks committed navigation ids for a
+  // certain period, so this function may not return the correct result if the
+  // navigation associated with the `resource` is too old.
   static std::optional<base::TimeTicks> GetBlockedPageCommittedTimestamp(
       const security_interstitials::UnsafeResource& resource);
 
@@ -76,6 +82,8 @@
 
   size_t PendingCheckersSizeForTesting();
 
+  void SetNavigationTimestampsSizeThresholdForTesting(size_t threshold);
+
   base::WeakPtr<AsyncCheckTracker> GetWeakPtr();
 
  private:
@@ -95,6 +103,10 @@
   // is keyed by `excluded_navigation_id`.
   void DeletePendingCheckers(std::optional<int64_t> excluded_navigation_id);
 
+  // Deletes expired timestamps to avoid `committed_navigation_timestamps_`
+  // getting too large.
+  void DeleteExpiredNavigationTimestamps();
+
   // Displays an interstitial if there is unsafe resource associated with
   // `redirect_chain` and `navigation_id`.
   void MaybeDisplayBlockingPage(const std::vector<GURL>& redirect_chain,
@@ -128,6 +140,10 @@
   // used for logging metrics.
   base::flat_map<int64_t, base::TimeTicks> committed_navigation_timestamps_;
 
+  // The threshold that will trigger a cleanup on
+  // `committed_navigation_timestamps_`. Overridden in tests.
+  size_t navigation_timestamps_size_threshold_;
+
   // Callback that is called once all checkers are completed. Used only for
   // tests.
   base::OnceClosure on_all_checkers_completed_callback_for_testing_;
diff --git a/components/safe_browsing/content/browser/async_check_tracker_unittest.cc b/components/safe_browsing/content/browser/async_check_tracker_unittest.cc
index 8aa6a1ae..32a0d67 100644
--- a/components/safe_browsing/content/browser/async_check_tracker_unittest.cc
+++ b/components/safe_browsing/content/browser/async_check_tracker_unittest.cc
@@ -49,6 +49,8 @@
   UnsafeResource displayed_resource_;
 };
 
+constexpr int kLocalNavigationTimestampsSizeThreshold = 5;
+
 }  // namespace
 
 class AsyncCheckTrackerTest : public content::RenderViewHostTestHarness,
@@ -56,7 +58,8 @@
  protected:
   AsyncCheckTrackerTest()
       : RenderViewHostTestHarness(
-            content::BrowserTaskEnvironment::REAL_IO_THREAD) {
+            content::BrowserTaskEnvironment::REAL_IO_THREAD,
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
     bool sb_on_ui_thread_enabled = GetParam();
     if (sb_on_ui_thread_enabled) {
       feature_list_.InitWithFeatures(
@@ -294,6 +297,80 @@
   EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
 }
 
+TEST_P(AsyncCheckTrackerTest,
+       IsMainPageLoadPending_DeleteExpiredNavigationTimestamps) {
+  tracker_->SetNavigationTimestampsSizeThresholdForTesting(
+      kLocalNavigationTimestampsSizeThreshold);
+  UnsafeResource resource;
+  resource.threat_type = SB_THREAT_TYPE_URL_PHISHING;
+  resource.frame_tree_node_id = main_rfh()->GetFrameTreeNodeId();
+
+  std::vector<int64_t> old_navigation_ids;
+  for (int i = 0; i < kLocalNavigationTimestampsSizeThreshold; i++) {
+    content::MockNavigationHandle handle(url_, main_rfh());
+    old_navigation_ids.push_back(handle.GetNavigationId());
+    CallDidFinishNavigation(handle, /*has_committed=*/true);
+  }
+  for (int64_t id : old_navigation_ids) {
+    resource.navigation_id = id;
+    EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+  }
+
+  task_environment()->FastForwardBy(base::Seconds(180));
+  content::MockNavigationHandle recent_handle1(url_, main_rfh());
+  CallDidFinishNavigation(recent_handle1, /*has_committed=*/true);
+  for (int64_t id : old_navigation_ids) {
+    resource.navigation_id = id;
+    EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+  }
+
+  task_environment()->FastForwardBy(base::Seconds(1));
+  content::MockNavigationHandle recent_handle2(url_, main_rfh());
+  CallDidFinishNavigation(recent_handle2, /*has_committed=*/true);
+  // The old navigation timestamps have been cleaned up, so the function returns
+  // true.
+  for (int64_t id : old_navigation_ids) {
+    resource.navigation_id = id;
+    EXPECT_TRUE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+  }
+
+  // The recent timestamps should not be cleaned up.
+  resource.navigation_id = recent_handle1.GetNavigationId();
+  EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+  resource.navigation_id = recent_handle2.GetNavigationId();
+  EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+}
+
+TEST_P(
+    AsyncCheckTrackerTest,
+    IsMainPageLoadPending_DeleteExpiredNavigationTimestamps_NotReachingThreshold) {
+  tracker_->SetNavigationTimestampsSizeThresholdForTesting(
+      kLocalNavigationTimestampsSizeThreshold);
+  UnsafeResource resource;
+  resource.threat_type = SB_THREAT_TYPE_URL_PHISHING;
+  resource.frame_tree_node_id = main_rfh()->GetFrameTreeNodeId();
+
+  content::MockNavigationHandle handle(url_, main_rfh());
+  CallDidFinishNavigation(handle, /*has_committed=*/true);
+  resource.navigation_id = handle.GetNavigationId();
+  EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+
+  task_environment()->FastForwardBy(base::Seconds(181));
+  content::MockNavigationHandle recent_handle(url_, main_rfh());
+  CallDidFinishNavigation(recent_handle, /*has_committed=*/true);
+  // The timestamp has expired but not cleaned up, because the size of the
+  // timestamps has not reached the threshold.
+  EXPECT_FALSE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+
+  for (int i = 0; i < kLocalNavigationTimestampsSizeThreshold - 1; i++) {
+    content::MockNavigationHandle new_handle(url_, main_rfh());
+    CallDidFinishNavigation(new_handle, /*has_committed=*/true);
+  }
+  // The size of the timestamps has reached the threshold, so the old timestamp
+  // is cleaned up.
+  EXPECT_TRUE(AsyncCheckTracker::IsMainPageLoadPending(resource));
+}
+
 TEST_P(AsyncCheckTrackerTest, GetBlockedPageCommittedTimestamp) {
   content::MockNavigationHandle handle(web_contents());
   UnsafeResource resource;
diff --git a/components/user_manager/multi_user/multi_user_sign_in_policy.h b/components/user_manager/multi_user/multi_user_sign_in_policy.h
index 7113a06..e843786 100644
--- a/components/user_manager/multi_user/multi_user_sign_in_policy.h
+++ b/components/user_manager/multi_user/multi_user_sign_in_policy.h
@@ -13,8 +13,14 @@
 namespace user_manager {
 
 enum class MultiUserSignInPolicy {
+  // The user is allowed to be either a primary user or secondary user in
+  // multi user sign-in sessions.
   kUnrestricted = 0,
+
+  // The user can be only be a primary user in multi user sign-in sessions.
   kPrimaryOnly = 1,
+
+  // The user cannot be a part of multi user sign-in sessions.
   kNotAllowed = 2,
 };
 
diff --git a/components/variations/service/limited_entropy_synthetic_trial.cc b/components/variations/service/limited_entropy_synthetic_trial.cc
index 8b0347b..e5d56be 100644
--- a/components/variations/service/limited_entropy_synthetic_trial.cc
+++ b/components/variations/service/limited_entropy_synthetic_trial.cc
@@ -5,6 +5,7 @@
 
 #include <cstdint>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/pref_names.h"
@@ -12,23 +13,43 @@
 namespace variations {
 namespace {
 
+#if BUILDFLAG(IS_CHROMEOS)
+// A flag that is used to make sure that if seed of the trial is sync'ed from
+// Ash to Lacros, the trial should only be randomized after the seed is set.
+bool g_trial_is_randomized = false;
+#endif
+
 // The percentage of population that is enabled in this trial. It can be either
 // 100 or an integer within [0, 50].
 constexpr uint64_t kEnabledPercentage = 50;
 
+bool IsValidTrialSeed(uint64_t seed) {
+  return seed > 0 && seed <= 100;
+}
+
+uint64_t GenerateTrialSeed() {
+  // base::RandGenerator(100) will return a number within [0, 100). Adding one
+  // to avoid 0 being a valid value since 0 might be a default uint64 value.
+  auto seed = base::RandGenerator(100) + 1;
+  CHECK(IsValidTrialSeed(seed));
+  return seed;
+}
+
 std::string_view SelectGroup(PrefService* local_state) {
   static_assert((kEnabledPercentage >= 0 && kEnabledPercentage <= 50) ||
                 kEnabledPercentage == 100);
+#if BUILDFLAG(IS_CHROMEOS)
+  g_trial_is_randomized = true;
+#endif
   auto* seed_pref_name = prefs::kVariationsLimitedEntropySyntheticTrialSeed;
   if (!local_state->HasPrefPath(seed_pref_name)) {
-    // base::RandGenerator(100) will return a number within [0, 100).
-    local_state->SetUint64(seed_pref_name, base::RandGenerator(100));
+    local_state->SetUint64(seed_pref_name, GenerateTrialSeed());
   }
   auto rand_val = local_state->GetUint64(seed_pref_name);
 
-  if (rand_val < kEnabledPercentage) {
+  if (rand_val <= kEnabledPercentage) {
     return kLimitedEntropySyntheticTrialEnabled;
-  } else if (rand_val < 2 * kEnabledPercentage) {
+  } else if (rand_val <= 2 * kEnabledPercentage) {
     return kLimitedEntropySyntheticTrialControl;
   } else {
     return kLimitedEntropySyntheticTrialDefault;
@@ -54,6 +75,41 @@
       variations::prefs::kVariationsLimitedEntropySyntheticTrialSeed, 0);
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+// static
+void LimitedEntropySyntheticTrial::SetSeedFromAsh(PrefService* local_state,
+                                                  uint64_t seed) {
+  // This CHECK is defense in depth and is not expected to happen since this
+  // method will be called before the creation of
+  // `metrics::MetricsStateManager`, which is a dependency of
+  // `variations::VariationsService`. `VariationsService` will control the
+  // randomization of this trial through calling its constructor.
+  CHECK(!g_trial_is_randomized);
+
+  // The trial seed is only expected to be invalid when there is a version skew,
+  // in which the Ash Chrome's version is older at a point that it is not
+  // sending the seed over. In this case, the mojo field will carry a zero
+  // value, which is an invalid seed.
+  bool is_valid_seed = IsValidTrialSeed(seed);
+  base::UmaHistogramBoolean(kIsLimitedEntropySyntheticTrialSeedValidHistogram,
+                            is_valid_seed);
+  if (is_valid_seed) {
+    local_state->SetUint64(prefs::kVariationsLimitedEntropySyntheticTrialSeed,
+                           seed);
+  }
+}
+
+// static
+uint64_t LimitedEntropySyntheticTrial::GetRandomizationSeed(
+    PrefService* local_state) {
+  // Initialize the trial to set the value of
+  // |kVariationsLimitedEntropySyntheticTrialSeed|.
+  LimitedEntropySyntheticTrial trial(local_state);
+  return local_state->GetUint64(
+      prefs::kVariationsLimitedEntropySyntheticTrialSeed);
+}
+#endif
+
 bool LimitedEntropySyntheticTrial::IsEnabled() {
   return group_name_ == kLimitedEntropySyntheticTrialEnabled;
 }
diff --git a/components/variations/service/limited_entropy_synthetic_trial.h b/components/variations/service/limited_entropy_synthetic_trial.h
index 2ef9878..f009c5d 100644
--- a/components/variations/service/limited_entropy_synthetic_trial.h
+++ b/components/variations/service/limited_entropy_synthetic_trial.h
@@ -19,6 +19,9 @@
 inline constexpr char kLimitedEntropySyntheticTrialControl[] = "Control";
 inline constexpr char kLimitedEntropySyntheticTrialDefault[] = "Default";
 
+inline constexpr char kIsLimitedEntropySyntheticTrialSeedValidHistogram[] =
+    "Variations.LimitedEntropyTrial.AshSeedIsValid.OnSyncToLacros";
+
 class LimitedEntropySyntheticTrial {
  public:
   explicit LimitedEntropySyntheticTrial(PrefService* local_state);
@@ -31,6 +34,20 @@
   // Registers the prefs needed for this trial.
   static void RegisterPrefs(PrefRegistrySimple* registry);
 
+#if BUILDFLAG(IS_CHROMEOS)
+  // Overrides the seed of this trial with the value used in Ash chrome. Note
+  // this method needs to be called before instantiation of the trial for the
+  // seed to take effect. This should only be used by the Lacros client.
+  static void SetSeedFromAsh(PrefService* local_state, uint64_t seed);
+
+  // Returns the randomization seed of this trial. This should only be used by
+  // the Ash Chrome client when sending the seed to Lacros, or in tests.
+  //
+  // Side effect: Initializes the seed, storing the result to prefs, if the seed
+  // was not already initialized.
+  static uint64_t GetRandomizationSeed(PrefService* local_state);
+#endif
+
   // Returns whether the client is in the enabled group for this trial.
   bool IsEnabled();
 
diff --git a/components/variations/service/limited_entropy_synthetic_trial_unittest.cc b/components/variations/service/limited_entropy_synthetic_trial_unittest.cc
index 7830672..769b8a4 100644
--- a/components/variations/service/limited_entropy_synthetic_trial_unittest.cc
+++ b/components/variations/service/limited_entropy_synthetic_trial_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "components/variations/service/limited_entropy_synthetic_trial.h"
 
+#include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/variations/pref_names.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -18,6 +20,7 @@
 
  protected:
   TestingPrefServiceSimple local_state_;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(LimitedEntropySyntheticTrialTest, RandomizesWithExistingSeed_Enabled) {
@@ -62,4 +65,42 @@
   }
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+TEST_F(LimitedEntropySyntheticTrialTest, TestSetSeedFromAsh) {
+  LimitedEntropySyntheticTrial::SetSeedFromAsh(&local_state_, 42u);
+  LimitedEntropySyntheticTrial trial(&local_state_);
+
+  EXPECT_EQ(42u, trial.GetRandomizationSeed(&local_state_));
+  histogram_tester_.ExpectUniqueSample(
+      kIsLimitedEntropySyntheticTrialSeedValidHistogram, true, 1);
+}
+
+TEST_F(LimitedEntropySyntheticTrialTest,
+       TestSetSeedFromAsh_ExpectCheckIFailureIfRandomizedBeforeSyncingSeed) {
+  LimitedEntropySyntheticTrial trial(&local_state_);
+  EXPECT_CHECK_DEATH(
+      LimitedEntropySyntheticTrial::SetSeedFromAsh(&local_state_, 42u));
+}
+
+TEST_F(
+    LimitedEntropySyntheticTrialTest,
+    TestSetSeedFromAsh_ExpectCheckIFailureIfSettingSeedAgainAfterRandomization) {
+  LimitedEntropySyntheticTrial::SetSeedFromAsh(&local_state_, 42u);
+  LimitedEntropySyntheticTrial trial(&local_state_);
+  EXPECT_CHECK_DEATH(
+      LimitedEntropySyntheticTrial::SetSeedFromAsh(&local_state_, 62u));
+  histogram_tester_.ExpectUniqueSample(
+      kIsLimitedEntropySyntheticTrialSeedValidHistogram, true, 1);
+}
+
+TEST_F(LimitedEntropySyntheticTrialTest,
+       TestSetSeedFromAsh_SyncingInvalidSeed) {
+  LimitedEntropySyntheticTrial::SetSeedFromAsh(&local_state_, 999u);
+  LimitedEntropySyntheticTrial trial(&local_state_);
+  EXPECT_NE(999u, trial.GetRandomizationSeed(&local_state_));
+  histogram_tester_.ExpectUniqueSample(
+      kIsLimitedEntropySyntheticTrialSeedValidHistogram, false, 1);
+}
+
+#endif
 }  // namespace variations
diff --git a/components/variations/service/variations_service.h b/components/variations/service/variations_service.h
index 011edf6..dd2f0099 100644
--- a/components/variations/service/variations_service.h
+++ b/components/variations/service/variations_service.h
@@ -321,6 +321,11 @@
   FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, DoNotRetryAfterARetry);
   FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest,
                            DoNotRetryIfInsecureURLIsHTTPS);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // For the test to access |limited_entropy_synthetic_trial_|.
+  FRIEND_TEST_ALL_PREFIXES(VariationsServiceBrowserTest,
+                           LimitedEntropySyntheticTrialSeedTransfer);
+#endif
 
   void InitResourceRequestedAllowedNotifier();
 
diff --git a/components/viz/service/debugger/viz_debugger.h b/components/viz/service/debugger/viz_debugger.h
index 2e6b5c5..8a98084 100644
--- a/components/viz/service/debugger/viz_debugger.h
+++ b/components/viz/service/debugger/viz_debugger.h
@@ -285,7 +285,7 @@
                                                 __func__);                \
       if (dcs.IsActive()) {                                               \
         viz::VizDebugger::GetInstance()->AddLogMessage(                   \
-            base::StringPrintf(format, __VA_ARGS__), &dcs, option);       \
+            base::StringPrintf(format, ##__VA_ARGS__), &dcs, option);     \
       }                                                                   \
     }                                                                     \
   } while (0)
@@ -465,7 +465,7 @@
   DBG_DRAW_TEXT_OPT(anno, DBG_OPT_BLACK, pos, text)
 
 #define DBG_LOG(anno, format, ...) \
-  DBG_LOG_OPT(anno, DBG_OPT_BLACK, format, __VA_ARGS__)
+  DBG_LOG_OPT(anno, DBG_OPT_BLACK, format, ##__VA_ARGS__)
 
 #define DBG_DRAW_RECT_OPT_BUFF_UV(anno, option, rect, id, uv)             \
   DBG_DRAW_RECTANGLE_OPT_BUFF_UV(                                         \
diff --git a/components/viz/service/display/dc_layer_overlay.cc b/components/viz/service/display/dc_layer_overlay.cc
index 5949052f..a9ea0539 100644
--- a/components/viz/service/display/dc_layer_overlay.cc
+++ b/components/viz/service/display/dc_layer_overlay.cc
@@ -434,7 +434,7 @@
   return !occluding_damage_rect.IsEmpty();
 }
 
-bool IsPossibleFullScreenLetterboxing(const QuadList::Iterator& it,
+bool IsPossibleFullScreenLetterboxing(const QuadList::ConstIterator& it,
                                       QuadList::ConstIterator quad_list_end,
                                       const gfx::Rect& display_rect) {
   // Two cases are considered as possible fullscreen letterboxing:
@@ -654,6 +654,99 @@
   }
 }
 
+// Return value of |ValidateDrawQuad|.
+struct ValidateDrawQuadResult {
+  DCLayerResult code = DC_LAYER_FAILED_UNSUPPORTED_QUAD;
+  bool is_yuv_overlay = false;
+  gpu::Mailbox promotion_hint_mailbox;
+};
+
+ValidateDrawQuadResult ValidateDrawQuad(
+    DisplayResourceProvider* resource_provider,
+    const QuadList::ConstIterator& it,
+    const std::vector<gfx::Rect>& backdrop_filter_rects,
+    const bool is_page_fullscreen_mode,
+    const bool has_overlay_support,
+    const bool has_p010_video_processor_support,
+    const int allowed_yuv_overlay_count,
+    const int processed_yuv_overlay_count,
+    const bool allow_promotion_hinting) {
+  ValidateDrawQuadResult result;
+  switch (it->material) {
+    case DrawQuad::Material::kYuvVideoContent:
+      result.code = ValidateYUVQuad(
+          YUVVideoDrawQuad::MaterialCast(*it), backdrop_filter_rects,
+          is_page_fullscreen_mode, has_overlay_support,
+          has_p010_video_processor_support, allowed_yuv_overlay_count,
+          processed_yuv_overlay_count, resource_provider);
+      result.is_yuv_overlay = true;
+      break;
+
+    case DrawQuad::Material::kTextureContent: {
+      const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it);
+
+      if (tex_quad->is_stream_video) {
+        // Stream video quads contain Media Foundation dcomp surface which is
+        // always presented as overlay.
+        result.code = DC_LAYER_SUCCESS;
+      } else {
+        result.code = ValidateTextureQuad(
+            tex_quad, backdrop_filter_rects, is_page_fullscreen_mode,
+            has_overlay_support, has_p010_video_processor_support,
+            allowed_yuv_overlay_count, processed_yuv_overlay_count,
+            resource_provider);
+      }
+
+      result.is_yuv_overlay = tex_quad->is_video_frame;
+
+      if (allow_promotion_hinting) {
+        // If this quad has marked itself as wanting promotion hints then get
+        // the associated mailbox.
+        ResourceId id = tex_quad->resource_id();
+        if (resource_provider->DoesResourceWantPromotionHint(id)) {
+          result.promotion_hint_mailbox = resource_provider->GetMailbox(id);
+        }
+      }
+    } break;
+
+    default:
+      result.code = DC_LAYER_FAILED_UNSUPPORTED_QUAD;
+      break;
+  }
+
+  return result;
+}
+
+void FromDrawQuad(DisplayResourceProvider* resource_provider,
+                  const AggregatedRenderPass* render_pass,
+                  bool is_page_fullscreen_mode,
+                  const QuadList::ConstIterator& it,
+                  int& processed_yuv_overlay_count,
+                  OverlayCandidate& dc_layer) {
+  dc_layer.possible_video_fullscreen_letterboxing =
+      is_page_fullscreen_mode
+          ? IsPossibleFullScreenLetterboxing(it, render_pass->quad_list.end(),
+                                             render_pass->output_rect)
+          : false;
+  switch (it->material) {
+    case DrawQuad::Material::kYuvVideoContent:
+      FromYUVQuad(YUVVideoDrawQuad::MaterialCast(*it),
+                  render_pass->transform_to_root_target, &dc_layer);
+      processed_yuv_overlay_count++;
+      break;
+    case DrawQuad::Material::kTextureContent: {
+      const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it);
+      FromTextureQuad(tex_quad, render_pass->transform_to_root_target,
+                      resource_provider, &dc_layer);
+      if (tex_quad->is_video_frame) {
+        processed_yuv_overlay_count++;
+      }
+    } break;
+    default:
+      NOTREACHED();
+  }
+}
+
 }  // namespace
 
 DCLayerOverlayProcessor::DCLayerOverlayProcessor(
@@ -887,51 +980,14 @@
       continue;
     }
 
-    gpu::Mailbox promotion_hint_mailbox;
-    DCLayerResult result;
-    bool is_yuv_overlay = false;
-    switch (it->material) {
-      case DrawQuad::Material::kYuvVideoContent:
-        result = ValidateYUVQuad(
-            YUVVideoDrawQuad::MaterialCast(*it), backdrop_filter_rects,
-            is_page_fullscreen_mode, has_overlay_support_,
-            has_p010_video_processor_support_, allowed_yuv_overlay_count_,
-            global_overlay_state.processed_yuv_overlay_count,
-            resource_provider);
-        is_yuv_overlay = true;
-        break;
-      case DrawQuad::Material::kTextureContent: {
-        const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it);
+    ValidateDrawQuadResult result = ValidateDrawQuad(
+        resource_provider, it, backdrop_filter_rects, is_page_fullscreen_mode,
+        has_overlay_support_, has_p010_video_processor_support_,
+        allowed_yuv_overlay_count_,
+        global_overlay_state.processed_yuv_overlay_count,
+        allow_promotion_hinting_);
 
-        if (tex_quad->is_stream_video) {
-          // Stream video quads contain Media Foundation dcomp surface which is
-          // always presented as overlay.
-          result = DC_LAYER_SUCCESS;
-        } else {
-          result = ValidateTextureQuad(
-              tex_quad, backdrop_filter_rects, is_page_fullscreen_mode,
-              has_overlay_support_, has_p010_video_processor_support_,
-              allowed_yuv_overlay_count_,
-              global_overlay_state.processed_yuv_overlay_count,
-              resource_provider);
-        }
-
-        is_yuv_overlay = tex_quad->is_video_frame;
-
-        if (allow_promotion_hinting_) {
-          // If this quad has marked itself as wanting promotion hints then get
-          // the associated mailbox.
-          ResourceId id = tex_quad->resource_id();
-          if (resource_provider->DoesResourceWantPromotionHint(id)) {
-            promotion_hint_mailbox = resource_provider->GetMailbox(id);
-          }
-        }
-      } break;
-      default:
-        result = DC_LAYER_FAILED_UNSUPPORTED_QUAD;
-    }
-
-    if (is_yuv_overlay) {
+    if (result.is_yuv_overlay) {
       global_overlay_state.yuv_quads++;
       if (no_undamaged_overlay_promotion_) {
         if (it->shared_quad_state->overlay_damage_index.has_value() &&
@@ -940,30 +996,31 @@
                                                ->overlay_damage_index.value()]
                  .IsEmpty()) {
           global_overlay_state.damaged_yuv_quads++;
-          if (result == DC_LAYER_SUCCESS) {
+          if (result.code == DC_LAYER_SUCCESS) {
             global_overlay_state.processed_yuv_overlay_count++;
           }
         }
       } else {
-        if (result == DC_LAYER_SUCCESS) {
+        if (result.code == DC_LAYER_SUCCESS) {
           global_overlay_state.processed_yuv_overlay_count++;
         }
       }
     }
 
-    if (!promotion_hint_mailbox.IsZero()) {
+    if (!result.promotion_hint_mailbox.IsZero()) {
       DCHECK(allow_promotion_hinting_);
-      bool promoted = result == DC_LAYER_SUCCESS;
+      bool promoted = result.code == DC_LAYER_SUCCESS;
       auto* overlay_state_service = OverlayStateService::GetInstance();
       // The OverlayStateService should always be initialized by GpuServiceImpl
       // at creation - DCHECK here just to assert there aren't any corner cases
       // where this isn't true.
       DCHECK(overlay_state_service->IsInitialized());
-      overlay_state_service->SetPromotionHint(promotion_hint_mailbox, promoted);
+      overlay_state_service->SetPromotionHint(result.promotion_hint_mailbox,
+                                              promoted);
     }
 
-    if (result != DC_LAYER_SUCCESS) {
-      RecordDCLayerResult(result, it);
+    if (result.code != DC_LAYER_SUCCESS) {
+      RecordDCLayerResult(result.code, it);
       continue;
     }
 
@@ -1240,28 +1297,8 @@
   RecordDCLayerResult(DC_LAYER_SUCCESS, it);
 
   OverlayCandidate dc_layer;
-  dc_layer.possible_video_fullscreen_letterboxing =
-      is_page_fullscreen_mode
-          ? IsPossibleFullScreenLetterboxing(it, render_pass->quad_list.end(),
-                                             render_pass->output_rect)
-          : false;
-  switch (it->material) {
-    case DrawQuad::Material::kYuvVideoContent:
-      FromYUVQuad(YUVVideoDrawQuad::MaterialCast(*it),
-                  render_pass->transform_to_root_target, &dc_layer);
-      global_overlay_state.processed_yuv_overlay_count++;
-      break;
-    case DrawQuad::Material::kTextureContent: {
-      const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it);
-      FromTextureQuad(tex_quad, render_pass->transform_to_root_target,
-                      resource_provider, &dc_layer);
-      if (tex_quad->is_video_frame) {
-        global_overlay_state.processed_yuv_overlay_count++;
-      }
-    } break;
-    default:
-      NOTREACHED();
-  }
+  FromDrawQuad(resource_provider, render_pass, is_page_fullscreen_mode, it,
+               global_overlay_state.processed_yuv_overlay_count, dc_layer);
 
   // Underlays are less efficient, so attempt regular overlays first. We can
   // only check for occlusion within a render pass.
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index f29118d3..79acba9 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -952,6 +952,9 @@
       DCHECK(!disable_image_filtering);
     }
 
+    DBG_LOG("renderer.ptr", "renderer = %p%s", this,
+            renderer_.get() == software_renderer_ ? " (software)" : "");
+
     draw_timer.emplace();
     overlay_processor_->SetFrameSequenceNumber(frame_sequence_number_);
     overlay_processor_->SetIsPageFullscreen(frame.page_fullscreen_mode);
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 63e2bc32..3625794 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -22,6 +22,7 @@
 #include "base/task/bind_post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
+#include "base/trace_event/traced_value.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "cc/base/math_util.h"
@@ -468,6 +469,64 @@
 
 }  // namespace
 
+// A helper class to emit Viz debugger messages that has access to SkiaRenderer
+// internals.
+class SkiaRenderer::VizDebuggerLog {
+ public:
+  static void DebugLogDumpRenderPassBackings(
+      const base::flat_map<AggregatedRenderPassId, RenderPassBacking>&
+          render_pass_backings) {
+    bool enabled;
+    DBG_CONNECTED_OR_TRACING(enabled);
+    if (enabled) {
+      DBG_LOG("renderer.skia.render_pass_backings",
+              "render_pass_backings_ = [");
+      for (auto& kv : render_pass_backings) {
+        base::trace_event::TracedValueJSON value;
+        base::trace_event::TracedValue::Dictionary(
+            {
+                {"size", kv.second.size.ToString()},
+                {"generate_mipmap", kv.second.generate_mipmap},
+                {"color_space", kv.second.color_space.ToString()},
+                {"format", kv.second.format.ToString()},
+                {"mailbox", kv.second.mailbox.ToDebugString()},
+                {"is_root", kv.second.is_root},
+                {"is_scanout", kv.second.is_scanout},
+                {"scanout_dcomp_surface", kv.second.scanout_dcomp_surface},
+            })
+            .WriteToValue(&value);
+        DBG_LOG("renderer.skia.render_pass_backings", "%" PRIu64 ": %s",
+                kv.first.value(), value.ToFormattedJSON().c_str());
+      }
+      DBG_LOG("renderer.skia.render_pass_backings", "]");
+    }
+  }
+
+  static void DebugLogNewRenderPassBacking(
+      const AggregatedRenderPassId& render_pass_id,
+      const RenderPassRequirements& requirements) {
+    bool enabled;
+    DBG_CONNECTED_OR_TRACING(enabled);
+    if (enabled) {
+      base::trace_event::TracedValueJSON value;
+      base::trace_event::TracedValue::Dictionary(
+          {
+              {"size", requirements.size.ToString()},
+              {"generate_mipmap", requirements.generate_mipmap},
+              {"format", requirements.format.ToString()},
+              {"color_space", requirements.color_space.ToString()},
+              {"alpha_type", static_cast<int>(requirements.alpha_type)},
+              {"is_scanout", requirements.is_scanout},
+              {"scanout_dcomp_surface", requirements.scanout_dcomp_surface},
+          })
+          .WriteToValue(&value);
+      DBG_LOG("renderer.skia.render_pass_backings",
+              "allocate backing for render_pass %" PRIu64 ", %s",
+              render_pass_id.value(), value.ToFormattedJSON().c_str());
+    }
+  }
+};
+
 // chrome style prevents this from going in skia_renderer.h, but since it
 // uses std::optional, the style also requires it to have a declared ctor
 SkiaRenderer::BatchedQuadState::BatchedQuadState() = default;
@@ -1065,6 +1124,8 @@
 
   swap_buffer_rect_ = current_frame()->root_damage_rect;
 
+  VizDebuggerLog::DebugLogDumpRenderPassBackings(render_pass_backings_);
+
 #if BUILDFLAG(IS_OZONE)
   MaybeScheduleBackgroundImage(current_frame()->overlay_list);
 #endif  // BUILDFLAG(IS_OZONE)
@@ -3457,6 +3518,9 @@
     auto render_pass_it = render_passes_in_frame.find(backing_id);
     if (render_pass_it == render_passes_in_frame.end()) {
       passes_to_delete.push_back(backing_id);
+      DBG_LOG("renderer.skia.render_pass_backings",
+              "render_pass %" PRIu64 " is no longer in frame",
+              backing_id.value());
       continue;
     }
 
@@ -3482,6 +3546,15 @@
         !no_change_in_alpha_type || !no_change_in_color_space ||
         !scanout_appropriate) {
       passes_to_delete.push_back(backing_id);
+      DBG_LOG("renderer.skia.render_pass_backings",
+              "render_pass %" PRIu64
+              " allocation part not appropriate:%s%s%s%s%s%s",
+              backing_id.value(), !size_appropriate ? " size" : "",
+              !mipmap_appropriate ? " mipmap" : "",
+              !no_change_in_format ? " format" : "",
+              !no_change_in_alpha_type ? " alpha_type" : "",
+              !no_change_in_color_space ? " color_space" : "",
+              !scanout_appropriate ? " scanout" : "");
     }
   }
 
@@ -3572,6 +3645,9 @@
       requirements.format, requirements.size, requirements.color_space,
       requirements.alpha_type, usage, "RenderPassBacking",
       gpu::kNullSurfaceHandle);
+
+  VizDebuggerLog::DebugLogNewRenderPassBacking(render_pass_id, requirements);
+
   render_pass_backings_.emplace(
       render_pass_id,
       RenderPassBacking({requirements.size, requirements.generate_mipmap,
diff --git a/components/viz/service/display/skia_renderer.h b/components/viz/service/display/skia_renderer.h
index e630f0a0..8ecbdfc 100644
--- a/components/viz/service/display/skia_renderer.h
+++ b/components/viz/service/display/skia_renderer.h
@@ -123,6 +123,7 @@
   struct OverlayLock;
   class ScopedSkImageBuilder;
   class ScopedYUVSkImageBuilder;
+  class VizDebuggerLog;
 
   void ClearCanvas(SkColor4f color);
   void ClearFramebuffer();
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index 587b6e5..832edae 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -5,6 +5,7 @@
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
 
 #include <algorithm>
+#include <cmath>
 #include <limits>
 #include <optional>
 #include <utility>
@@ -21,6 +22,7 @@
 #include "base/time/default_tick_clock.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "base/tracing_buildflags.h"
 #include "build/build_config.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
@@ -157,6 +159,21 @@
   }
 }
 
+int AsPercent(float value) {
+  return base::saturated_cast<int>(std::nearbyint(value * 100.0f));
+}
+
+perfetto::Track FrameInUseTrack(const media::VideoFrameMetadata& metadata) {
+  return perfetto::Track(static_cast<uint64_t>(
+      (metadata.capture_begin_time.value() - base::TimeTicks())
+          .InMicroseconds()));
+}
+
+perfetto::Track CaptureTrack(const media::VideoFrameMetadata& metadata) {
+  return perfetto::Track(static_cast<uint64_t>(
+      (metadata.reference_time.value() - base::TimeTicks()).InMicroseconds()));
+}
+
 }  // namespace
 
 // static
@@ -791,19 +808,15 @@
   UMA_HISTOGRAM_BOOLEAN("Viz.FrameSinkVideoCapturer.FrameResurrected",
                         can_resurrect_content);
 
-  // Compute the current in-flight utilization and attenuate it: The utilization
-  // reported to the oracle is in terms of a maximum sustainable amount (not the
-  // absolute maximum).
-  const float utilization =
-      GetPipelineUtilization() / kTargetPipelineUtilization;
+  const float utilization = GetPipelineUtilization();
+  const int utilization_pct = AsPercent(utilization);
 
   // Do not proceed if the pool did not provide a frame: This indicates the
   // pipeline is full.
   if (!frame) {
     TRACE_EVENT_INSTANT("gpu.capture", "PipelineLimited", "trigger",
                         VideoCaptureOracle::EventAsString(event),
-                        "atten_util_percent",
-                        base::saturated_cast<int>(utilization * 100.0f + 0.5f));
+                        "utilization_pct", utilization_pct);
     oracle_->RecordWillNotCapture(utilization);
     if (next_capture_frame_number_ == 0) {
       // The pool was unable to provide a buffer for the very first capture, and
@@ -829,17 +842,16 @@
   if (utilization >= 1.0) {
     TRACE_EVENT_INSTANT("gpu.capture", "NearlyPipelineLimited", "trigger",
                         VideoCaptureOracle::EventAsString(event),
-                        "atten_util_percent",
-                        base::saturated_cast<int>(utilization * 100.0f + 0.5f));
+                        "utilization_pct", utilization_pct);
   }
 
   // At this point, the capture is going to proceed. Populate the VideoFrame's
   // metadata, and notify the oracle.
   const int64_t capture_frame_number = next_capture_frame_number_++;
+
   // !WARNING: now that the frame number has been incremented, returning without
   // adding the frame to the |delivery_queue_| or decrementing the frame number
   // will cause the queue to be permanently stuck.
-
   VideoFrameMetadata& metadata = frame->metadata();
   metadata.capture_begin_time = capture_begin_time;
   metadata.capture_counter = capture_frame_number;
@@ -859,6 +871,11 @@
   }
   metadata.top_controls_visible_height = last_top_controls_visible_height_;
 
+  // Record that the frame has been reserved for capture.
+  TRACE_EVENT_BEGIN("gpu.capture", "FrameInUse", FrameInUseTrack(metadata),
+                    "frame_number", capture_frame_number, "utilization_pct",
+                    utilization_pct);
+
   oracle_->RecordCapture(utilization);
 
   // `content_rect` is the region of the `frame` that we would like to populate.
@@ -883,9 +900,8 @@
   // Note: The following is used by
   // chrome/browser/media/cast_mirroring_performance_browsertest.cc, in
   // addition to the usual runtime tracing
-  TRACE_EVENT_BEGIN("gpu.capture", "Capture",
-                    perfetto::Track(oracle_frame_number), "frame_number",
-                    capture_frame_number, "trigger",
+  TRACE_EVENT_BEGIN("gpu.capture", "Capture", CaptureTrack(metadata),
+                    "frame_number", capture_frame_number, "trigger",
                     VideoCaptureOracle::EventAsString(event));
 
   // Determine what rectangular region has changed since the last captured
@@ -1110,11 +1126,11 @@
         "format=%s (%s) area:%s "
         "scale_from: %s "
         "scale_to: %s "
-        "frame pool utilization: %f",
+        "frame pool utilization: %d",
         format.c_str(), is_bitmap ? "bitmap" : "GPU memory buffer",
         request->area().ToString().c_str(),
         request->scale_from().ToString().c_str(),
-        request->scale_to().ToString().c_str(), utilization));
+        request->scale_to().ToString().c_str(), utilization_pct));
   }
 
   const SubtreeCaptureId subtree_id =
@@ -1360,8 +1376,8 @@
     // Note: The following is used by
     // chrome/browser/media/cast_mirroring_performance_browsertest.cc, in
     // addition to the usual runtime tracing
-    TRACE_EVENT_END("gpu.capture", perfetto::Track(oracle_frame_number));
-    TRACE_EVENT_INSTANT("gpu.capture", "CaptureEnd", "success", false);
+    TRACE_EVENT_END("gpu.capture", CaptureTrack(frame->metadata()), "success",
+                    false);
     MaybeScheduleRefreshFrame();
     return;
   }
@@ -1375,9 +1391,8 @@
   // Note: The following is used by
   // chrome/browser/media/cast_mirroring_performance_browsertest.cc, in
   // addition to the usual runtime tracing
-  TRACE_EVENT_END("gpu.capture", perfetto::Track(oracle_frame_number),
-                  "success", true, "time_delta",
-                  frame->timestamp().InMicroseconds());
+  TRACE_EVENT_END("gpu.capture", CaptureTrack(frame->metadata()), "success",
+                  true, "time_delta", frame->timestamp().InMicroseconds());
 
   // Clone a handle to the shared memory backing the populated video frame, to
   // send to the consumer.
@@ -1412,9 +1427,15 @@
       callbacks.InitWithNewPipeAndPassReceiver());
 
   num_frames_in_flight_++;
+
+#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
+  TRACE_COUNTER("gpu.capture", "NumFramesInFlight", num_frames_in_flight_);
+#else
+  // TODO(crbug/1006541): Delete when Perfetto is the default.
   TRACE_COUNTER_ID1("gpu.capture",
                     "FrameSinkVideoCapturerImpl::num_frames_in_flight_", this,
                     num_frames_in_flight_);
+#endif  // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 
   // Send the frame to the consumer.
   consumer_->OnFrameCaptured(std::move(handle), std::move(info), content_rect,
@@ -1480,18 +1501,17 @@
 void FrameSinkVideoCapturerImpl::NotifyFrameReleased(
     scoped_refptr<media::VideoFrame> frame) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
   num_frames_in_flight_--;
-
-  TRACE_COUNTER_ID1("gpu.capture",
-                    "FrameSinkVideoCapturerImpl::num_frames_in_flight_", this,
-                    num_frames_in_flight_);
+  const media::VideoFrameMetadata metadata = frame->metadata();
+  TRACE_EVENT_END("gpu.capture", FrameInUseTrack(metadata), "frame_number",
+                  metadata.capture_counter, "utilization_pct",
+                  AsPercent(GetPipelineUtilization()));
 }
 
 float FrameSinkVideoCapturerImpl::GetPipelineUtilization() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  return static_cast<float>(num_frames_in_flight_) / kDesignLimitMaxFrames;
+  return num_frames_in_flight_ /
+         (kDesignLimitMaxFrames * kTargetPipelineUtilization);
 }
 
 void FrameSinkVideoCapturerImpl::MaybeInformConsumerOfEmptyRegion() {
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
index 258e4275..8d819951 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
@@ -335,9 +335,13 @@
   // been released by it.
   void NotifyFrameReleased(scoped_refptr<media::VideoFrame> frame);
 
-  // Returns pipeline utilization. Pipeline utilization is different from pool
-  // utilization, since a marked frame may be returned multiple times w/o
-  // increasing pool utilization, but it would increase pipeline utilization.
+  // Returns pipeline utilization as a fraction of kTargetPipelineUtilization.
+  // May be more than 1.0 if the current pool utilization is greater than
+  // kTargetPipelineUtilization.
+  //
+  // Pipeline utilization is different from pool utilization, since a marked
+  // frame may be returned multiple times w/o increasing pool utilization, but
+  // it would increase pipeline utilization.
   float GetPipelineUtilization() const;
 
   // Informs the consumer that the frame was dropped due to being cropped
diff --git a/components/webapps/browser/android/app_banner_manager_android.cc b/components/webapps/browser/android/app_banner_manager_android.cc
index d019319..27364a6 100644
--- a/components/webapps/browser/android/app_banner_manager_android.cc
+++ b/components/webapps/browser/android/app_banner_manager_android.cc
@@ -560,6 +560,11 @@
                            GetCurrentTime())) {
     return false;
   }
+  // If the manifest_id isn't valid, then we don't have enough information to
+  // show any banner for this page yet.
+  if (!manifest_id_.is_valid()) {
+    return false;
+  }
   auto a2hs_params = AppBannerManagerAndroid::CreateAddToHomescreenParams(
       GetCurrentInstallBannerConfig(), native_java_app_data_, install_source);
   return PwaBottomSheetController::MaybeShow(
diff --git a/content/OWNERS b/content/OWNERS
index 9377789..a56cbbb 100644
--- a/content/OWNERS
+++ b/content/OWNERS
@@ -2,12 +2,9 @@
 # subdirectories that have specific owners who are the experts in reviewing that
 # code. Top level content/OWNERS are reviewers who are qualified to review
 # changes across all of content and are responsible for its architecture. They
-# will defer to subdirectory owners as needed. As such we expect that a
-# content/OWNER has made substantial contributions to content/ in the recent
-# past, specifically both in the browser and renderer subdirectories as they
-# encapsulate the main architecture, and demonstrated overall knowledge of the
-# content architecture by doing code reviews for changes that affect different
-# parts of content even before being a content/OWNER.
+# will defer to subdirectory owners as needed. For more information about owner
+# responsibilities as well as how to become a top-level owner, please see
+# //content/README.md#content-owners.
 #
 # For public questions directed to OWNERS, you can send email
 # to content-owners@chromium.org.
diff --git a/content/README.md b/content/README.md
index 5ff6fff..0675289 100644
--- a/content/README.md
+++ b/content/README.md
@@ -96,6 +96,80 @@
 embedders from content's inner workings, and makes it clear to people working on
 content which methods are used by embedders.
 
+## Content OWNERS
+Top-level `content` OWNERS are reviewers who are qualified to review changes
+across all of `content` and are responsible for its architecture. In general,
+`content` subdirectories will have specific owners who are the experts in
+reviewing that code, and top-level owners will defer to subdirectory owners as
+needed. For large architectural changes to `content`, all owners should loop in
+content-owners@chromium.org to give others a chance to post suggestions. This
+applies to changes large enough to warrant a design doc.
+
+To become a content/OWNER, candidates are expected to show substantial
+contributions to `content` in recent past that demonstrate knowledge of the core
+architecture and design principles, including both the browser process side and
+the renderer side.  To become a top-level owner, please follow the following
+process:
+
+1. Become an owner in a few `content` subdirectories and establish yourself as
+   an expert reviewer in those areas.
+
+2. Find 1-2 current top-level owners who can become your "sponsors" for an owner
+   nomination. Work with them to (1) review your technical changes in `content`
+   to gain trust in your technical work and (2) shadow-review `content` changes
+   that you also review to gain trust in you as a reviewer. Once ready, your
+   sponsors will nominate you for ownership by sending an email to
+   the current top-level owners.
+
+A typical nomination includes:
+- Projects that you worked on that involved `content`, and which concepts they
+  covered.
+- Some representative CLs contributed and/or reviewed. This can also include
+  aggregate statistics, e.g. via `git shortlog -s --author=<username>
+  content/browser`.
+- Significant improvements to documentation of the above concepts
+
+For reference, a top-level `content` OWNER is expected to be familiar with most
+(but not necessarily all) of the following core parts of `content`:
+- [Navigation](https://chromium.googlesource.com/chromium/src/+/main/docs/navigation.md)
+- [Process model](https://chromium.googlesource.com/chromium/src/+/main/docs/process_model_and_site_isolation.md)
+- [Session history](https://chromium.googlesource.com/chromium/src/+/main/docs/session_history.md)
+- Loading, interactions with the network stack
+- Manipulating pages, documents, frames, and frame trees.
+- [MPArch](https://chromium.googlesource.com/chromium/src/+/main/docs/frame_trees.md)
+  concepts like inner FrameTrees, primary vs non-primary pages.
+- How `content` interacts with compositing and input handling.
+- Mojo interfaces between `content/browser` and `content/renderer` and/or
+  `blink/renderer`.
+- Security checks (e.g., `ChildProcessSecurityPolicy`).
+- [content/public APIs](public/README.md): rules for adding them, common APIs
+  like `WebContentsObserver`, `ContentBrowserClient`, and `NavigationThrottle`.
+- Know that there are `content` embedders beyond //chrome (e.g., Android Webview).
+- DEPS rules, what should and should not depend on //content.
+
+Correspondingly, a top-level `content` OWNER is typically familiar with most of
+the following core `content` classes:
+- `Render(Frame|FrameProxy|Process|Widget|View)Host`
+- `Render(Frame|Widget|Thread)`
+- `WebContents` and `WebContentsObserver`
+- `FrameTree` and `FrameTreeNode`
+- `RenderFrameHostManager`
+- `NavigationHandle` and `NavigationRequest`, their ownership and lifetime
+- `Page` vs `RenderFrameHost` vs blink's `Document`,
+  `RenderDocumentHostUserData`/`NavigationHandleUserData`, and associated
+  lifetime issues.
+- `SiteInstance` and `BrowsingInstance`, `SiteInfo`
+- `NavigationController`, `NavigationEntry` vs `FrameNavigationEntry`
+- `ChildProcessSecurityPolicy`
+- `BrowserContext`, `StoragePartition`
+- `ContentBrowserClient`
+
 ## Further documentation
 
 * [Bluetooth](browser/bluetooth/README.md)
+* [content/browser/renderer_host](browser/renderer_host/README.md)
+* [Frame trees](https://chromium.googlesource.com/chromium/src/+/main/docs/frame_trees.md)
+* [Navigation](https://chromium.googlesource.com/chromium/src/+/main/docs/navigation.md)
+* [Process model](https://chromium.googlesource.com/chromium/src/+/main/docs/process_model_and_site_isolation.md)
+* [RenderDocument](https://chromium.googlesource.com/chromium/src/+/main/docs/render_document.md)
+* [Session history](https://chromium.googlesource.com/chromium/src/+/main/docs/session_history.md)
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 49dbd33..8c57b88 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -449,8 +449,9 @@
     const std::optional<testing::Matcher<std::string>>& id,
     const std::optional<testing::Matcher<std::string>>& name,
     const std::optional<testing::Matcher<std::string>>& src,
-    const std::vector<testing::Matcher<std::string>>& reasons,
+    const std::vector<BlockingDetailsReasonsMatcher>& reasons,
     const std::optional<SameOriginMatcher>& same_origin_details) {
+  // TODO(crbug.com/1523191) Make this matcher display human-friendly messages.
   return testing::Pointee(testing::AllOf(
       id.has_value()
           ? testing::Field(
@@ -492,6 +493,7 @@
 SameOriginMatcher BackForwardCacheBrowserTest::MatchesSameOriginDetails(
     const testing::Matcher<std::string>& url,
     const std::vector<ReasonsMatcher>& children) {
+  // TODO(crbug.com/1523191) Make this matcher display human-friendly messages.
   return testing::Pointee(testing::AllOf(
       testing::Field(
           "url", &blink::mojom::SameOriginBfcacheNotRestoredDetails::url, url),
@@ -501,11 +503,49 @@
           testing::ElementsAreArray(children))));
 }
 
+BlockingDetailsReasonsMatcher
+BackForwardCacheBrowserTest::MatchesDetailedReason(
+    const testing::Matcher<std::string>& name,
+    const std::optional<BlockingReasonLocationMatcher>& source) {
+  // TODO(crbug.com/1523191) Make this matcher display human-friendly
+  // messages.
+  return testing::Pointee(testing::AllOf(
+      testing::Field("name", &blink::mojom::BFCacheBlockingDetailedReason::name,
+                     name),
+      testing::Field(
+          "source", &blink::mojom::BFCacheBlockingDetailedReason::source,
+          source.has_value()
+              ? source.value()
+              : testing::Property(
+                    "is_null",
+                    &blink::mojom::BlockingReasonSourceLocationPtr::is_null,
+                    true))));
+}
+
+BlockingReasonLocationMatcher
+BackForwardCacheBrowserTest::MatchesSourceLocation(
+    const testing::Matcher<std::string>& url,
+    const testing::Matcher<uint64_t>& line_number,
+    const testing::Matcher<uint64_t>& column_number) {
+  // TODO(crbug.com/1523191) Make this matcher display human-friendly
+  // messages.
+  return testing::Pointee(testing::AllOf(
+      testing::Field("url", &blink::mojom::BlockingReasonSourceLocation::url,
+                     url),
+      testing::Field("line_number",
+                     &blink::mojom::BlockingReasonSourceLocation::line_number,
+                     line_number),
+      testing::Field("column_number",
+                     &blink::mojom::BlockingReasonSourceLocation::column_number,
+                     column_number)));
+}
+
 BlockingDetailsMatcher BackForwardCacheBrowserTest::MatchesBlockingDetails(
     const std::optional<testing::Matcher<std::string>>& url,
     const std::optional<testing::Matcher<std::string>>& function_name,
     const testing::Matcher<uint64_t>& line_number,
     const testing::Matcher<uint64_t>& column_number) {
+  // TODO(crbug.com/1523191) Make this matcher display human-friendly messages.
   return testing::Pointee(testing::AllOf(
       url.has_value()
           ? testing::Field("url", &blink::mojom::BlockingDetails::url,
diff --git a/content/browser/back_forward_cache_browsertest.h b/content/browser/back_forward_cache_browsertest.h
index 4688e85..09928a5 100644
--- a/content/browser/back_forward_cache_browsertest.h
+++ b/content/browser/back_forward_cache_browsertest.h
@@ -36,6 +36,10 @@
     const blink::mojom::BackForwardCacheNotRestoredReasonsPtr&>;
 using SameOriginMatcher = testing::Matcher<
     const blink::mojom::SameOriginBfcacheNotRestoredDetailsPtr&>;
+using BlockingDetailsReasonsMatcher =
+    testing::Matcher<const blink::mojom::BFCacheBlockingDetailedReasonPtr&>;
+using BlockingReasonLocationMatcher =
+    testing::Matcher<const blink::mojom::BlockingReasonSourceLocationPtr&>;
 using BlockingDetailsMatcher =
     testing::Matcher<const blink::mojom::BlockingDetailsPtr&>;
 
@@ -143,12 +147,26 @@
       const std::optional<testing::Matcher<std::string>>& id,
       const std::optional<testing::Matcher<std::string>>& name,
       const std::optional<testing::Matcher<std::string>>& src,
-      const std::vector<testing::Matcher<std::string>>& reasons,
+      const std::vector<BlockingDetailsReasonsMatcher>& reasons,
       const std::optional<SameOriginMatcher>& same_origin_details);
+
   SameOriginMatcher MatchesSameOriginDetails(
       const testing::Matcher<std::string>& url,
       const std::vector<ReasonsMatcher>& children);
 
+  // Used in tests that ensure source location is sent to the renderer side from
+  // the browser one
+  BlockingDetailsReasonsMatcher MatchesDetailedReason(
+      const testing::Matcher<std::string>& name,
+      const std::optional<BlockingReasonLocationMatcher>& source);
+
+  BlockingReasonLocationMatcher MatchesSourceLocation(
+      const testing::Matcher<std::string>& url,
+      const testing::Matcher<uint64_t>& line_number,
+      const testing::Matcher<uint64_t>& column_number);
+
+  // Used in tests that ensure source location is sent to the browser side from
+  // the renderer one.
   BlockingDetailsMatcher MatchesBlockingDetails(
       const std::optional<testing::Matcher<std::string>>& url,
       const std::optional<testing::Matcher<std::string>>& function_name,
diff --git a/content/browser/back_forward_cache_no_store_browsertest.cc b/content/browser/back_forward_cache_no_store_browsertest.cc
index 58cca5e..1cf689a1 100644
--- a/content/browser/back_forward_cache_no_store_browsertest.cc
+++ b/content/browser/back_forward_cache_no_store_browsertest.cc
@@ -959,8 +959,11 @@
   auto subframe_result = MatchesNotRestoredReasons(
       /*id=*/"", /*name=*/"", /*src=*/url_a_no_store.spec(),
       /*reasons=*/
-      {"JsNetworkRequestReceivedCacheControlNoStoreResource",
-       "MainResourceHasCacheControlNoStore"},
+      {MatchesDetailedReason(
+           "JsNetworkRequestReceivedCacheControlNoStoreResource",
+           /*source=*/std::nullopt),
+       MatchesDetailedReason("MainResourceHasCacheControlNoStore",
+                             /*source=*/std::nullopt)},
       MatchesSameOriginDetails(
           /*url=*/url_a_no_store.spec(),
           /*children=*/{}));
@@ -968,7 +971,9 @@
       current_frame_host()->NotRestoredReasonsForTesting(),
       MatchesNotRestoredReasons(
           /*id=*/std::nullopt, /*name=*/std::nullopt, /*src=*/std::nullopt,
-          /*reasons=*/{"MainResourceHasCacheControlNoStore"},
+          /*reasons=*/
+          {MatchesDetailedReason("MainResourceHasCacheControlNoStore",
+                                 /*source=*/std::nullopt)},
           MatchesSameOriginDetails(
               /*url=*/url_a_no_store.spec(),
               /*children=*/
diff --git a/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc b/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
index c09ca5c..8da89b5b2 100644
--- a/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
+++ b/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/back_forward_cache_browsertest.h"
 
+#include <optional>
+
 #include "content/browser/back_forward_cache_test_util.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -74,7 +76,8 @@
   // Expect that NotRestoredReasons are reported.
   auto rfh_a_result = MatchesNotRestoredReasons(
       /*id=*/std::nullopt,
-      /*name=*/std::nullopt, /*src=*/std::nullopt, /*reasons=*/{"Dummy"},
+      /*name=*/std::nullopt, /*src=*/std::nullopt, /*reasons=*/
+      {MatchesDetailedReason("Dummy", /*source=*/std::nullopt)},
       MatchesSameOriginDetails(
           /*url=*/rfh_a_url, /*children=*/{}));
   EXPECT_THAT(current_frame_host()->NotRestoredReasonsForTesting(),
@@ -134,7 +137,9 @@
   // Note that |rfh_a_3| is masked because it's a child of |rfh_b|.
   auto rfh_b_result = MatchesNotRestoredReasons(
       /*id=*/"rfh_b_id", /*name=*/"rfh_b_name",
-      /*src=*/rfh_b_url, /*reasons=*/{"masked"}, std::nullopt);
+      /*src=*/rfh_b_url, /*reasons=*/
+      {MatchesDetailedReason("masked", /*source=*/std::nullopt)},
+      /*same_origin_details=*/std::nullopt);
 
   auto rfh_a_2_result = MatchesNotRestoredReasons(
       /*id=*/"rfh_a_2_id", /*name=*/"rfh_a_2_name",
@@ -192,18 +197,22 @@
   // of null.
   EXPECT_EQ(true, EvalJs(current_frame_host(),
                          "document.getElementById('child-0').name == ''"));
-  auto rfh_a_2_result = MatchesNotRestoredReasons(/*id=*/"child-0", /*name=*/"",
-                                                  /*src=*/rfh_a_2_url,
-                                                  /*reasons=*/{"Dummy"},
-                                                  MatchesSameOriginDetails(
-                                                      /*url=*/rfh_a_2_url,
-                                                      /*children=*/{}));
-  auto rfh_a_4_result = MatchesNotRestoredReasons(/*id=*/"child-0", /*name=*/"",
-                                                  /*src=*/rfh_a_4_url,
-                                                  /*reasons=*/{"Dummy"},
-                                                  MatchesSameOriginDetails(
-                                                      /*url=*/rfh_a_4_url,
-                                                      /*children=*/{}));
+  auto rfh_a_2_result = MatchesNotRestoredReasons(
+      /*id=*/"child-0", /*name=*/"",
+      /*src=*/rfh_a_2_url,
+      /*reasons=*/
+      {MatchesDetailedReason("Dummy", /*source=*/std::nullopt)},
+      MatchesSameOriginDetails(
+          /*url=*/rfh_a_2_url,
+          /*children=*/{}));
+  auto rfh_a_4_result = MatchesNotRestoredReasons(
+      /*id=*/"child-0", /*name=*/"",
+      /*src=*/rfh_a_4_url,
+      /*reasons=*/
+      {MatchesDetailedReason("Dummy", /*source=*/std::nullopt)},
+      MatchesSameOriginDetails(
+          /*url=*/rfh_a_4_url,
+          /*children=*/{}));
   EXPECT_EQ(true, EvalJs(current_frame_host(),
                          "document.getElementById('child-1').name == ''"));
   auto rfh_a_3_result = MatchesNotRestoredReasons(
@@ -217,7 +226,8 @@
   auto rfh_a_1_result = MatchesNotRestoredReasons(
       /*id=*/std::nullopt,
       /*name=*/std::nullopt, /*src=*/std::nullopt,
-      /*reasons=*/{"Dummy"},
+      /*reasons=*/
+      {MatchesDetailedReason("Dummy", /*source=*/std::nullopt)},
       MatchesSameOriginDetails(
           /*url=*/rfh_a_1_url,
           /*children=*/{rfh_a_2_result, rfh_a_3_result}));
@@ -322,7 +332,10 @@
   auto rfh_a_result = MatchesNotRestoredReasons(
       /*id=*/std::nullopt,
       /*name=*/std::nullopt, /*src=*/std::nullopt,
-      /*reasons=*/{"Related active contents", "internal-error"},
+      /*reasons=*/
+      {MatchesDetailedReason("Related active contents",
+                             /*source=*/std::nullopt),
+       MatchesDetailedReason("internal-error", /*source=*/std::nullopt)},
       MatchesSameOriginDetails(
           /*url=*/rfh_a_url,
           /*children=*/{}));
@@ -370,13 +383,14 @@
   auto reasons =
       navigation_request->commit_params().not_restored_reasons.Clone();
   // The reasons have not been reset yet.
-  auto rfh_a_result =
-      MatchesNotRestoredReasons(/*id=*/std::nullopt, /*name=*/std::nullopt,
-                                /*src=*/std::nullopt,
-                                /*reasons=*/{"JavaScript execution"},
-                                MatchesSameOriginDetails(
-                                    /*url=*/url_a_redirect.spec(),
-                                    /*children=*/{}));
+  auto rfh_a_result = MatchesNotRestoredReasons(
+      /*id=*/std::nullopt, /*name=*/std::nullopt,
+      /*src=*/std::nullopt,
+      /*reasons=*/
+      {MatchesDetailedReason("JavaScript execution", /*source=*/std::nullopt)},
+      MatchesSameOriginDetails(
+          /*url=*/url_a_redirect.spec(),
+          /*children=*/{}));
 
   EXPECT_THAT(reasons, rfh_a_result);
 
@@ -427,14 +441,16 @@
   // Blocking reasons should be recorded.
   ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
                     {kBlockingReasonEnum}, {}, {}, {}, FROM_HERE);
-  // Expect that NotRestoredReasons are reported.
-  auto rfh_a_result =
-      MatchesNotRestoredReasons(/*id=*/std::nullopt, /*name=*/std::nullopt,
-                                /*src=*/std::nullopt,
-                                /*reasons=*/{kBlockingReasonString},
-                                MatchesSameOriginDetails(
-                                    /*url=*/url_a.spec(),
-                                    /*children=*/{}));
+  // Expect that NotRestoredReasons and the blocking feature's source location
+  // are reported.
+  auto rfh_a_result = MatchesNotRestoredReasons(
+      /*id=*/std::nullopt, /*name=*/std::nullopt,
+      /*src=*/std::nullopt,
+      /*reasons=*/
+      {MatchesDetailedReason(kBlockingReasonString, /*source=*/std::nullopt)},
+      MatchesSameOriginDetails(
+          /*url=*/url_a.spec(),
+          /*children=*/{}));
   EXPECT_THAT(current_frame_host()->NotRestoredReasonsForTesting(),
               rfh_a_result);
 
@@ -503,7 +519,8 @@
       /*same_origin_details=*/std::nullopt);
   auto rfh_a_result = MatchesNotRestoredReasons(
       /*id=*/std::nullopt,
-      /*name=*/std::nullopt, /*src=*/std::nullopt, /*reasons=*/{"masked"},
+      /*name=*/std::nullopt, /*src=*/std::nullopt, /*reasons=*/
+      {MatchesDetailedReason("masked", /*source=*/std::nullopt)},
       MatchesSameOriginDetails(
           /*url=*/rfh_a_url,
           /*children=*/{rfh_a_1_result, rfh_a_2_result}));
diff --git a/content/browser/renderer_host/back_forward_cache_impl.cc b/content/browser/renderer_host/back_forward_cache_impl.cc
index 9499be2f..a1a576d 100644
--- a/content/browser/renderer_host/back_forward_cache_impl.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl.cc
@@ -51,6 +51,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
+#include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom.h"
 #include "third_party/blink/public/mojom/frame/sudden_termination_disabler_type.mojom-shared.h"
 #if BUILDFLAG(IS_ANDROID)
 #include "content/public/browser/android/child_process_importance.h"
@@ -1786,18 +1787,31 @@
         blink::mojom::SameOriginBfcacheNotRestoredDetails::New();
     not_restored_reasons->same_origin_details->url = url_.spec();
     // Populate the reasons for same-origin frames.
-    not_restored_reasons->reasons = GetDocumentResult().GetStringReasons();
+    for (auto& name : GetDocumentResult().GetStringReasons()) {
+      blink::mojom::BFCacheBlockingDetailedReasonPtr reason =
+          blink::mojom::BFCacheBlockingDetailedReason::New();
+      reason->name = name;
+      not_restored_reasons->reasons.push_back(std::move(reason));
+    }
     if (is_root_outermost_main_frame_) {
       int index_copy = exposed_cross_origin_iframe_index;
+      bool no_masked_reason =
+          std::find_if(
+              not_restored_reasons->reasons.begin(),
+              not_restored_reasons->reasons.end(),
+              [](const blink::mojom::BFCacheBlockingDetailedReasonPtr& reason) {
+                return reason->name == "masked";
+              }) == not_restored_reasons->reasons.end();
       if (HasUnexposedCrossOriginBlockingIframe(index_copy) &&
-          std::find(not_restored_reasons->reasons.begin(),
-                    not_restored_reasons->reasons.end(),
-                    "masked") == not_restored_reasons->reasons.end()) {
+          no_masked_reason) {
         // If any cross-origin iframe is blocking and does not have "masked" in
         // its own reasons, we need to add "masked" to the outermost main
         // frame's reasons. Note that we need to add "masked" only when the
         // reasons do not have it yet.
-        not_restored_reasons->reasons.push_back("masked");
+        blink::mojom::BFCacheBlockingDetailedReasonPtr masked_reason =
+            blink::mojom::BFCacheBlockingDetailedReason::New();
+        masked_reason->name = "masked";
+        not_restored_reasons->reasons.push_back(std::move(masked_reason));
       }
     }
     for (const auto& subtree : GetChildren()) {
@@ -1815,7 +1829,10 @@
       // Note that we need to flatten the tree in order to check the eligibility
       // of the cross-origin subtree. Add "masked" to this frame to signal that
       // this is the blocking frame.
-      not_restored_reasons->reasons.push_back("masked");
+      blink::mojom::BFCacheBlockingDetailedReasonPtr masked_reason =
+          blink::mojom::BFCacheBlockingDetailedReason::New();
+      masked_reason->name = "masked";
+      not_restored_reasons->reasons.push_back(std::move(masked_reason));
     }
     // Decrease the index now that we saw a cross-origin iframe.
     exposed_cross_origin_iframe_index--;
diff --git a/content/browser/renderer_host/back_forward_cache_impl_unittest.cc b/content/browser/renderer_host/back_forward_cache_impl_unittest.cc
index fc913c43..44fa1e1 100644
--- a/content/browser/renderer_host/back_forward_cache_impl_unittest.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl_unittest.cc
@@ -82,7 +82,8 @@
   auto result = tree_root->GetWebExposedNotRestoredReasonsInternal(index);
   // Main frame has "masked" as a reason.
   EXPECT_EQ(static_cast<int>(result->reasons.size()), 1);
-  EXPECT_EQ(result->reasons[0], "masked");
+  EXPECT_EQ(result->reasons[0]->name, "masked");
+  EXPECT_FALSE(result->reasons[0]->source);
 
   // b-1 is masked.
   EXPECT_TRUE(result->same_origin_details->children[0]->reasons.empty());
@@ -115,7 +116,8 @@
   auto result = tree_root->GetWebExposedNotRestoredReasonsInternal(index);
   // Main frame has "masked" as a reason.
   EXPECT_EQ(static_cast<int>(result->reasons.size()), 1);
-  EXPECT_EQ(result->reasons[0], "masked");
+  EXPECT_EQ(result->reasons[0]->name, "masked");
+  EXPECT_FALSE(result->reasons[0]->source);
   // b-1 is unmasked, but reasons are empty because it does not have any
   // blocking reasons.
   EXPECT_TRUE(result->same_origin_details->children[0]->reasons.empty());
@@ -134,7 +136,8 @@
   auto result = tree_root->GetWebExposedNotRestoredReasonsInternal(index);
   // Main frame has "masked" as a reason.
   EXPECT_EQ(static_cast<int>(result->reasons.size()), 1);
-  EXPECT_EQ(result->reasons[0], "masked");
+  EXPECT_EQ(result->reasons[0]->name, "masked");
+  EXPECT_FALSE(result->reasons[0]->source);
 
   // b-1 is masked.
   EXPECT_TRUE(result->same_origin_details->children[0]->reasons.empty());
@@ -143,10 +146,11 @@
                                  ->same_origin_details->children[0]
                                  ->reasons.size()),
             1);
-  EXPECT_EQ(result->same_origin_details->children[1]
-                ->same_origin_details->children[0]
-                ->reasons[0],
-            "masked");
+  auto& reason = result->same_origin_details->children[1]
+                     ->same_origin_details->children[0]
+                     ->reasons[0];
+  EXPECT_EQ(reason->name, "masked");
+  EXPECT_FALSE(result->reasons[0]->source);
   // b-4 is masked.
   EXPECT_TRUE(result->same_origin_details->children[2]->reasons.empty());
 }
diff --git a/content/common/service_worker/race_network_request_url_loader_client.cc b/content/common/service_worker/race_network_request_url_loader_client.cc
index 1d7d2e0..35efb9bd 100644
--- a/content/common/service_worker/race_network_request_url_loader_client.cc
+++ b/content/common/service_worker/race_network_request_url_loader_client.cc
@@ -18,6 +18,7 @@
 #include "mojo/public/c/system/data_pipe.h"
 #include "net/http/http_status_code.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/header_util.h"
 #include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
 
@@ -256,14 +257,14 @@
     case FetchResponseFrom::kSubresourceLoaderIsHandlingRedirect:
       // If the fetch handler result is a fallback, commit the
       // RaceNetworkRequest response. If the result is not a fallback and the
-      // response is not 200, use the other response from the fetch handler
-      // instead because it may have a response from the cache.
+      // response is not ok status, use the other response from the fetch
+      // handler instead because it may have a response from the cache.
       // TODO(crbug.com/1420517): More comprehensive error handling may be
       // needed, especially the case when HTTP cache hit or redirect happened.
       //
       // When the AutoPreload is enabled, RaceNetworkRequest works just for the
       // dedupe purpose. The fetch handler should always commit the response.
-      if (head_->headers->response_code() != net::HttpStatusCode::HTTP_OK) {
+      if (!network::IsSuccessfulStatus(head_->headers->response_code())) {
         owner_->SetCommitResponsibility(FetchResponseFrom::kServiceWorker);
       } else {
         owner_->SetCommitResponsibility(
@@ -658,8 +659,14 @@
   uint32_t buffer_num_bytes = 0;
   base::span<const char> read_buffer;
   MojoResult result = body_->BeginReadData(&buffer, &buffer_num_bytes,
-                                           MOJO_READ_DATA_FLAG_NONE);
+                                           MOJO_BEGIN_READ_DATA_FLAG_NONE);
   if (result == MOJO_RESULT_OK) {
+    SCOPED_CRASH_KEY_NUMBER("SWRace", "num_bytes_read_buffer",
+                            buffer_num_bytes);
+    volatile const char* buffer_v = static_cast<volatile const char*>(buffer);
+    for (size_t i = 0; i < buffer_num_bytes; ++i) {
+      buffer_v[i];
+    }
     read_buffer =
         base::make_span(static_cast<const char*>(buffer), buffer_num_bytes);
   }
diff --git a/content/renderer/media/renderer_web_media_player_delegate.cc b/content/renderer/media/renderer_web_media_player_delegate.cc
index 7339726..19dcac3 100644
--- a/content/renderer/media/renderer_web_media_player_delegate.cc
+++ b/content/renderer/media/renderer_web_media_player_delegate.cc
@@ -258,7 +258,7 @@
   if (!pending_update_task_) {
     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, base::BindOnce(&RendererWebMediaPlayerDelegate::UpdateTask,
-                                  AsWeakPtr()));
+                                  weak_ptr_factory_.GetWeakPtr()));
     pending_update_task_ = true;
   }
 }
diff --git a/content/renderer/media/renderer_web_media_player_delegate.h b/content/renderer/media/renderer_web_media_player_delegate.h
index 2d5b403..b9e86944 100644
--- a/content/renderer/media/renderer_web_media_player_delegate.h
+++ b/content/renderer/media/renderer_web_media_player_delegate.h
@@ -32,11 +32,10 @@
 
 // Standard implementation of WebMediaPlayerDelegate; communicates state to
 // the MediaPlayerDelegateHost.
-class CONTENT_EXPORT RendererWebMediaPlayerDelegate
+class CONTENT_EXPORT RendererWebMediaPlayerDelegate final
     : public content::RenderFrameObserver,
       public blink::WebViewObserver,
-      public blink::WebMediaPlayerDelegate,
-      public base::SupportsWeakPtr<RendererWebMediaPlayerDelegate> {
+      public blink::WebMediaPlayerDelegate {
  public:
   explicit RendererWebMediaPlayerDelegate(content::RenderFrame* render_frame);
 
@@ -152,6 +151,8 @@
   // Last page shown/hidden state sent to the player.  Unset if we have not sent
   // any message.  Used to elide duplicates.
   std::optional<bool> is_shown_;
+
+  base::WeakPtrFactory<RendererWebMediaPlayerDelegate> weak_ptr_factory_{this};
 };
 
 }  // namespace media
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 390db90..d882dd9 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -126,6 +126,10 @@
   # workaround. See crbug.com/1079244.
   _first_run_test: Optional[str] = None
 
+  # Keeps track of whether this is the first browser start on a shard for a
+  # flakiness workaround. See crbug.com/323927831.
+  _is_first_browser_start = True
+
   tab: Optional[ct.Tab] = None
 
   def __init__(self, *args, **kwargs):
@@ -540,15 +544,11 @@
     if os_name == 'android':
       cls.browser.platform.android_action_runner.TurnScreenOn()
 
+  # pylint: disable=no-self-use
   def _ShouldForceRetryOnFailureFirstTest(self) -> bool:
-    # TODO(crbug.com/323927831): Remove this once the flaky crashes on certain
-    # Mac machines go away.
-    try:
-      retry_on_ventura = 'ventura' in self.GetPlatformTags(self.browser)
-    except Exception:  # pylint: disable=broad-except
-      logging.warning('Failed to determine if running on Ventura, assuming no')
-      retry_on_ventura = False
-    return retry_on_ventura
+    return False
+
+  # pylint: enable=no-self-use
 
   def _DetermineFirstTestRetryWorkaround(self, test_name: str) -> bool:
     """Potentially allows retries for the first test run on a shard.
@@ -574,6 +574,21 @@
     return False
 
   # pylint: disable=no-self-use
+  def _DetermineFirstBrowserStartWorkaround(self) -> bool:
+    """Potentially allows retries for the first browser start on a shard.
+
+    This is a temporary workaround for crbug.com/323927831 and should be
+    removed once the root cause is fixed.
+    """
+    # The browser is assumed to be dead at this point, so we can't rely on
+    # GetPlatformTags() to restrict this to the flaking Mac configs.
+    if not GpuIntegrationTest._is_first_browser_start:
+      return False
+    return sys.platform == 'darwin'
+
+  # pylint: enable=no-self-use
+
+  # pylint: disable=no-self-use
   def _DetermineRetryWorkaround(self, exception: Exception) -> bool:
     """Potentially allows retries depending on the exception type.
 
@@ -1013,7 +1028,17 @@
     return cls._original_finder_options
 
   def setUp(self) -> None:
-    self._EnsureTabIsAvailable()
+    # TODO(crbug.com/323927831): Remove this try/except logic once the root
+    # cause of flakes on Macs is resolved.
+    try:
+      self._EnsureTabIsAvailable()
+    except Exception:  # pylint: disable=broad-except
+      if self._DetermineFirstBrowserStartWorkaround():
+        self._EnsureTabIsAvailable()
+      else:
+        raise
+    finally:
+      GpuIntegrationTest._is_first_browser_start = False
 
     # Append overlay support for extra Intel GPUs when the extra device id is
     # current active GPU's device id
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index c232cdb..f59da7b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -317,6 +317,9 @@
 # Causes the render process to hang
 crbug.com/1280692 [ fuchsia web-engine-shell ] conformance/context/context-creation-and-destruction.html [ Skip ]
 
+# Crashes on fuchsia-fyi-sherlock-qemu
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 passthrough ] WebglExtension_EXT_texture_mirror_clamp_to_edge [ Skip ]
+
 # Linux Intel
 # Can occasionally kill machines. Appears to be tied to kernel version, so can
 # be removed once upgraded to kernel 5.4.0-77 or later.
@@ -456,6 +459,30 @@
 # Anti-aliasing disabled on Fuchsia
 [ fuchsia ] conformance/context/context-attributes-alpha-depth-stencil-antialias.html [ Failure ]
 
+# Failures on fuchsia-fyi-sherlock-qemu
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/extensions/oes-texture-float-linear.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/extensions/oes-texture-float-with-canvas.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/glsl/samplers/glsl-function-texture2dproj.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/glsl/samplers/glsl-function-texture2dprojlod.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/tex-video-using-tex-unit-non-zero.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texparameter-test.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texture-npot.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/gl-teximage.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texture-active-bind-2.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texture-active-bind.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/canvas/tex-2d-alpha-alpha-unsigned_byte.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/canvas-teximage-after-multiple-drawimages.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/canvas/tex-2d-luminance-luminance-unsigned_byte.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texture-srgb-upload.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/canvas/tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/rendering/line-loop-tri-fan.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/rendering/line-rendering-quality.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/canvas/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/extensions/webgl-polygon-mode.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/rendering/more-than-65536-indices.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/canvas/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ Failure ]
+crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 ] conformance/textures/misc/texture-video-transparent.html [ Failure ]
+
 ####################
 # Win failures     #
 ####################
diff --git a/docs/website b/docs/website
index ac76949..7854b9d 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit ac769493150a27e6a4dc32da747c9613496a9e87
+Subproject commit 7854b9d90b9fb1e57c50e6f039bc44fa587a9eca
diff --git a/gpu/command_buffer/service/BUILD.gn b/gpu/command_buffer/service/BUILD.gn
index b6afb48..f5362e9 100644
--- a/gpu/command_buffer/service/BUILD.gn
+++ b/gpu/command_buffer/service/BUILD.gn
@@ -316,9 +316,8 @@
 
   if (!is_android) {
     sources += [
+      "abstract_texture.cc",
       "abstract_texture.h",
-      "passthrough_abstract_texture_impl.cc",
-      "passthrough_abstract_texture_impl.h",
     ]
   } else {
     sources += [
diff --git a/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc b/gpu/command_buffer/service/abstract_texture.cc
similarity index 74%
rename from gpu/command_buffer/service/passthrough_abstract_texture_impl.cc
rename to gpu/command_buffer/service/abstract_texture.cc
index 65806587..f25b3bb 100644
--- a/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc
+++ b/gpu/command_buffer/service/abstract_texture.cc
@@ -8,23 +8,23 @@
 #include "gpu/command_buffer/service/abstract_texture.h"
 #include "gpu/command_buffer/service/context_group.h"
 #include "gpu/command_buffer/service/error_state.h"
-#include "gpu/command_buffer/service/passthrough_abstract_texture_impl.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/scoped_binders.h"
 #include "ui/gl/scoped_make_current.h"
+#include "gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h"
 
 namespace gpu {
 namespace gles2 {
 
-PassthroughAbstractTextureImpl::PassthroughAbstractTextureImpl(
+AbstractTexture::AbstractTexture(
     scoped_refptr<TexturePassthrough> texture_passthrough,
     GLES2DecoderPassthroughImpl* decoder)
     : texture_passthrough_(std::move(texture_passthrough)),
       gl_api_(decoder->api()),
       decoder_(decoder) {}
 
-PassthroughAbstractTextureImpl::~PassthroughAbstractTextureImpl() {
+AbstractTexture::~AbstractTexture() {
   if (cleanup_cb_) {
     DCHECK(texture_passthrough_);
     std::move(cleanup_cb_).Run(this);
@@ -35,11 +35,11 @@
   DCHECK(!texture_passthrough_);
 }
 
-TextureBase* PassthroughAbstractTextureImpl::GetTextureBase() const {
+TextureBase* AbstractTexture::GetTextureBase() const {
   return texture_passthrough_.get();
 }
 
-void PassthroughAbstractTextureImpl::SetParameteri(GLenum pname, GLint param) {
+void AbstractTexture::SetParameteri(GLenum pname, GLint param) {
   if (!texture_passthrough_)
     return;
 
@@ -47,20 +47,20 @@
   gl_api_->glTexParameteriFn(texture_passthrough_->target(), pname, param);
 }
 
-void PassthroughAbstractTextureImpl::SetCleared() {
+void AbstractTexture::SetCleared() {
   // The passthrough decoder has no notion of 'cleared', so do nothing.
 }
 
-void PassthroughAbstractTextureImpl::SetCleanupCallback(CleanupCallback cb) {
+void AbstractTexture::SetCleanupCallback(CleanupCallback cb) {
   cleanup_cb_ = std::move(cb);
 }
 
-void PassthroughAbstractTextureImpl::NotifyOnContextLost() {
+void AbstractTexture::NotifyOnContextLost() {
   NOTIMPLEMENTED();
 }
 
 scoped_refptr<TexturePassthrough>
-PassthroughAbstractTextureImpl::OnDecoderWillDestroy() {
+AbstractTexture::OnDecoderWillDestroy() {
   // Make sure that destruction_cb_ does nothing when destroyed, since
   // the DecoderContext is invalid. Also null out invalid pointer.
   DCHECK(texture_passthrough_);
diff --git a/gpu/command_buffer/service/abstract_texture.h b/gpu/command_buffer/service/abstract_texture.h
index 0888b488..39c07a8 100644
--- a/gpu/command_buffer/service/abstract_texture.h
+++ b/gpu/command_buffer/service/abstract_texture.h
@@ -6,8 +6,11 @@
 #define GPU_COMMAND_BUFFER_SERVICE_ABSTRACT_TEXTURE_H_
 
 #include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/service/texture_base.h"
+#include "gpu/command_buffer/service/texture_manager.h"
 #include "gpu/gpu_gles2_export.h"
 
 // Forwardly declare a few GL types to avoid including GL header files.
@@ -20,6 +23,8 @@
 
 namespace gles2 {
 
+class GLES2DecoderPassthroughImpl;
+
 // An AbstractTexture enables access to GL textures from the GPU process, for
 // things that set up textures using some client's decoder.  Creating an
 // AbstractTexture is similar to "glGenTexture", and deleting it is similar to
@@ -41,21 +46,24 @@
  public:
   using CleanupCallback = base::OnceCallback<void(AbstractTexture*)>;
 
+  AbstractTexture(scoped_refptr<TexturePassthrough> texture_passthrough,
+                  GLES2DecoderPassthroughImpl* decoder);
+
   // The texture is guaranteed to be around while |this| exists, as long as
   // the decoder isn't destroyed / context isn't lost.
-  virtual ~AbstractTexture() = default;
+  ~AbstractTexture();
 
   // Return our TextureBase, useful mostly for creating a mailbox.  This may
   // return null if the texture has been destroyed.
-  virtual TextureBase* GetTextureBase() const = 0;
+  TextureBase* GetTextureBase() const;
 
   // Set a texture parameter.  The GL context must be current.
-  virtual void SetParameteri(GLenum pname, GLint param) = 0;
+  void SetParameteri(GLenum pname, GLint param);
 
   // Marks the texture as cleared, to help prevent sending an uninitialized
   // texture to the (untrusted) renderer.  One should call this only when one
   // has actually initialized the texture.
-  virtual void SetCleared() = 0;
+  void SetCleared();
 
   // Set a callback that will be called when the AbstractTexture is going to
   // drop its reference to the underlying TextureBase.  We can't guarantee that
@@ -64,12 +72,21 @@
   // AbstractTexture is destroyed, or when our stub is destroyed.  Do not change
   // the current context during this callback.  Also, do not assume that one
   // has a current context.
-  virtual void SetCleanupCallback(CleanupCallback cleanup_callback) = 0;
+  void SetCleanupCallback(CleanupCallback cleanup_callback);
 
   // Used to notify the AbstractTexture if the context is lost.
-  virtual void NotifyOnContextLost() = 0;
+  void NotifyOnContextLost();
 
   unsigned int service_id() const { return GetTextureBase()->service_id(); }
+
+  // Called when our decoder is going away, so that we can try to clean up.
+  scoped_refptr<TexturePassthrough> OnDecoderWillDestroy();
+
+ private:
+  scoped_refptr<TexturePassthrough> texture_passthrough_;
+  raw_ptr<gl::GLApi> gl_api_;
+  raw_ptr<GLES2DecoderPassthroughImpl> decoder_;
+  CleanupCallback cleanup_cb_;
 };
 
 }  // namespace gles2
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index dc6a6f1..23d249bd 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -14,6 +14,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
+#include "gpu/command_buffer/service/abstract_texture.h"
 #include "gpu/command_buffer/service/command_buffer_service.h"
 #include "gpu/command_buffer/service/decoder_client.h"
 #include "gpu/command_buffer/service/feature_info.h"
@@ -1104,7 +1105,7 @@
 
 #if !BUILDFLAG(IS_ANDROID)
   if (resources_) {  // Initialize may not have been called yet.
-    for (PassthroughAbstractTextureImpl* iter : abstract_textures_) {
+    for (AbstractTexture* iter : abstract_textures_) {
       resources_->textures_pending_destruction.insert(
           iter->OnDecoderWillDestroy());
     }
@@ -1648,15 +1649,15 @@
       new TexturePassthrough(service_id, target));
 
   // Unretained is safe, because of the destruction cb.
-  std::unique_ptr<PassthroughAbstractTextureImpl> abstract_texture =
-      std::make_unique<PassthroughAbstractTextureImpl>(texture, this);
+  std::unique_ptr<AbstractTexture> abstract_texture =
+      std::make_unique<AbstractTexture>(texture, this);
 
   abstract_textures_.insert(abstract_texture.get());
   return abstract_texture;
 }
 
 void GLES2DecoderPassthroughImpl::OnAbstractTextureDestroyed(
-    PassthroughAbstractTextureImpl* abstract_texture,
+    AbstractTexture* abstract_texture,
     scoped_refptr<TexturePassthrough> texture) {
   DCHECK(texture);
   abstract_textures_.erase(abstract_texture);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
index 51a3214..ecf5445 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
@@ -38,10 +38,6 @@
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/gpu_switching_observer.h"
 
-#if !BUILDFLAG(IS_ANDROID)
-#include "gpu/command_buffer/service/passthrough_abstract_texture_impl.h"
-#endif
-
 namespace gl {
 class GLFence;
 class ProgressReporter;
@@ -54,7 +50,7 @@
 
 class ContextGroup;
 class GPUTracer;
-class PassthroughAbstractTextureImpl;
+class AbstractTexture;
 class MultiDrawManager;
 class GLES2DecoderPassthroughImpl;
 class GLES2ExternalFramebuffer;
@@ -404,7 +400,7 @@
       CopyTexImageResourceManager* copy_tex_image_blit) override;
 
 #if !BUILDFLAG(IS_ANDROID)
-  void OnAbstractTextureDestroyed(PassthroughAbstractTextureImpl*,
+  void OnAbstractTextureDestroyed(AbstractTexture*,
                                   scoped_refptr<TexturePassthrough>);
 #endif
 
@@ -524,9 +520,9 @@
   bool OnlyHasPendingProgramCompletionQueries();
 
 #if !BUILDFLAG(IS_ANDROID)
-  // A set of raw pointers to currently living PassthroughAbstractTextures
+  // A set of raw pointers to currently living AbstractTextures
   // which allow us to properly signal to them when we are destroyed.
-  base::flat_set<PassthroughAbstractTextureImpl*> abstract_textures_;
+  base::flat_set<AbstractTexture*> abstract_textures_;
 #endif
 
   int commands_to_process_;
diff --git a/gpu/command_buffer/service/passthrough_abstract_texture_impl.h b/gpu/command_buffer/service/passthrough_abstract_texture_impl.h
deleted file mode 100644
index 4fe2777..0000000
--- a/gpu/command_buffer/service/passthrough_abstract_texture_impl.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef GPU_COMMAND_BUFFER_SERVICE_PASSTHROUGH_ABSTRACT_TEXTURE_IMPL_H_
-#define GPU_COMMAND_BUFFER_SERVICE_PASSTHROUGH_ABSTRACT_TEXTURE_IMPL_H_
-
-#include "base/memory/raw_ptr.h"
-#include "build/build_config.h"
-#include "gpu/command_buffer/service/abstract_texture.h"
-#include "gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h"
-
-#include "base/functional/callback.h"
-#include "base/memory/scoped_refptr.h"
-#include "gpu/command_buffer/service/texture_manager.h"
-
-namespace gpu {
-namespace gles2 {
-
-class TexturePassthrough;
-class GLES2DecoderPassthroughImpl;
-
-// Implementation of AbstractTexture used by the passthrough command decoder.
-class GPU_GLES2_EXPORT PassthroughAbstractTextureImpl : public AbstractTexture {
- public:
-  PassthroughAbstractTextureImpl(
-      scoped_refptr<TexturePassthrough> texture_passthrough,
-      GLES2DecoderPassthroughImpl* decoder);
-  ~PassthroughAbstractTextureImpl() override;
-
-  // AbstractTexture
-  TextureBase* GetTextureBase() const override;
-  void SetParameteri(GLenum pname, GLint param) override;
-
-  void SetCleared() override;
-  void SetCleanupCallback(CleanupCallback cb) override;
-  void NotifyOnContextLost() override;
-
-  // Called when our decoder is going away, so that we can try to clean up.
-  scoped_refptr<TexturePassthrough> OnDecoderWillDestroy();
-
- private:
-  scoped_refptr<TexturePassthrough> texture_passthrough_;
-  raw_ptr<gl::GLApi> gl_api_;
-  raw_ptr<GLES2DecoderPassthroughImpl> decoder_;
-  CleanupCallback cleanup_cb_;
-};
-
-}  // namespace gles2
-}  // namespace gpu
-
-#endif  // GPU_COMMAND_BUFFER_SERVICE_PASSTHROUGH_ABSTRACT_TEXTURE_IMPL_H_
diff --git a/gpu/command_buffer/service/raster_decoder_unittest_base.cc b/gpu/command_buffer/service/raster_decoder_unittest_base.cc
index 6723ae82..de4ed2a 100644
--- a/gpu/command_buffer/service/raster_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/raster_decoder_unittest_base.cc
@@ -111,6 +111,9 @@
   // in turn initialize FeatureInfo, which needs a context to determine
   // extension support.
   context_ = new StrictMock<GLContextMock>();
+  // The stub ctx needs to be initialized so that the gl::GLContext can
+  // store the offscreen stub |surface|.
+  context_->Initialize(surface_.get(), {});
   context_->SetExtensionsString(all_extensions.c_str());
   context_->SetGLVersionString(init.gl_version.c_str());
 
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index d8b78ad49..a03fd2f4 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -220,7 +220,6 @@
       share_group_(std::move(share_group)),
       context_(context),
       real_context_(std::move(context)),
-      surface_(std::move(surface)),
       sk_surface_cache_(MaxNumSkSurface()) {
   if (gr_context_type_ == GrContextType::kVulkan) {
     if (vk_context_provider_) {
@@ -231,6 +230,11 @@
     }
   }
 
+  DCHECK(context_ && surface && context_->default_surface());
+  // |this| no longer stores the |surface| as that one must be stored by the
+  // context. Do a sanity check that it is true.
+  DCHECK(context_->default_surface() == surface);
+
   if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
     base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
         this, "SharedContextState",
@@ -274,15 +278,7 @@
   // current, or the GrContext was already abandoned if the GLContext was lost.
   owned_gr_context_.reset();
 
-  // |surface_| needs to be destroyed while there is a current GL context if
-  // using GL, as some implementations make calls to the GL bindings. Any such
-  // implementations will themselves ensure that the context is current in their
-  // destructor (we cannot blindly make the context current here, as there are
-  // other implementations that crash if the context is made current at this
-  // point :\). However, we drop our reference to |surface_| before releasing
-  // the context below so that the release has the intended effect.
   last_current_surface_ = nullptr;
-  surface_.reset();
 
   if (context_->IsCurrent(nullptr))
     context_->ReleaseCurrent(nullptr);
@@ -567,7 +563,7 @@
     auto virtual_context = base::MakeRefCounted<GLContextVirtual>(
         share_group_.get(), real_context_.get(),
         weak_ptr_factory_.GetWeakPtr());
-    if (!virtual_context->Initialize(surface_.get(), gl::GLContextAttribs())) {
+    if (!virtual_context->Initialize(surface(), gl::GLContextAttribs())) {
       LOG(ERROR) << "SharedContextState::InitializeGL failure Initialize "
                     "virtual context failed";
       feature_info_ = nullptr;
@@ -646,8 +642,9 @@
 
   const bool using_gl = GrContextIsGL() || needs_gl;
   if (using_gl) {
-    gl::GLSurface* dont_care_surface =
-        last_current_surface_ ? last_current_surface_.get() : surface_.get();
+    gl::GLSurface* dont_care_surface = last_current_surface_
+                                           ? last_current_surface_.get()
+                                           : context_->default_surface();
     surface = surface ? surface : dont_care_surface;
 
     if (!context_->MakeCurrent(surface)) {
@@ -831,8 +828,14 @@
   }
 }
 
+gl::GLSurface* SharedContextState::surface() const {
+  return context_->default_surface();
+}
+
 gl::GLDisplay* SharedContextState::display() {
-  return surface_.get()->GetGLDisplay();
+  auto* gl_surface = surface();
+  DCHECK(gl_surface);
+  return gl_surface->GetGLDisplay();
 }
 
 bool SharedContextState::initialized() const {
diff --git a/gpu/command_buffer/service/shared_context_state.h b/gpu/command_buffer/service/shared_context_state.h
index e57deab..a885986 100644
--- a/gpu/command_buffer/service/shared_context_state.h
+++ b/gpu/command_buffer/service/shared_context_state.h
@@ -146,7 +146,7 @@
   gl::GLShareGroup* share_group() const { return share_group_.get(); }
   gl::GLContext* context() const { return context_.get(); }
   gl::GLContext* real_context() const { return real_context_.get(); }
-  gl::GLSurface* surface() const { return surface_.get(); }
+  gl::GLSurface* surface() const;
   gl::GLDisplay* display();  // non const since it calls GLSurface::GetGLDisplay
   viz::VulkanContextProvider* vk_context_provider() const {
     return vk_context_provider_;
@@ -378,7 +378,6 @@
   scoped_refptr<gl::GLShareGroup> share_group_;
   scoped_refptr<gl::GLContext> context_;
   scoped_refptr<gl::GLContext> real_context_;
-  scoped_refptr<gl::GLSurface> surface_;
 
   // Most recent surface that this ShareContextState was made current with.
   // Avoids a call to MakeCurrent with a different surface, if we don't
diff --git a/gpu/command_buffer/service/shared_context_state_unittest.cc b/gpu/command_buffer/service/shared_context_state_unittest.cc
index e3782e34..dd4bd29 100644
--- a/gpu/command_buffer/service/shared_context_state_unittest.cc
+++ b/gpu/command_buffer/service/shared_context_state_unittest.cc
@@ -58,6 +58,9 @@
     context->SetGLVersionString(gl_version);
     const char gl_extensions[] = "GL_KHR_robustness";
     context->SetExtensionsString(gl_extensions);
+    // The stub ctx needs to be initialized so that the gl::GLContext can
+    // store the offscreen stub |surface|.
+    context->Initialize(surface.get(), {});
 
     context->MakeCurrent(surface.get());
 
diff --git a/infra/config/generated/builders/try/linux_chromium_asan_siso_rel_ng-compilator/properties.json b/infra/config/generated/builders/try/linux_chromium_asan_siso_rel_ng-compilator/properties.json
index 640ab37..16d87763 100644
--- a/infra/config/generated/builders/try/linux_chromium_asan_siso_rel_ng-compilator/properties.json
+++ b/infra/config/generated/builders/try/linux_chromium_asan_siso_rel_ng-compilator/properties.json
@@ -92,7 +92,8 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/try/linux_chromium_compile_siso_dbg_ng/properties.json b/infra/config/generated/builders/try/linux_chromium_compile_siso_dbg_ng/properties.json
index 9fe19e5d..c972589a 100644
--- a/infra/config/generated/builders/try/linux_chromium_compile_siso_dbg_ng/properties.json
+++ b/infra/config/generated/builders/try/linux_chromium_compile_siso_dbg_ng/properties.json
@@ -90,7 +90,8 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/builders/try/linux_chromium_tsan_siso_rel_ng-compilator/properties.json b/infra/config/generated/builders/try/linux_chromium_tsan_siso_rel_ng-compilator/properties.json
index b527551..95ad3eb 100644
--- a/infra/config/generated/builders/try/linux_chromium_tsan_siso_rel_ng-compilator/properties.json
+++ b/infra/config/generated/builders/try/linux_chromium_tsan_siso_rel_ng-compilator/properties.json
@@ -90,7 +90,8 @@
   },
   "$build/siso": {
     "configs": [
-      "builder"
+      "builder",
+      "remote-library-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index fcb45b7d..b2b0664f 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -602,7 +602,8 @@
         '  },'
         '  "$build/siso": {'
         '    "configs": ['
-        '      "builder"'
+        '      "builder",'
+        '      "remote-library-link"'
         '    ],'
         '    "enable_cloud_profiler": true,'
         '    "enable_cloud_trace": true,'
@@ -1119,7 +1120,9 @@
         '    "scandeps_server": true'
         '  },'
         '  "$build/siso": {'
-        '    "configs": [],'
+        '    "configs": ['
+        '      "remote-library-link"'
+        '    ],'
         '    "enable_cloud_profiler": true,'
         '    "enable_cloud_trace": true,'
         '    "experiments": [],'
@@ -1379,7 +1382,8 @@
         '  },'
         '  "$build/siso": {'
         '    "configs": ['
-        '      "builder"'
+        '      "builder",'
+        '      "remote-library-link"'
         '    ],'
         '    "enable_cloud_profiler": true,'
         '    "enable_cloud_trace": true,'
@@ -74932,7 +74936,8 @@
         '  },'
         '  "$build/siso": {'
         '    "configs": ['
-        '      "builder"'
+        '      "builder",'
+        '      "remote-library-link"'
         '    ],'
         '    "enable_cloud_profiler": true,'
         '    "enable_cloud_trace": true,'
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index fab1872..f4ef9ab9 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -307,32 +307,32 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6287.0',
+    'description': 'Run with ash-chrome version 123.0.6288.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6287.0',
-          'revision': 'version:123.0.6287.0',
+          'location': 'lacros_version_skew_tests_v123.0.6288.0',
+          'revision': 'version:123.0.6288.0',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 122.0.6241.0',
+    'description': 'Run with ash-chrome version 123.0.6276.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v122.0.6241.0',
-          'revision': 'version:122.0.6241.0',
+          'location': 'lacros_version_skew_tests_v123.0.6276.0',
+          'revision': 'version:123.0.6276.0',
         },
       ],
     },
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index f48c8bd..cb9f7df 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -71,13 +71,14 @@
 
 def cq_build_perf_builder(description_html, **kwargs):
     # Use CQ reclient instance and high reclient jobs/cores to simulate CQ builds.
+    if not kwargs.get("siso_configs"):
+        kwargs["siso_configs"] = ["builder"]
     return ci.builder(
         description_html = description_html + "<br>Build stats is show in http://shortn/_gaAdI3x6o6.",
         reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
         reclient_instance = reclient.instance.DEFAULT_UNTRUSTED,
         siso_project = siso.project.DEFAULT_UNTRUSTED,
         use_clang_coverage = True,
-        siso_configs = ["builder"],
         **kwargs
     )
 
@@ -213,6 +214,7 @@
         category = "linux",
         short_name = "siso",
     ),
+    siso_configs = ["builder", "remote-library-link"],
 )
 
 cq_build_perf_builder(
@@ -337,6 +339,7 @@
         category = "cros",
         short_name = "siso",
     ),
+    siso_configs = ["builder", "remote-library-link"],
 )
 
 cq_build_perf_builder(
@@ -486,6 +489,7 @@
         short_name = "dev",
     ),
     reclient_jobs = 5120,
+    siso_configs = ["remote-library-link"],
 )
 
 developer_build_perf_builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 239f461..c317c4b 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -108,6 +108,7 @@
             ],
         },
     },
+    siso_configs = ["builder", "remote-library-link"],
     siso_enabled = True,
     tryjob = try_.job(
         experiment_percentage = 10,
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index 9b10010..35bbfd7 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -610,6 +610,7 @@
 try_.compilator_builder(
     name = "linux_chromium_asan_siso_rel_ng-compilator",
     main_list_view = "try",
+    siso_configs = ["builder", "remote-library-link"],
     siso_enabled = True,
 )
 
@@ -747,6 +748,7 @@
     contact_team_email = "chrome-build-team@google.com",
     main_list_view = "try",
     reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
+    siso_configs = ["builder", "remote-library-link"],
     siso_enabled = True,
     tryjob = try_.job(
         experiment_percentage = 10,
@@ -880,6 +882,7 @@
 try_.compilator_builder(
     name = "linux_chromium_tsan_siso_rel_ng-compilator",
     main_list_view = "try",
+    siso_configs = ["builder", "remote-library-link"],
     siso_enabled = True,
 )
 
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index be0093d..9dcd8d4 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,32 +1,32 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 123.0.6287.0",
+    "description": "Run with ash-chrome version 123.0.6288.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v123.0.6287.0",
-          "revision": "version:123.0.6287.0"
+          "location": "lacros_version_skew_tests_v123.0.6288.0",
+          "revision": "version:123.0.6288.0"
         }
       ]
     }
   },
   "LACROS_VERSION_SKEW_DEV": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 122.0.6241.0",
+    "description": "Run with ash-chrome version 123.0.6276.0",
     "identifier": "Lacros version skew testing ash dev",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v122.0.6241.0",
-          "revision": "version:122.0.6241.0"
+          "location": "lacros_version_skew_tests_v123.0.6276.0",
+          "revision": "version:123.0.6276.0"
         }
       ]
     }
diff --git a/internal b/internal
index c0441ac..7dad41b4 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit c0441ac66681bb025fe45ca06acbd21a72b8f39e
+Subproject commit 7dad41b491ac6ef10b238af85d0cfe5a91124393
diff --git a/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.mm b/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.mm
index cf06815f..c035f03 100644
--- a/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.mm
+++ b/ios/chrome/browser/docking_promo/ui/docking_promo_view_controller.mm
@@ -48,7 +48,7 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
   self.view.accessibilityIdentifier = kDockingPromoAccessibilityId;
-  self.view.backgroundColor = [UIColor colorNamed:kGrey100Color];
+  self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor];
   if (self.animationViewWrapper) {
     [self configureAndLayoutAnimationView];
   }
@@ -56,6 +56,22 @@
   [self layoutAlertScreen];
 }
 
+// Called when the device is rotated or dark mode is enabled/disabled. (Un)Hide
+// the animations accordingly.
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+  BOOL darkModeEnabled =
+      (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);
+  BOOL hidden = ![self shouldShowAnimation];
+
+  self.animationViewWrapper.animationView.hidden = hidden || darkModeEnabled;
+  self.animationViewWrapperDarkMode.animationView.hidden =
+      hidden || !darkModeEnabled;
+
+  [self updateAnimationsPlaying];
+  [self updateAlertScreenTopAnchorConstraint];
+}
+
 #pragma mark - DockingPromoConsumer
 
 - (void)setTitleString:(NSString*)titleString
@@ -122,8 +138,7 @@
   ];
 
   UIView* instructionView =
-      [[InstructionView alloc] initWithList:dockingPromoSteps
-                                      style:InstructionViewStyleBold];
+      [[InstructionView alloc] initWithList:dockingPromoSteps];
 
   instructionView.translatesAutoresizingMaskIntoConstraints = NO;
 
diff --git a/ios/chrome/browser/shared/ui/elements/instruction_view.h b/ios/chrome/browser/shared/ui/elements/instruction_view.h
index 21dc73f..a11d842 100644
--- a/ios/chrome/browser/shared/ui/elements/instruction_view.h
+++ b/ios/chrome/browser/shared/ui/elements/instruction_view.h
@@ -10,8 +10,7 @@
 // Enum defining different styles for the instruction view.
 typedef NS_ENUM(NSInteger, InstructionViewStyle) {
   InstructionViewStyleDefault = 0,
-  InstructionViewStyleGrayscale,
-  InstructionViewStyleBold,
+  InstructionViewStyleGrayscale
 };
 
 // View containing an instruction list with their step number.
diff --git a/ios/chrome/browser/shared/ui/elements/instruction_view.mm b/ios/chrome/browser/shared/ui/elements/instruction_view.mm
index 9180fa0..e48a3ada 100644
--- a/ios/chrome/browser/shared/ui/elements/instruction_view.mm
+++ b/ios/chrome/browser/shared/ui/elements/instruction_view.mm
@@ -93,7 +93,6 @@
         self.backgroundColor =
             [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
         break;
-      case InstructionViewStyleBold:
       case InstructionViewStyleDefault:
         self.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
         break;
@@ -284,11 +283,6 @@
 // initialization and when entering or exiting dark mode.
 - (void)updateColorForStepNumberLabel:(UILabel*)stepNumberLabel {
   switch (self.style) {
-    case InstructionViewStyleBold:
-      stepNumberLabel.textColor = [UIColor colorNamed:kBlue600Color];
-      stepNumberLabel.layer.backgroundColor =
-          [UIColor colorNamed:kBlueHaloColor].CGColor;
-      break;
     case InstructionViewStyleGrayscale:
       stepNumberLabel.textColor = [UIColor colorNamed:kGrey600Color];
       stepNumberLabel.layer.backgroundColor =
diff --git a/media/capture/content/video_capture_oracle.cc b/media/capture/content/video_capture_oracle.cc
index 1be76d3..5f5ed1a7 100644
--- a/media/capture/content/video_capture_oracle.cc
+++ b/media/capture/content/video_capture_oracle.cc
@@ -230,8 +230,8 @@
   return true;
 }
 
-void VideoCaptureOracle::RecordCapture(double pool_utilization) {
-  DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0);
+void VideoCaptureOracle::RecordCapture(float pool_utilization) {
+  DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0f);
 
   smoothing_sampler_.RecordSample();
   const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_);
@@ -246,12 +246,12 @@
   next_frame_number_++;
 }
 
-void VideoCaptureOracle::RecordWillNotCapture(double pool_utilization) {
+void VideoCaptureOracle::RecordWillNotCapture(float pool_utilization) {
   VLOG(1) << "Client rejects proposal to capture frame (at #"
           << next_frame_number_ << ").";
 
   if (capture_size_throttling_mode_ == kThrottlingActive) {
-    DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0);
+    DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0f);
     const base::TimeTicks timestamp = GetFrameTimestamp(next_frame_number_);
     buffer_pool_utilization_.Update(pool_utilization, timestamp);
     AnalyzeAndAdjust(timestamp);
diff --git a/media/capture/content/video_capture_oracle.h b/media/capture/content/video_capture_oracle.h
index ecd42fc1..593a56b 100644
--- a/media/capture/content/video_capture_oracle.h
+++ b/media/capture/content/video_capture_oracle.h
@@ -92,8 +92,8 @@
   // the current buffer pool utilization relative to a sustainable maximum (not
   // the absolute maximum).  This method should only be called if the last call
   // to ObserveEventAndDecideCapture() returned true.
-  void RecordCapture(double pool_utilization);
-  void RecordWillNotCapture(double pool_utilization);
+  void RecordCapture(float pool_utilization);
+  void RecordWillNotCapture(float pool_utilization);
 
   // Notify of the completion of a capture, and whether it was successful.
   // Returns true iff the captured frame should be delivered.  |frame_timestamp|
diff --git a/net/third_party/quiche/src b/net/third_party/quiche/src
index 9c463fd..bccc7cc 160000
--- a/net/third_party/quiche/src
+++ b/net/third_party/quiche/src
@@ -1 +1 @@
-Subproject commit 9c463fdd34e5975521f6211c5609d1f3945f3c15
+Subproject commit bccc7cc648236d93d8cd4f07c82d7a15a6218bf6
diff --git a/services/webnn/coreml/context_impl.mm b/services/webnn/coreml/context_impl.mm
index 9c7b945..889f3a0b 100644
--- a/services/webnn/coreml/context_impl.mm
+++ b/services/webnn/coreml/context_impl.mm
@@ -2,11 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <CoreML/CoreML.h>
-
 #include "services/webnn/coreml/context_impl.h"
 
-#import "base/strings/sys_string_conversions.h"
+#import <CoreML/CoreML.h>
+
 #include "services/webnn/coreml/graph_impl.h"
 #include "services/webnn/webnn_context_provider_impl.h"
 
diff --git a/services/webnn/coreml/graph_impl.mm b/services/webnn/coreml/graph_impl.mm
index f89a52c0..82ad582 100644
--- a/services/webnn/coreml/graph_impl.mm
+++ b/services/webnn/coreml/graph_impl.mm
@@ -26,7 +26,7 @@
 
 @implementation WebNNMLFeatureProvider
 - (MLFeatureValue*)featureValueForName:(NSString*)featureName {
-  return [_featureValues objectForKey:featureName];
+  return _featureValues[featureName];
 }
 - (instancetype)initWithFeatures:(NSSet<NSString*>*)feature_names
                    featureValues:(NSDictionary*)feature_values {
@@ -138,8 +138,7 @@
 
   // TODO(https://crbug.com/1522278): Add metrics to measure compilation time.
   [MLModel
-      compileModelAtURL:[NSURL URLWithString:base::apple::FilePathToNSString(
-                                                 model_file_path)]
+      compileModelAtURL:base::apple::FilePathToNSURL(model_file_path)
       completionHandler:^(NSURL* compiled_model_url, NSError* error) {
         // compiled_model_url refers to a directory placed directly inside
         // NSTemporaryDirectory(), it is not inside model_file_dir.
@@ -262,11 +261,11 @@
   }
   uint32_t current_stride = expected_size.ValueOrDie();
   for (uint32_t dimension : operand_info->dimensions) {
-    [shape addObject:[NSNumber numberWithUnsignedInt:dimension]];
+    [shape addObject:@(dimension)];
     // since expected_size was computed by multiplying all dimensions together
     // current_stride has to be perfectly divisible by dimension.
     current_stride = current_stride / dimension;
-    [stride addObject:[NSNumber numberWithUnsignedInt:current_stride]];
+    [stride addObject:@(current_stride)];
   }
   return GraphImpl::CoreMLFeatureInfo(data_type, shape, stride);
 }
@@ -300,7 +299,7 @@
           mojom::Error::Code::kUnknownError, "Input initialization error")));
       return;
     }
-    [feature_values setObject:feature_value forKey:feature_name];
+    feature_values[feature_name] = feature_value;
   }
 
   // Run the MLModel
diff --git a/services/webnn/dml/adapter.cc b/services/webnn/dml/adapter.cc
index a9af33a..c489bd8 100644
--- a/services/webnn/dml/adapter.cc
+++ b/services/webnn/dml/adapter.cc
@@ -115,10 +115,26 @@
   hr = dml_create_device_proc(d3d12_device.Get(), flags,
                               IID_PPV_ARGS(&dml_device));
   if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to create DirectML device: " +
-                       logging::SystemErrorCodeToString(hr);
-    return base::unexpected(CreateError(mojom::Error::Code::kUnknownError,
-                                        "Failed to create DirectML device."));
+    if (hr == DXGI_ERROR_SDK_COMPONENT_MISSING) {
+      // DirectML debug layer can fail to load even when it has been installed
+      // on the system. Try again without the debug flag and see if we're
+      // successful.
+      flags = flags & ~DML_CREATE_DEVICE_FLAG_DEBUG;
+      hr = dml_create_device_proc(d3d12_device.Get(), flags,
+                                  IID_PPV_ARGS(&dml_device));
+      if (FAILED(hr)) {
+        DLOG(ERROR) << "Failed to create DirectML device without debug flag: " +
+                           logging::SystemErrorCodeToString(hr);
+        return base::unexpected(
+            CreateError(mojom::Error::Code::kUnknownError,
+                        "Failed to create DirectML device."));
+      }
+    } else {
+      DLOG(ERROR) << "Failed to create DirectML device: " +
+                         logging::SystemErrorCodeToString(hr);
+      return base::unexpected(CreateError(mojom::Error::Code::kUnknownError,
+                                          "Failed to create DirectML device."));
+    }
   };
 
   const DML_FEATURE_LEVEL max_feature_level_supported =
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 72eefddf..b34e3b66 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5311,9 +5311,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5323,8 +5323,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -5341,9 +5341,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5353,8 +5353,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -5467,9 +5467,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5479,8 +5479,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -5497,9 +5497,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5509,8 +5509,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index e9b6218f..40b112c 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20464,9 +20464,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20476,8 +20476,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -20493,9 +20493,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20505,8 +20505,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -20614,9 +20614,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20626,8 +20626,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -20643,9 +20643,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20655,8 +20655,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 3e99092..75956db 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41458,9 +41458,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41469,8 +41469,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -41487,9 +41487,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41498,8 +41498,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -41608,9 +41608,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41619,8 +41619,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -41637,9 +41637,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41648,8 +41648,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -42940,9 +42940,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42951,8 +42951,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -42969,9 +42969,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42980,8 +42980,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -43090,9 +43090,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43101,8 +43101,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -43119,9 +43119,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43130,8 +43130,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 521bf51..f1ee887 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16507,12 +16507,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16522,8 +16522,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -16540,12 +16540,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16555,8 +16555,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
@@ -16683,12 +16683,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6287.0",
+        "description": "Run with ash-chrome version 123.0.6288.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16698,8 +16698,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6287.0",
-              "revision": "version:123.0.6287.0"
+              "location": "lacros_version_skew_tests_v123.0.6288.0",
+              "revision": "version:123.0.6288.0"
             }
           ],
           "dimensions": {
@@ -16716,12 +16716,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 122.0.6241.0",
+        "description": "Run with ash-chrome version 123.0.6276.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16731,8 +16731,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6241.0",
-              "revision": "version:122.0.6241.0"
+              "location": "lacros_version_skew_tests_v123.0.6276.0",
+              "revision": "version:123.0.6276.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index c4fdd272..f4e3f30 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -21,7 +21,7 @@
 # "ios_chrome_ui_eg2tests_module_iPhone 6s 14.0"
 
 # The goal is to drive the number of exceptions to zero, to make all
-# the bots behave similarly.
+# the bots behave similarly. whitespace
 {
   'accessibility_unittests': {
     'modifications': {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index fab1872..f4ef9ab9 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -307,32 +307,32 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6287.0',
+    'description': 'Run with ash-chrome version 123.0.6288.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6287.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6288.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6287.0',
-          'revision': 'version:123.0.6287.0',
+          'location': 'lacros_version_skew_tests_v123.0.6288.0',
+          'revision': 'version:123.0.6288.0',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 122.0.6241.0',
+    'description': 'Run with ash-chrome version 123.0.6276.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6241.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6276.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v122.0.6241.0',
-          'revision': 'version:122.0.6241.0',
+          'location': 'lacros_version_skew_tests_v123.0.6276.0',
+          'revision': 'version:123.0.6276.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1198221..7083b5b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2394,8 +2394,10 @@
             "platforms": [
                 "android",
                 "android_webview",
+                "chromeos",
                 "chromeos_lacros",
                 "fuchsia",
+                "ios",
                 "linux",
                 "mac",
                 "windows"
@@ -3654,6 +3656,21 @@
             ]
         }
     ],
+    "ChromeOSDocumentScanAsyncDiscovery": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AsynchronousScannerDiscovery"
+                    ]
+                }
+            ]
+        }
+    ],
     "ChromeOSGlanceablesTimeManagementTasksView": [
         {
             "platforms": [
@@ -4781,6 +4798,24 @@
             ]
         }
     ],
+    "CrOSFederatedAutocorrectPhh": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "launch_stage": "prod"
+                    },
+                    "enable_features": [
+                        "AutocorrectFederatedPhh"
+                    ]
+                }
+            ]
+        }
+    ],
     "CrOSFederatedLauncherQueryPhhVersion1": [
         {
             "platforms": [
@@ -4799,6 +4834,24 @@
             ]
         }
     ],
+    "CrOSFederatedLauncherQueryPhhVersion2": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "launch_stage": "prod"
+                    },
+                    "enable_features": [
+                        "FederatedLauncherQueryAnalyticsVersion2Task"
+                    ]
+                }
+            ]
+        }
+    ],
     "CrOSFederatedTimezoneCodePhh": [
         {
             "platforms": [
@@ -18239,6 +18292,38 @@
             ],
             "experiments": [
                 {
+                    "name": "UploadCadence20ReadCadence5",
+                    "params": {
+                        "external_metrics_collection_interval_in_seconds": "300",
+                        "upload_time_in_seconds": "1200"
+                    },
+                    "enable_features": [
+                        "EnableStructuredMetrics",
+                        "EnableStructuredMetricsService"
+                    ]
+                },
+                {
+                    "name": "UploadCadence10ReadCadence3",
+                    "params": {
+                        "external_metrics_collection_interval_in_seconds": "180",
+                        "upload_time_in_seconds": "600"
+                    },
+                    "enable_features": [
+                        "EnableStructuredMetrics",
+                        "EnableStructuredMetricsService"
+                    ]
+                },
+                {
+                    "name": "UploadCadence20",
+                    "params": {
+                        "upload_time_in_seconds": "1200"
+                    },
+                    "enable_features": [
+                        "EnableStructuredMetrics",
+                        "EnableStructuredMetricsService"
+                    ]
+                },
+                {
                     "name": "StructuredMetricsExternalMetrics_100_20231027",
                     "params": {
                         "file_limit": "100"
diff --git a/third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom b/third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom
index 5e510ec4..330f32be 100644
--- a/third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom
+++ b/third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom
@@ -12,11 +12,37 @@
 struct SameOriginBfcacheNotRestoredDetails {
   // URL when the frame navigated away. This could be cut down if the
   // reported value was longer than a limit.
+  // TODO(crbug.com/324031239): Use url.mojom.Url (in url/mojom/url.mojom)
+  // instead of string.
   string url;
   // List of children frames' information.
   array<BackForwardCacheNotRestoredReasons> children;
 };
 
+// JavaScript source location of a BFCache blocking reason.
+struct BlockingReasonSourceLocation {
+  // The absolute URL (e.g. https://...) of the file where the reason was found.
+  // This should be empty string if the reason doesn't have URL.
+  // TODO(crbug.com/324031239): Use url.mojom.Url (in url/mojom/url.mojom)
+  // instead of string.
+  string url;
+  // The line number of the reason.
+  uint64 line_number;
+  // The column number of the reason.
+  uint64 column_number;
+};
+
+// Detailed information for why the frame in question was blocked
+// from BFCache.
+struct BFCacheBlockingDetailedReason {
+  // The name of the reason.
+  // TODO(crbug.com/1519483): Define enums of reasons and use them instead of strings.
+  string name;
+  // The JavaScript source location of the reason. This should be null if the reason
+  // doesn't have a source location.
+  BlockingReasonSourceLocation? source;
+};
+
 // Struct for NotRestoredReasons API to report from browser to renderer. This
 // contains the information that is common to both same-origin and cross-origin iframes.
 struct BackForwardCacheNotRestoredReasons {
@@ -29,7 +55,7 @@
   string? name;
   // List of reasons that blocked back/forward cache if any.
   // TODO(crbug.com/1519483): Define enums of reasons and use them instead of strings.
-  array<string> reasons;
+  array<BFCacheBlockingDetailedReason> reasons;
   // This will be absl::nullopt when this document is cross-origin from the main
   // document.
   SameOriginBfcacheNotRestoredDetails? same_origin_details;
diff --git a/third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom b/third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom
index 9f561f5a..714d21e 100644
--- a/third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom
+++ b/third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom
@@ -17,9 +17,10 @@
   // TODO(crbug.com/1522767): Use enum instead of uint32 or have validation to
   // really avoid unintended messages.
   uint32? feature;
-  // The URL of the JavaScript file where the feature is used or where JavaScript executed.
-  // This will be empty when when a flag is disabled to send this information, and will be
-  // null when a feature is used from C++ in tests.
+  // The absolute URL of the JavaScript file where the feature is used or where JavaScript
+  // executed. This will be empty when when a flag is disabled to send this information, and
+  // will be null when a feature is used from C++ in tests.
+  // TODO(crbug.com/324031239): Use the struct url.mojom.Url instead of string.
   string? url;
   // The function in the JavaScript file where the feature is used or where JavaScript executed.
   // This will be empty when a flag is disabled to send this information, and will be null when
@@ -28,6 +29,8 @@
   // The line number in JavaScript file where the feature is used or where JavaScript executed.
   // This will be zero when a flag is disabled to send this information, and when a feature is
   // used from C++ in tests.
+  // TODO(crbug.com/1523193): Make `line_number` and `column_number` nullable, so the types here
+  // will correspond to blink::mojom::BFCacheBlockingDetailedReason.
   uint64 line_number;
   // The column number in JavaScript file where the feature is used or where JavaScript executed.
   // This will be zero when a flag is disabled to send this information, and when a feature is
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index e01031af..ce29d02 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -2908,7 +2908,7 @@
                                        overflow_style->OverscrollBehaviorY())));
       }
 
-      if (overflow_style->HasCustomScrollbarStyle()) {
+      if (overflow_style->HasCustomScrollbarStyle(GetDocument())) {
         update_scrollbar_style = true;
       }
     }
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index a5e2d4c..d763485 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -3189,8 +3189,19 @@
     if (reasons_to_copy->src) {
       not_restored_reasons->src = reasons_to_copy->src.value().c_str();
     }
-    for (const auto& reason : reasons_to_copy->reasons) {
-      not_restored_reasons->reasons.push_back(reason.c_str());
+    for (const auto& reason_to_copy : reasons_to_copy->reasons) {
+      mojom::blink::BFCacheBlockingDetailedReasonPtr reason =
+          mojom::blink::BFCacheBlockingDetailedReason::New();
+      reason->name = WTF::String(reason_to_copy->name);
+      if (reason_to_copy->source) {
+        mojom::blink::BlockingReasonSourceLocationPtr source_location =
+            mojom::blink::BlockingReasonSourceLocation::New();
+        source_location->url = WTF::String(reason_to_copy->source->url);
+        source_location->line_number = reason_to_copy->source->line_number;
+        source_location->column_number = reason_to_copy->source->column_number;
+        reason->source = std::move(source_location);
+      }
+      not_restored_reasons->reasons.push_back(std::move(reason));
     }
     if (reasons_to_copy->same_origin_details) {
       auto details = mojom::blink::SameOriginBfcacheNotRestoredDetails::New();
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index f69eb607..ed86caa 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -1032,8 +1032,9 @@
 
 bool LayoutBox::UsesOverlayScrollbars() const {
   NOT_DESTROYED();
-  if (StyleRef().HasCustomScrollbarStyle())
+  if (StyleRef().HasCustomScrollbarStyle(GetDocument())) {
     return false;
+  }
   if (GetFrame()->GetPage()->GetScrollbarTheme().UsesOverlayScrollbars())
     return true;
   return false;
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 3f15131..8d9a46d 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -1490,13 +1490,15 @@
     // can scroll.
     Element* body = doc.body();
     if (body && body->GetLayoutObject() && body->GetLayoutObject()->IsBox() &&
-        body->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle())
+        body->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle(doc)) {
       return *body->GetLayoutObject();
+    }
 
     // If the <body> didn't have a custom style, then the root element might.
     Element* doc_element = doc.documentElement();
     if (doc_element && doc_element->GetLayoutObject() &&
-        doc_element->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle() &&
+        doc_element->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle(
+            doc) &&
         !layout_box.StyleRef().UsesStandardScrollbarStyle()) {
       return *doc_element->GetLayoutObject();
     }
@@ -1543,7 +1545,8 @@
     return scrollbar->ScrollbarThickness();
 
   const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox());
-  if (style_source.StyleRef().HasCustomScrollbarStyle()) {
+  if (style_source.StyleRef().HasCustomScrollbarStyle(
+          style_source.GetDocument())) {
     return CustomScrollbar::HypotheticalScrollbarThickness(this, orientation,
                                                            &style_source);
   }
@@ -1561,7 +1564,8 @@
 
   const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox());
   bool needs_custom =
-      style_source.IsBox() && style_source.StyleRef().HasCustomScrollbarStyle();
+      style_source.IsBox() && style_source.StyleRef().HasCustomScrollbarStyle(
+                                  style_source.GetDocument());
 
   Scrollbar* scrollbars[] = {HorizontalScrollbar(), VerticalScrollbar()};
 
@@ -1646,9 +1650,10 @@
     // only appear when scrolling, we don't create them if there isn't overflow
     // to scroll. Thus, overlay scrollbars can't be "always on". i.e.
     // |overlay:scroll| behaves like |overlay:auto|.
+    Document& document = GetLayoutBox()->GetDocument();
     bool has_custom_scrollbar_style = ScrollbarStyleSource(*GetLayoutBox())
                                           .StyleRef()
-                                          .HasCustomScrollbarStyle();
+                                          .HasCustomScrollbarStyle(document);
     bool will_be_overlay = GetPageScrollbarTheme().UsesOverlayScrollbars() &&
                            !has_custom_scrollbar_style;
     if (will_be_overlay) {
@@ -2646,7 +2651,8 @@
   Scrollbar* scrollbar = nullptr;
   const LayoutObject& style_source =
       ScrollbarStyleSource(*ScrollableArea()->GetLayoutBox());
-  if (style_source.StyleRef().HasCustomScrollbarStyle()) {
+  if (style_source.StyleRef().HasCustomScrollbarStyle(
+          style_source.GetDocument())) {
     DCHECK(style_source.GetNode() && style_source.GetNode()->IsElementNode());
     scrollbar = MakeGarbageCollected<CustomScrollbar>(
         ScrollableArea(), orientation, &style_source);
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.cc
index 95734f5..fedfbbf 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.cc
@@ -128,6 +128,13 @@
   return style_.fade_out_duration;
 }
 
+ScrollbarPart ScrollbarThemeFluent::PartsToInvalidateOnThumbPositionChange(
+    const Scrollbar& scrollbar,
+    float old_position,
+    float new_position) const {
+  return ScrollbarPart::kNoPart;
+}
+
 int ScrollbarThemeFluent::ThumbThickness(
     const float scale_from_dip,
     const EScrollbarWidth scrollbar_width) const {
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.h
index 712562fc..ec9b10c 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent.h
@@ -51,6 +51,11 @@
   base::TimeDelta OverlayScrollbarFadeOutDelay() const override;
   base::TimeDelta OverlayScrollbarFadeOutDuration() const override;
 
+  ScrollbarPart PartsToInvalidateOnThumbPositionChange(
+      const Scrollbar&,
+      float old_position,
+      float new_position) const override;
+
  private:
   friend class ScrollbarThemeFluentMock;
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
index 076fb07e..15447def 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_fluent_unittest.cc
@@ -19,7 +19,7 @@
 
 namespace blink {
 
-using ::testing::NiceMock;
+using ::testing::Return;
 
 namespace {
 
@@ -62,7 +62,8 @@
   void SetUp() override {
     feature_list_.InitAndEnableFeature(::features::kFluentScrollbar);
     ScrollbarThemeSettings::SetFluentScrollbarsEnabled(true);
-    mock_scrollable_area_ = MakeGarbageCollected<MockScrollableArea>();
+    mock_scrollable_area_ = MakeGarbageCollected<MockScrollableArea>(
+        /*maximum_scroll_offset=*/ScrollOffset(0, 1000));
     mock_scrollable_area_->SetScaleFromDIP(GetParam());
     // ScrollbarThemeFluent Needs to be instantiated after feature flag and
     // scrollbar settings have been set.
@@ -179,6 +180,39 @@
                                    scrollbar_thickness));
 }
 
+// The test verifies that the track/buttons paint is not invalidated when
+// the thumb position changes. Aura scrollbars change arrow buttons color
+// when the scroll offset changes from and to the min/max scroll offset.
+// Fluent scrollbars do not change the arrow buttons color in this case.
+TEST_P(ScrollbarThemeFluentTest, ScrollbarTrackPartInvalidationTest) {
+  Scrollbar* scrollbar = Scrollbar::CreateForTesting(
+      mock_scrollable_area(), kVerticalScrollbar, &(theme_->GetInstance()));
+  ON_CALL(*mock_scrollable_area(), VerticalScrollbar())
+      .WillByDefault(Return(scrollbar));
+
+  scrollbar->SetFrameRect(
+      gfx::Rect(0, 0, ScrollbarThickness(), kScrollbarLength));
+  scrollbar->ClearTrackNeedsRepaint();
+
+  // Verifies that when the thumb position changes from min offset, the track
+  // invalidation is not triggered.
+  mock_scrollable_area()->SetScrollOffset(
+      ScrollOffset(0, 10), mojom::blink::ScrollType::kCompositor);
+  EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
+
+  // Verifies that when the thumb position changes from a non-zero offset,
+  // the track invalidation is not triggered.
+  mock_scrollable_area()->SetScrollOffset(
+      ScrollOffset(0, 20), mojom::blink::ScrollType::kCompositor);
+  EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
+
+  // Verifies that when the thumb position changes back to 0 (min) offset,
+  // the track invalidation is not triggered.
+  mock_scrollable_area()->SetScrollOffset(
+      ScrollOffset(0, 0), mojom::blink::ScrollType::kCompositor);
+  EXPECT_FALSE(scrollbar->TrackNeedsRepaint());
+}
+
 // Test that Scrollbar objects are correctly sized with Overlay Fluent theme
 // parts.
 TEST_P(OverlayScrollbarThemeFluentTest, OverlaySetsCorrectTrackAndInsetSize) {
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 0c56948..b96f7071 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -48,6 +48,7 @@
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/html/html_body_element.h"
 #include "third_party/blink/renderer/core/html/html_html_element.h"
 #include "third_party/blink/renderer/core/html/html_progress_element.h"
@@ -2280,6 +2281,24 @@
   return blink::GetVariableValue(*this, name, is_inherited_property);
 }
 
+bool ComputedStyle::HasCustomScrollbarStyle(const Document& document) const {
+
+  // Ignore ::-webkit-scrollbar when the web setting to prefer default scrollbar
+  // styling is true. This web setting ignores both ::-webkit-scrollbar styling
+  // and standard properties.
+  if (RuntimeEnabledFeatures::PreferDefaultScrollbarStylesEnabled()) {
+    const Settings* settings = document.GetSettings();
+    if (settings && settings->GetPrefersDefaultScrollbarStyles()) {
+      return false;
+    }
+  }
+
+  // Ignore non-standard ::-webkit-scrollbar when standard properties are in
+  // use.
+  return HasPseudoElementStyle(kPseudoIdScrollbar) &&
+         !UsesStandardScrollbarStyle();
+}
+
 Length ComputedStyle::LineHeight() const {
   const Length& lh = LineHeightInternal();
   // Unlike getFontDescription().computedSize() and hence fontSize(), this is
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 37839e9..153ebb5 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -712,12 +712,7 @@
            ScrollbarColor().has_value();
   }
 
-  // Ignore non-standard ::-webkit-scrollbar when standard properties are in
-  // use.
-  bool HasCustomScrollbarStyle() const {
-    return HasPseudoElementStyle(kPseudoIdScrollbar) &&
-           !UsesStandardScrollbarStyle();
-  }
+  bool HasCustomScrollbarStyle(const Document& document) const;
 
   // shape-outside (aka -webkit-shape-outside)
   ShapeValue* ShapeOutside() const { return ShapeOutsideInternal().Get(); }
diff --git a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
index 7841453..0524cd8 100644
--- a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
@@ -327,7 +327,7 @@
   HeapVector<Member<NotRestoredReasons>> children;
   for (const auto& reason : nrr->reasons) {
     NotRestoredReasonDetails* detail =
-        MakeGarbageCollected<NotRestoredReasonDetails>(reason);
+        MakeGarbageCollected<NotRestoredReasonDetails>(reason->name);
     reasons.push_back(detail);
   }
   if (nrr->same_origin_details) {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 03e73ba6..b20190e 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -3098,8 +3098,14 @@
     canvas->GetDocument().UpdateStyleAndLayoutTreeForElement(
         canvas, DocumentUpdateReason::kCanvas);
   }
+
   const Font& font = AccessFont(canvas);
 
+  if (HostAsOffscreenCanvas() && font.GetFontSelector() &&
+      !font.GetFontSelector()->GetExecutionContext()) {
+    return MakeGarbageCollected<TextMetrics>();
+  }
+
   const CanvasRenderingContext2DState& state = GetState();
   TextDirection direction = ToTextDirection(state.GetDirection(), canvas);
 
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d_test.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d_test.cc
index f20e9f6..5c389387 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d_test.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d_test.cc
@@ -273,5 +273,17 @@
   host->transferToImageBitmap(scope.GetScriptState(), no_exception);
 }
 
+// Regression test for https://crbug.com/1509382.
+TEST(OffscreenCanvasRenderingContext2DTest, NoCrashOnDocumentShutdown) {
+  test::TaskEnvironment task_environment;
+  V8TestingScope scope;
+  auto* host = OffscreenCanvas::Create(scope.GetScriptState(), /*width=*/10,
+                                       /*height=*/10);
+  OffscreenCanvasRenderingContext2D* context = GetContext(scope, host);
+  context->setFont("12px Ahem");
+  scope.GetDocument().Shutdown();
+  context->measureText("hello world");
+}
+
 }  // namespace
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_object.cc b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
index 67b8e615..ded2839 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_object.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
@@ -4,11 +4,31 @@
 
 #include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
 
+#include "base/numerics/checked_math.h"
 #include "gpu/command_buffer/client/webgpu_interface.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
 
 namespace blink {
 
+// ExternalMemoryTracker
+
+ExternalMemoryTracker::~ExternalMemoryTracker() {
+  SetCurrentSize(0);
+}
+
+void ExternalMemoryTracker::SetCurrentSize(size_t newSizeUnchecked) {
+  base::CheckedNumeric<int64_t> newSize = newSizeUnchecked;
+  base::CheckedNumeric<int64_t> deltaChecked = newSize - size_;
+
+  int64_t delta = deltaChecked.ValueOrDie();
+  if (delta != 0) {
+    v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(delta);
+    size_ = newSize.ValueOrDie();
+  }
+}
+
+// DawnObjectBase
+
 DawnObjectBase::DawnObjectBase(
     scoped_refptr<DawnControlClientHolder> dawn_control_client)
     : dawn_control_client_(std::move(dawn_control_client)) {}
@@ -31,6 +51,8 @@
   dawn_control_client_->Flush();
 }
 
+// DawnObjectImpl
+
 DawnObjectImpl::DawnObjectImpl(GPUDevice* device)
     : DawnObjectBase(device->GetDawnControlClient()), device_(device) {}
 
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_object.h b/third_party/blink/renderer/modules/webgpu/dawn_object.h
index 9bd305c..09ffb7fb 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_object.h
+++ b/third_party/blink/renderer/modules/webgpu/dawn_object.h
@@ -68,6 +68,31 @@
 
 class GPUDevice;
 
+// RAII tracker for known memory allocations outside of V8, informing V8 using
+// AdjustAmountOfExternalAllocatedMemory. This causes V8 to collect garbage
+// more often when it knows the footprint of V8-managed objects is large.
+//
+// - It is OK for the tracked size to be an estimate.
+// - It is OK to update the tracked size dynamically/asynchronously.
+// - It is OK to use this for CPU memory allocated in another process.
+// - It is NOT OK to use this for VRAM allocations. This may cause GC to
+//   trigger too often: GC is trying to manage CPU memory pressure, but freeing
+//   VRAM allocations may or may not reduce CPU memory pressure.
+class ExternalMemoryTracker final {
+ public:
+  // Non-copyable/non-movable
+  ExternalMemoryTracker(const ExternalMemoryTracker&) = delete;
+  ExternalMemoryTracker& operator=(const ExternalMemoryTracker&) = delete;
+
+  ExternalMemoryTracker() = default;
+  ~ExternalMemoryTracker();
+
+  void SetCurrentSize(size_t newSize);
+
+ private:
+  int64_t size_ = 0;
+};
+
 // This class allows objects to hold onto a DawnControlClientHolder.
 // The DawnControlClientHolder is used to hold the WebGPUInterface and keep
 // track of whether or not the client has been destroyed. If the client is
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
index 8076240..1c2721ab 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
@@ -7,6 +7,7 @@
 #include <dawn/webgpu.h>
 
 #include "base/command_line.h"
+#include "base/numerics/clamped_math.h"
 #include "gpu/command_buffer/client/webgpu_interface.h"
 #include "gpu/config/gpu_switches.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -41,7 +42,7 @@
   bool has_null_character = false;
   switch (wgsl_or_spirv->GetContentType()) {
     case V8UnionUSVStringOrUint32Array::ContentType::kUSVString: {
-      WTF::String wtf_wgsl_code(wgsl_or_spirv->GetAsUSVString());
+      const WTF::String& wtf_wgsl_code = wgsl_or_spirv->GetAsUSVString();
       wgsl_code = wtf_wgsl_code.Utf8();
       wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
       wgsl_desc.code = wgsl_code.c_str();
@@ -91,10 +92,25 @@
     shader_module = device->GetProcs().deviceCreateShaderModule(
         device->GetHandle(), &dawn_desc);
   }
+
   GPUShaderModule* shader =
       MakeGarbageCollected<GPUShaderModule>(device, shader_module);
   if (webgpu_desc->hasLabel())
     shader->setLabel(webgpu_desc->label());
+
+  // Very roughly approximate how much memory Tint might need for this shader.
+  // Pessimizes if Tint actually holds less memory than this (including if the
+  // shader module ends up being invalid).
+  //
+  // The actual estimate (100x code size) is chosen by profiling: large enough
+  // to show some improvement in peak GPU process memory usage, small enough to
+  // not slow down shader conformance tests (which are much, much heavier on
+  // shader creation than normal workloads) more than a few percent.
+  //
+  // TODO(crbug.com/dawn/2367): Get a real memory estimate from Tint.
+  base::ClampedNumeric<int32_t> input_code_size = wgsl_code.size();
+  shader->tint_memory_estimate_.SetCurrentSize(input_code_size * 100);
+
   return shader;
 }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
index e1c5dda..e7e40ab 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.h
@@ -27,6 +27,7 @@
 
   GPUShaderModule(const GPUShaderModule&) = delete;
   GPUShaderModule& operator=(const GPUShaderModule&) = delete;
+  ~GPUShaderModule() override = default;
 
   ScriptPromise getCompilationInfo(ScriptState* script_state);
 
@@ -39,6 +40,9 @@
     std::string utf8_label = value.Utf8();
     GetProcs().shaderModuleSetLabel(GetHandle(), utf8_label.c_str());
   }
+
+  // Holds an estimate of the memory used by Tint for this shader module.
+  ExternalMemoryTracker tint_memory_estimate_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index a29e8c3..cf6cbf0 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -17,12 +17,10 @@
 #include "components/viz/common/resources/transferable_resource.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/client_shared_image.h"
-#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/client/raster_interface.h"
 #include "gpu/command_buffer/client/shared_image_interface.h"
 #include "gpu/command_buffer/client/webgpu_interface.h"
 #include "gpu/command_buffer/common/capabilities.h"
-#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/shared_image_trace_utils.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
@@ -61,10 +59,6 @@
              "AddSharedImageRasterUsageWithNonOOPR",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAlwaysUseMappableSIForSoftwareCanvas,
-             "AlwaysUseMappableSIForSoftwareCanvas",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 }  // namespace
 
 CanvasResource::CanvasResource(base::WeakPtr<CanvasResourceProvider> provider,
@@ -444,27 +438,6 @@
                              context_provider_wrapper_->ContextProvider()
                                  ->GetCapabilities()
                                  .gpu_rasterization) {
-  auto* gpu_memory_buffer_manager =
-      SharedGpuContext::GetGpuMemoryBufferManager();
-
-  // Note that we create |gpu_memory_buffer_| only when MappableSI is not used
-  // and disabled.
-  if (!is_accelerated_ &&
-      !base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)) {
-    DCHECK(gpu_memory_buffer_manager);
-    DCHECK(shared_image_usage_flags & gpu::SHARED_IMAGE_USAGE_DISPLAY_READ);
-
-    gpu_memory_buffer_ = gpu_memory_buffer_manager->CreateGpuMemoryBuffer(
-        Size(), GetBufferFormat(), gfx::BufferUsage::SCANOUT_CPU_READ_WRITE,
-        gpu::kNullSurfaceHandle, nullptr);
-    if (!gpu_memory_buffer_)
-      return;
-
-#if BUILDFLAG(IS_MAC)
-    gpu_memory_buffer_->SetColorSpace(GetColorSpace());
-#endif
-  }
-
   auto* shared_image_interface =
       context_provider_wrapper_->ContextProvider()->SharedImageInterface();
   DCHECK(shared_image_interface);
@@ -510,12 +483,9 @@
   SkAlphaType surface_alpha_type = GetSkColorInfo().alphaType();
 
   scoped_refptr<gpu::ClientSharedImage> client_shared_image;
-  if (!is_accelerated_ &&
-      base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)) {
-    CHECK(!gpu_memory_buffer_);
-    // Using the new SII to create CPU mappable mailbox when this feature is
-    // enabled. Ideally we should add SHARED_IMAGE_USAGE_CPU_WRITE to the
-    // shared image usage flag here since mailbox will be used for CPU writes
+  if (!is_accelerated_) {
+    // Ideally we should add SHARED_IMAGE_USAGE_CPU_WRITE to the shared image
+    // usage flag here since mailbox will be used for CPU writes
     // by the client. But doing that stops us from using CompoundImagebacking as
     // many backings do not support SHARED_IMAGE_USAGE_CPU_WRITE.
     // TODO(crbug.com/1478238): Add that usage flag back here once the issue is
@@ -528,12 +498,6 @@
     if (!client_shared_image) {
       return;
     }
-  } else if (gpu_memory_buffer_) {
-    client_shared_image = shared_image_interface->CreateSharedImage(
-        GetSharedImageFormat(), Size(), GetColorSpace(), surface_origin,
-        surface_alpha_type, shared_image_usage_flags, "CanvasResourceRasterGmb",
-        gpu_memory_buffer_->CloneHandle());
-    CHECK(client_shared_image);
   } else {
     client_shared_image = shared_image_interface->CreateSharedImage(
         GetSharedImageFormat(), Size(), GetColorSpace(), surface_origin,
@@ -723,29 +687,18 @@
     std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> mapping;
     void* memory = nullptr;
     size_t stride = 0;
-    if (base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)) {
-      mapping = client_shared_image()->Map();
-      if (!mapping) {
-        LOG(ERROR) << "MapSharedImage Failed.";
-        return nullptr;
-      }
-      memory = mapping->Memory(0);
-      stride = mapping->Stride(0);
-    } else {
-      if (!gpu_memory_buffer_->Map()) {
-        LOG(ERROR) << "Unable to map gpu_memory_buffer_";
-        return nullptr;
-      }
-      memory = gpu_memory_buffer_->memory(0);
-      stride = gpu_memory_buffer_->stride(0);
+    mapping = client_shared_image()->Map();
+    if (!mapping) {
+      LOG(ERROR) << "MapSharedImage Failed.";
+      return nullptr;
     }
+    memory = mapping->Memory(0);
+    stride = mapping->Stride(0);
     SkPixmap pixmap(CreateSkImageInfo(), memory, stride);
     auto sk_image = SkImages::RasterFromPixmapCopy(pixmap);
 
     // Unmap the underlying buffer.
-    base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)
-        ? mapping.reset()
-        : gpu_memory_buffer_->Unmap();
+    mapping.reset();
     return sk_image ? UnacceleratedStaticBitmapImage::Create(sk_image)
                     : nullptr;
   }
@@ -811,22 +764,13 @@
   std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> mapping;
   void* memory = nullptr;
   size_t stride = 0;
-  if (base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)) {
-    mapping = client_shared_image()->Map();
-    if (!mapping) {
-      LOG(ERROR) << "MapSharedImage failed.";
-      return;
-    }
-    memory = mapping->Memory(0);
-    stride = mapping->Stride(0);
-  } else {
-    if (!gpu_memory_buffer_->Map()) {
-      LOG(ERROR) << "Unable to map gpu_memory_buffer_.";
-      return;
-    }
-    memory = gpu_memory_buffer_->memory(0);
-    stride = gpu_memory_buffer_->stride(0);
+  mapping = client_shared_image()->Map();
+  if (!mapping) {
+    LOG(ERROR) << "MapSharedImage failed.";
+    return;
   }
+  memory = mapping->Memory(0);
+  stride = mapping->Stride(0);
 
   auto surface = SkSurfaces::WrapPixels(CreateSkImageInfo(), memory, stride);
   SkPixmap pixmap;
@@ -834,9 +778,7 @@
   surface->writePixels(pixmap, 0, 0);
 
   // Unmap the underlying buffer.
-  base::FeatureList::IsEnabled(kAlwaysUseMappableSIForSoftwareCanvas)
-      ? mapping.reset()
-      : gpu_memory_buffer_->Unmap();
+  mapping.reset();
   sii->UpdateSharedImage(gpu::SyncToken(), client_shared_image()->mailbox());
   owning_thread_data().sync_token = sii->GenUnverifiedSyncToken();
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.h b/third_party/blink/renderer/platform/graphics/canvas_resource.h
index 08adfbe..2499528 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.h
@@ -37,7 +37,6 @@
 namespace gfx {
 
 class ColorSpace;
-class GpuMemoryBuffer;
 
 }  // namespace gfx
 
@@ -459,10 +458,6 @@
   // active readers or not.
   bool is_origin_clean_ = true;
 
-  // GMB based software raster path. The resource is written to on the CPU but
-  // passed using the mailbox to the display compositor for use as an overlay.
-  std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer_;
-
   // Accessed on any thread.
   const gfx::Size size_;
   const bool is_origin_top_left_;
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/test_loader.py b/third_party/blink/tools/blinkpy/wpt_tests/test_loader.py
index 685ee0db..2181e86 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/test_loader.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/test_loader.py
@@ -38,7 +38,7 @@
 
 path_finder.bootstrap_wpt_imports()
 from tools.manifest.manifest import Manifest
-from wptrunner import manifestexpected, testloader, wpttest
+from wptrunner import manifestexpected, testloader, testrunner, wpttest
 from wptrunner.wptmanifest import node as wptnode
 from wptrunner.wptmanifest.backends import static
 
@@ -169,10 +169,52 @@
 
     @classmethod
     def install(cls, port: Port, expectations: TestExpectations):
+        """Patch overrides into the wptrunner API (may be unstable)."""
         testloader.TestLoader = functools.partial(cls,
                                                   port,
                                                   expectations=expectations)
 
+        # Ideally, we would patch `executorchrome.*.convert_result`, but changes
+        # to the executor classes here in the main process don't persist to
+        # worker processes, which reload the module from source. Therefore,
+        # patch `TestRunnerManager`, which runs in the main process.
+        test_ended = testrunner.TestRunnerManager.test_ended
+
+        @functools.wraps(test_ended)
+        def wrapper(self, test, results):
+            return test_ended(self, test,
+                              allow_any_subtests_on_timeout(test, results))
+
+        testrunner.TestRunnerManager.test_ended = wrapper
+
+
+Results = Tuple[wpttest.Result, List[wpttest.SubtestResult]]
+
+
+def allow_any_subtests_on_timeout(test: wpttest.Test,
+                                  results: Results) -> Results:
+    """On timeout, suppress all subtest mismatches with added expectations.
+
+    When a test times out in `run_web_tests.py`, text mismatches don't affect
+    pass/fail status or trigger retries. This prevents the test from being
+    susceptible to flakiness due to variation in which subtest times out and
+    lets build gardeners suppress failures with just a `[ Timeout ]` line in
+    TestExpectations.
+
+    This converter injects extra expectations to mimic the `run_web_tests.py`
+    behavior. Note that, if the test expected to time out runs to completion,
+    the subtest results are then checked as usual.
+
+    See Also:
+        https://github.com/web-platform-tests/wpt/pull/44134
+    """
+    harness_result, subtest_results = results
+    if harness_result.status == 'TIMEOUT':
+        for result in subtest_results:
+            result.expected, result.known_intermittent = result.status, []
+            result.message = test.expected_fail_message(result.name)
+    return harness_result, subtest_results
+
 
 def _build_expectation_ast(name: str,
                            statuses: Container[str],
@@ -237,20 +279,7 @@
                     expr_data={},
                     data_cls_getter=lambda x, y: manifestexpected.SubtestNode,
                     test_path=self.parent.test_path))
-        subtest = super().get_subtest(name)
-        # When a test times out in `run_web_tests.py`, the text output is still
-        # diffed but mismatches don't affect pass/fail status or retries. For
-        # wptrunner to mimic this behavior with variations in timing (i.e.,
-        # which subtest times out can differ between runs), any subtest is
-        # allowed to time out or not run if the overall test is expected to
-        # time out.
-        if subtest and 'TIMEOUT' in {self.expected, *self.known_intermittent}:
-            expected = [subtest.expected, *subtest.known_intermittent]
-            for status in ['TIMEOUT', 'NOTRUN']:
-                if status not in expected:
-                    expected.append(status)
-            subtest.set('expected', expected)
-        return subtest
+        return super().get_subtest(name)
 
 
 def _test_basename(test_id: str) -> str:
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/test_loader_unittest.py b/third_party/blink/tools/blinkpy/wpt_tests/test_loader_unittest.py
index 9f768ed..9f0b895 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/test_loader_unittest.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/test_loader_unittest.py
@@ -5,13 +5,18 @@
 import json
 import textwrap
 import unittest
+from unittest import mock
 
 from blinkpy.common import path_finder
 from blinkpy.common.host_mock import MockHost
-from blinkpy.wpt_tests.test_loader import TestLoader, wpt_url_to_blink_test
+from blinkpy.wpt_tests.test_loader import (
+    allow_any_subtests_on_timeout,
+    TestLoader,
+    wpt_url_to_blink_test,
+)
 
 path_finder.bootstrap_wpt_imports()
-from wptrunner import wptlogging
+from wptrunner import wptlogging, wpttest
 from tools.manifest.manifest import load_and_update
 
 
@@ -263,14 +268,51 @@
 
         subtest = test.get_subtest('subtest1')
         self.assertEqual(subtest.expected, 'FAIL')
-        self.assertEqual(subtest.known_intermittent, ['TIMEOUT', 'NOTRUN'])
+        self.assertEqual(subtest.known_intermittent, [])
 
         subtest = test.get_subtest('subtest2')
         self.assertEqual(subtest.expected, 'PASS')
-        self.assertEqual(subtest.known_intermittent, ['TIMEOUT', 'NOTRUN'])
+        self.assertEqual(subtest.known_intermittent, [])
 
     def test_wpt_url_to_exp_test(self):
         self.assertEqual(wpt_url_to_blink_test('/css/test.html?a'),
                          'external/wpt/css/test.html?a')
         self.assertEqual(wpt_url_to_blink_test('/wpt_internal/test.html'),
                          'wpt_internal/test.html')
+
+    def test_allow_any_subtests_on_timeout(self):
+        test = mock.Mock()
+        test.expected_fail_message.return_value = 'expect this message'
+        test_result = wpttest.TestharnessResult('TIMEOUT',
+                                                message=None,
+                                                expected='TIMEOUT')
+        subtest_result = wpttest.TestharnessSubtestResult('subtest',
+                                                          'TIMEOUT',
+                                                          message=None,
+                                                          expected='FAIL')
+
+        test_result, (subtest_result, ) = allow_any_subtests_on_timeout(
+            test, (test_result, [subtest_result]))
+        self.assertEqual(subtest_result.expected, 'TIMEOUT')
+        self.assertEqual(subtest_result.known_intermittent, [])
+        self.assertEqual(subtest_result.message, 'expect this message')
+        test.expected_fail_message.assert_called_once_with('subtest')
+
+    def test_do_not_allow_any_subtests_on_completion(self):
+        test = mock.Mock()
+        test.expected_fail_message.return_value = (
+            'should not expect this message')
+        test_result = wpttest.TestharnessResult('OK',
+                                                message=None,
+                                                expected='TIMEOUT')
+        subtest_result = wpttest.TestharnessSubtestResult('subtest',
+                                                          'FAIL',
+                                                          message=None,
+                                                          expected='TIMEOUT')
+
+        test_result, (subtest_result, ) = allow_any_subtests_on_timeout(
+            test, (test_result, [subtest_result]))
+        self.assertEqual(subtest_result.expected, 'TIMEOUT')
+        self.assertEqual(subtest_result.known_intermittent, [])
+        self.assertIsNone(subtest_result.message)
+        test.expected_fail_message.assert_not_called()
diff --git a/third_party/blink/web_tests/ChromeTestExpectations b/third_party/blink/web_tests/ChromeTestExpectations
index 0a42599..d40b321 100644
--- a/third_party/blink/web_tests/ChromeTestExpectations
+++ b/third_party/blink/web_tests/ChromeTestExpectations
@@ -55,24 +55,25 @@
 crbug.com/1512119 external/wpt/webdriver/tests/classic/perform_actions/pointer_touch.py [ Failure ]
 
 # Those tests (flaky) fail when DCHECK enabled
+crbug.com/1499775 external/wpt/svg/styling/use-element-transitions.html [ Failure Pass ]
 crbug.com/1499775 external/wpt/media-capabilities/encodingInfo.any.worker.html [ Crash ]
 crbug.com/1499775 external/wpt/navigation-api/navigate-event/navigate-history-back-bfcache.html [ Failure ]
 crbug.com/1499775 external/wpt/storage-access-api/requestStorageAccess-cross-origin-iframe-navigation.sub.https.window.html [ Failure Timeout ]
 crbug.com/1499775 external/wpt/url/failure.html [ Pass Timeout ]
-crbug.com/1499775 external/wpt/credential-management/fedcm-endpoint-redirects.https.html [ Crash Timeout ]
+crbug.com/1499775 external/wpt/credential-management/fedcm-endpoint-redirects.https.html [ Crash Failure Timeout ]
 crbug.com/1499775 external/wpt/credential-management/fedcm-no-login-url.https.html [ Crash Timeout ]
-crbug.com/1499775 wpt_internal/credential-management/fedcm-reauth-with-signin-status.https.html [ Crash Timeout ]
+crbug.com/1499775 wpt_internal/credential-management/fedcm-reauth-with-signin-status.https.html [ Crash Failure Timeout ]
 crbug.com/1499775 external/wpt/css/css-properties-values-api/animation/custom-property-animation-used-in-shorthand.html [ Failure Pass ]
 crbug.com/1499775 external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-seeking.html [ Failure Pass ]
 crbug.com/1499775 external/wpt/screen-capture/getdisplaymedia.https.html [ Crash Failure Pass ]
 crbug.com/1499775 external/wpt/webxr/xrSession_requestAnimationFrame_getViewerPose.https.html [ Failure Pass ]
 crbug.com/1499775 external/wpt/cookies/attributes/attributes-ctl.sub.html [ Failure Pass ]
-crbug.com/1499775 external/wpt/credential-management/fedcm-auto-selected-flag.https.html [ Crash Pass Timeout ]
-crbug.com/1499775 external/wpt/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html [ Crash Pass Timeout ]
-crbug.com/1499775 external/wpt/credential-management/fedcm-returning-account-auto-reauthn.https.html [ Crash Pass Timeout ]
+crbug.com/1499775 external/wpt/credential-management/fedcm-auto-selected-flag.https.html [ Crash Failure Timeout ]
+crbug.com/1499775 external/wpt/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html [ Crash Failure Timeout ]
+crbug.com/1499775 external/wpt/credential-management/fedcm-returning-account-auto-reauthn.https.html [ Crash Failure Timeout ]
 crbug.com/1499775 external/wpt/credential-management/fedcm-pending-disconnect.https.html [ Crash Pass Timeout ]
 crbug.com/1499775 external/wpt/credential-management/fedcm-too-many-disconnect-calls.https.html [ Crash Pass Timeout ]
-crbug.com/1499775 external/wpt/credential-management/fedcm-pending-userinfo.https.html [ Crash Pass Timeout ]
+crbug.com/1499775 external/wpt/credential-management/fedcm-pending-userinfo.https.html [ Crash Failure Timeout ]
 crbug.com/1499775 wpt_internal/credential-management/fedcm-mismatch-dialog.tentative.https.html [ Crash Pass Timeout ]
 crbug.com/1499775 external/wpt/css/css-grid/alignment/grid-item-aspect-ratio-stretch-4.html [ Failure Pass ]
 crbug.com/1499775 external/wpt/css/css-images/object-view-box-fit-fill-video.html [ Failure Pass ]
@@ -385,6 +386,7 @@
 crbug.com/1499775 external/wpt/html/semantics/popovers/popover-open-overflow-display.tentative.html [ Failure ]  # Reftest image failure
 crbug.com/1499775 external/wpt/html/semantics/popovers/popover-top-layer-combinations.html [ Timeout ]
 crbug.com/1499775 external/wpt/html/semantics/popovers/popover-top-layer-interactions.html [ Timeout ]
+crbug.com/1499775 external/wpt/html/semantics/popovers/popover-focus-2.html [ Failure ]  # Flaky output
 crbug.com/1499775 external/wpt/html/user-activation/activation-trigger-keyboard-enter.html [ Timeout ]
 crbug.com/1499775 external/wpt/html/user-activation/activation-trigger-mouse-left.html [ Timeout ]
 crbug.com/1499775 external/wpt/html/user-activation/activation-trigger-mouse-right.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 430ebc6..3d52308 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -50,6 +50,7 @@
 external/wpt/infrastructure/expected-fail/timeout.html [ Timeout ]
 external/wpt/infrastructure/reftest/reftest_ref_timeout.html [ Timeout ]
 external/wpt/infrastructure/reftest/reftest_timeout.html [ Timeout ]
+wpt_internal/infrastructure/fail-before-timeout.html [ Timeout ]
 
 # We don't support extracting fuzzy information from .ini files, which these
 # WPT infrastructure tests rely on.
@@ -6732,8 +6733,6 @@
 crbug.com/1519178 [ Mac12 ] virtual/fenced-frame-mparch/http/tests/inspector-protocol/fenced-frame/automatic-beacon.https.js [ Failure Pass ]
 crbug.com/1521090 [ Mac12 ] virtual/static-routing-api/http/tests/inspector-protocol/service-worker/tentative/static-router/receive-router-rules-on-update.js [ Failure Pass ]
 
-crbug.com/1522196 http/tests/devtools/resource-har-conversion.js [ Failure Pass ]
-
 ### sheriff 2023-01-24
 crbug.com/1521311 http/tests/devtools/service-workers/sw-navigate-useragent.js [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index f651d2fe..266014a 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -2672,5 +2672,15 @@
     "bases": [
       "external/wpt/custom-elements/state"
     ]
+  },
+  {
+    "prefix": "prefers-default-scrollbar-styles",
+    "platforms": ["Linux"],
+    "exclusive_tests": "ALL",
+    "bases": ["scrollbars/prefers-default-scrollbar-styles"],
+    "args": ["--enable-blink-features=PreferDefaultScrollbarStyles",
+             "--blink-settings=prefersDefaultScrollbarStyles=true"],
+    "expires": "Feb 1, 2025",
+    "owners": ["evelynn.kaplan@microsoft.com", "almaher@microsoft.com"]
   }
 ]
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py
index c2bcfce..d30d41b 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/direct.py
@@ -4,4 +4,8 @@
 def main(request, response):
     if 'server_slow' in request.url_parts.query:
         time.sleep(0.2)
+    if 'server_no_content' in request.url_parts.query:
+        return 204, [(b'Content-Type', b'text/plain')], u'Network with %s request' % request.method
+    if 'server_not_found' in request.url_parts.query:
+        return 404, [(b'Content-Type', b'text/plain')], u'Not Found'
     return 200, [(b'Content-Type', b'text/plain')], u'Network with %s request' % request.method
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html
index de31f6c..0dad64ad9 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-race-network-and-fetch-handler.https.html
@@ -61,5 +61,31 @@
   const {requests}  = await get_info_from_worker(worker);
   assert_equals(requests.length, 2);
 }, 'Subresource load matched the rule with race-network-and-fetch-handler source, and the server reseponse is faster than the fetch handler');
+
+promise_test(async t => {
+  const rnd = randomString();
+  const worker = await registerAndActivate(t, ROUTER_KEY, SW_SRC);
+  const iframe = await createIframe(t, FRAME_SRC);
+  // Expect the response from the network request.
+  const response = await iframe.contentWindow.fetch(`?nonce=${rnd}&sw_slow&server_no_content`);
+  assert_equals(response.status, 204);
+  // Ensure the fetch handler is also executed.
+  const {requests}  = await get_info_from_worker(worker);
+  assert_equals(requests.length, 2);
+}, 'Subresource load matched the rule with race-network-and-fetch-handler source, and the server reseponse with 204 response is faster than the fetch handler');
+
+
+promise_test(async t => {
+  const rnd = randomString();
+  const worker = await registerAndActivate(t, ROUTER_KEY, SW_SRC);
+  const iframe = await createIframe(t, FRAME_SRC);
+  const response = await iframe.contentWindow.fetch(`?nonce=${rnd}&sw_slow&server_not_found`);
+  // Expect the response from the network request was faster, but the result was 404.
+  // So, the fetch handler result will be used instead.
+  assert_equals(response.status, 200);
+  assert_equals(await response.text(), rnd);
+  const {requests}  = await get_info_from_worker(worker);
+  assert_equals(requests.length, 2);
+}, 'Subresource load matched the rule with race-network-and-fetch-handler source, and the server reseponse is faster than the fetch handler, but not found');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/http/tests/devtools/resource-har-conversion-expected.txt b/third_party/blink/web_tests/http/tests/devtools/resource-har-conversion-expected.txt
index e7337727..1591381 100644
--- a/third_party/blink/web_tests/http/tests/devtools/resource-har-conversion-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/resource-har-conversion-expected.txt
@@ -1,6 +1,8 @@
 Tests conversion of Inspector's resource representation into HAR format.
 
 Page reloaded.
+error: Failed getting cookie attribute: Discard
+error: Failed getting cookie attribute: Version
 {
     creator : {
         name : "WebInspector"
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/interaction/focus/document-level-focus-apis/document-has-system-focus-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/interaction/focus/document-level-focus-apis/document-has-system-focus-expected.txt
new file mode 100644
index 0000000..5214859
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/interaction/focus/document-level-focus-apis/document-has-system-focus-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+[FAIL] Top-level document receives blur/focus events and loses system focus during opening/closing of a popup
+  assert_true: Document received blur event when popup opened expected true got false
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/semantics/popovers/popover-focus-2-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/semantics/popovers/popover-focus-2-expected.txt
deleted file mode 100644
index 5b37deb..0000000
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/semantics/popovers/popover-focus-2-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-All subtests passed and are omitted for brevity.
-See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
-Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/screen-orientation/onchange-event-subframe-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/screen-orientation/onchange-event-subframe-expected.txt
new file mode 100644
index 0000000..fa4e62a3c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/screen-orientation/onchange-event-subframe-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+[FAIL] Test subframes receive orientation change events
+  promise_test: Unhandled rejection with value: object "NotSupportedError: screen.orientation.lock() is not available on this device."
+[FAIL] Check directly that events are fired in right order (from top to bottom)
+  promise_test: Unhandled rejection with value: object "NotSupportedError: screen.orientation.lock() is not available on this device."
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/speech-api/SpeechSynthesis-pause-resume.tentative-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/speech-api/SpeechSynthesis-pause-resume.tentative-expected.txt
new file mode 100644
index 0000000..73b0b7e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/speech-api/SpeechSynthesis-pause-resume.tentative-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+[FAIL] speechSynthesis.pause() and resume() state and events
+  assert_unreached: error event Reached unreachable code
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled.html b/third_party/blink/web_tests/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled.html
new file mode 100644
index 0000000..6759627
--- /dev/null
+++ b/third_party/blink/web_tests/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>
+  Tests that the scrollbar styling using ::webkit-scrollbar pseudos is
+  disabled when there is a preference for default scrollbar styles.
+</title>
+<style>
+  p {
+    font-size: 100px;
+  }
+
+  ::-webkit-scrollbar {
+    width: 100px;
+    height: 40px;
+  }
+
+  ::-webkit-scrollbar-button
+  {
+    background-color: red;
+  }
+
+  ::-webkit-scrollbar-corner {
+    background-color: orange;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    background-color: green;
+  }
+
+  ::-webkit-scrollbar-track {
+    background-color: purple;
+  }
+
+  ::-webkit-scrollbar-track-piece {
+    background-color: blue;
+  }
+</style>
+<body>
+  <p>testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest</p>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+</body>
diff --git a/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/README.md b/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/README.md
new file mode 100644
index 0000000..fd836a1
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/README.md
@@ -0,0 +1,2 @@
+These tests run with the runtime feature PreferDefaultScrollbarStyles enabled
+and the blink web setting prefersDefaultScrollbarStyles set to true.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled-expected.html b/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled-expected.html
new file mode 100644
index 0000000..3e180d9
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefers-default-scrollbar-styles/scrollbars/prefers-default-scrollbar-styles/webkit-scrollbar-styles-disabled-expected.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>
+  Tests that the scrollbar styling using ::webkit-scrollbar pseudos is
+  disabled when there is a preference for default scrollbar styles.
+</title>
+<style>
+  p {
+    font-size: 100px;
+  }
+</style>
+<body>
+  <p>testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest</p>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+  <br><br><br><br><br>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/infrastructure/fail-before-timeout.html b/third_party/blink/web_tests/wpt_internal/infrastructure/fail-before-timeout.html
new file mode 100644
index 0000000..108345d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/infrastructure/fail-before-timeout.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>`run_wpt_tests.py` should ignore unexpected FAIL before expected TIMEOUT</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(() => {
+    assert_equals(1, 2);
+  }, 'Fail unexpectedly');
+
+  promise_test(async (t) =>
+    new Promise((resolve) => t.step_timeout(resolve, 120000)), 'Timeout expectedly');
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/delay_get_texture.https.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/delay_get_texture.https.html
new file mode 100644
index 0000000..8dce6d0b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/delay_get_texture.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/" />
+  <title>WebGPU delay getCurrentTexture</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <meta name="assert" content="WebGPU delay calling getCurrentTexture should be presented correctly" />
+  <link rel="match" href="./ref/delay_get_texture-ref.html" />
+  <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+  <script type="module" src="delay_get_texture.html.js"></script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
new file mode 100644
index 0000000..624dadf9
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/ref/" />
+  <title>WebGPU delay getCurrentTexture (ref)</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+  <script>
+    function draw(canvas) {
+      var c = document.getElementById(canvas);
+      var ctx = c.getContext('2d');
+      ctx.fillStyle = '#00FF00';
+      ctx.fillRect(0, 0, c.width, c.height);
+    }
+
+    draw('cvs0');
+ </script>
+</html>
diff --git a/third_party/cardboard/proguard-rules.pro b/third_party/cardboard/proguard-rules.pro
index a698b2365..b5ca79e 100644
--- a/third_party/cardboard/proguard-rules.pro
+++ b/third_party/cardboard/proguard-rules.pro
@@ -20,3 +20,7 @@
   getDistortionCoefficientsCount();
   getLeftEyeFieldOfViewAngles(int);
 }
+# Prevent native methods which are used from being obfuscated.
+-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class com.google.cardboard.sdk.** {
+  native <methods>;
+}
diff --git a/third_party/catapult b/third_party/catapult
index 27e029f..ca502d3 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 27e029f52955bf8ac9e35a1dacf4f56bb39d3eb8
+Subproject commit ca502d307f30e7ac2de76211d8d590ff31e5080f
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 94fec55..8460157 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 94fec55cb64a630ad0790e1833dd195181ce6688
+Subproject commit 8460157bc5ad13094b0e93276c7387b47932195d
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 784db7a..74a6ca9 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 784db7a5f04f5093941b6c2228e40b9a10d553b2
+Subproject commit 74a6ca92bba677fc87328637bf573deb5944ac91
diff --git a/third_party/libc++/src b/third_party/libc++/src
index 9d119c1..c51c9efb 160000
--- a/third_party/libc++/src
+++ b/third_party/libc++/src
@@ -1 +1 @@
-Subproject commit 9d119c1f4a097b7d27210874f4eba3fc91a83a4e
+Subproject commit c51c9efb6c5a83e0e25c1e30864449d2beadd7a8
diff --git a/third_party/libunwind/src b/third_party/libunwind/src
index fc50574..8bad7bd 160000
--- a/third_party/libunwind/src
+++ b/third_party/libunwind/src
@@ -1 +1 @@
-Subproject commit fc505746f02c927d792bdeb328307e0e87500342
+Subproject commit 8bad7bd6ec30f94bce82f7cb5b58ecbd6ce02996
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index 739dc8a..03c85f7 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit 739dc8a257547a54c71ecf73bba66bef0726825a
+Subproject commit 03c85f7fe80efa241092f258e33452f47b2ace4e
diff --git a/third_party/perfetto b/third_party/perfetto
index 74cdba3..6821c955 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 74cdba3b28c2a48bb3c5b5cae32812eb6c76a8da
+Subproject commit 6821c955bdf682556ec4828a174a9cd916d0a02a
diff --git a/third_party/skia b/third_party/skia
index 9b103a4..fe4aa8a3 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 9b103a4148e291537909a1c0765b43a5125676d2
+Subproject commit fe4aa8a3ea5380f02ba16568299b2d11a2c0a41e
diff --git a/third_party/webgpu-cts/resource_files.txt b/third_party/webgpu-cts/resource_files.txt
index f0e84564..2866cba 100644
--- a/third_party/webgpu-cts/resource_files.txt
+++ b/third_party/webgpu-cts/resource_files.txt
@@ -1,14 +1,17 @@
 README.md
 cache
+four-colors-h264-bt601-hflip.mp4
 four-colors-h264-bt601-rotate-180.mp4
 four-colors-h264-bt601-rotate-270.mp4
 four-colors-h264-bt601-rotate-90.mp4
+four-colors-h264-bt601-vflip.mp4
 four-colors-h264-bt601.mp4
-four-colors-theora-bt601.ogv
 four-colors-vp8-bt601.webm
+four-colors-vp9-bt601-hflip.mp4
 four-colors-vp9-bt601-rotate-180.mp4
 four-colors-vp9-bt601-rotate-270.mp4
 four-colors-vp9-bt601-rotate-90.mp4
+four-colors-vp9-bt601-vflip.mp4
 four-colors-vp9-bt601.mp4
 four-colors-vp9-bt601.webm
 four-colors-vp9-bt709.webm
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index e082b08..6493b87 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit e082b08475761a2ba6a3349dfea72f704c8b68d4
+Subproject commit 6493b876ade082dc4e4d883691e8900d5b42f01c
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 6b552a86..320c2cf 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -179,6 +179,7 @@
 src/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.ts
 src/webgpu/api/operation/memory_sync/buffer/multiple_buffers.spec.ts
 src/webgpu/api/operation/memory_sync/buffer/single_buffer.spec.ts
+src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts
 src/webgpu/api/operation/memory_sync/texture/texture_sync_test.ts
 src/webgpu/api/operation/memory_sync/texture/same_subresource.spec.ts
 src/webgpu/api/operation/pipeline/default_layout.spec.ts
@@ -205,6 +206,7 @@
 src/webgpu/api/operation/rendering/stencil.spec.ts
 src/webgpu/api/operation/resource_init/buffer.spec.ts
 src/webgpu/util/texture/subresource.ts
+src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts
 src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
 src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
 src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
@@ -243,6 +245,7 @@
 src/webgpu/api/validation/capability_checks/features/texture_formats.spec.ts
 src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
 src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
+src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts
 src/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup.spec.ts
 src/webgpu/api/validation/capability_checks/limits/maxBufferSize.spec.ts
 src/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample.spec.ts
@@ -359,10 +362,12 @@
 src/webgpu/shader/types.ts
 src/webgpu/shader/values.ts
 src/webgpu/shader/execution/float_parse.spec.ts
+src/webgpu/shader/execution/memory_layout.spec.ts
 src/webgpu/shader/execution/padding.spec.ts
 src/webgpu/shader/execution/robust_access.spec.ts
 src/webgpu/shader/execution/robust_access_vertex.spec.ts
 src/webgpu/shader/execution/shadow.spec.ts
+src/webgpu/shader/execution/stage.spec.ts
 src/webgpu/shader/execution/zero_init.spec.ts
 src/webgpu/shader/execution/expression/expression.ts
 src/webgpu/shader/execution/expression/binary/af_addition.cache.ts
@@ -480,6 +485,8 @@
 src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts
 src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
@@ -533,6 +540,10 @@
 src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts
 src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts
@@ -595,6 +606,8 @@
 src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts
 src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
 src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
@@ -614,6 +627,8 @@
 src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
 src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts
 src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
+src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts
+src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts
 src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts
 src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
 src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
@@ -631,6 +646,7 @@
 src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
 src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts
 src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
+src/webgpu/shader/execution/expression/unary/indirection.spec.ts
 src/webgpu/shader/execution/expression/unary/u32_complement.cache.ts
 src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
 src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts
@@ -655,13 +671,17 @@
 src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
 src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
 src/webgpu/shader/execution/shader_io/shared_structs.spec.ts
+src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts
+src/webgpu/shader/execution/statement/compound.spec.ts
 src/webgpu/shader/execution/statement/increment_decrement.spec.ts
 src/webgpu/shader/validation/shader_validation_test.ts
 src/webgpu/shader/validation/const_assert/const_assert.spec.ts
+src/webgpu/shader/validation/decl/compound_statement.spec.ts
 src/webgpu/shader/validation/decl/const.spec.ts
 src/webgpu/shader/validation/decl/override.spec.ts
 src/webgpu/shader/validation/decl/util.ts
 src/webgpu/shader/validation/decl/ptr_spelling.spec.ts
+src/webgpu/shader/validation/decl/var.spec.ts
 src/webgpu/shader/validation/decl/var_access_mode.spec.ts
 src/webgpu/shader/validation/expression/access/vector.spec.ts
 src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
@@ -669,12 +689,14 @@
 src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
+src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
+src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/bitcast.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
@@ -685,6 +707,7 @@
 src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
+src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
@@ -704,6 +727,7 @@
 src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts
 src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts
+src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts
 src/webgpu/shader/validation/functions/alias_analysis.spec.ts
 src/webgpu/shader/validation/functions/restrictions.spec.ts
 src/webgpu/shader/validation/parse/align.spec.ts
@@ -711,10 +735,13 @@
 src/webgpu/shader/validation/parse/binary_ops.spec.ts
 src/webgpu/shader/validation/parse/blankspace.spec.ts
 src/webgpu/shader/validation/parse/break.spec.ts
+src/webgpu/shader/validation/parse/break_if.spec.ts
 src/webgpu/shader/validation/parse/builtin.spec.ts
 src/webgpu/shader/validation/parse/comments.spec.ts
+src/webgpu/shader/validation/parse/compound.spec.ts
 src/webgpu/shader/validation/parse/const.spec.ts
 src/webgpu/shader/validation/parse/const_assert.spec.ts
+src/webgpu/shader/validation/parse/continuing.spec.ts
 src/webgpu/shader/validation/parse/diagnostic.spec.ts
 src/webgpu/shader/validation/parse/discard.spec.ts
 src/webgpu/shader/validation/parse/enable.spec.ts
@@ -722,6 +749,7 @@
 src/webgpu/shader/validation/parse/literal.spec.ts
 src/webgpu/shader/validation/parse/must_use.spec.ts
 src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
+src/webgpu/shader/validation/parse/requires.spec.ts
 src/webgpu/shader/validation/parse/semicolon.spec.ts
 src/webgpu/shader/validation/parse/source.spec.ts
 src/webgpu/shader/validation/parse/unary_ops.spec.ts
@@ -743,6 +771,7 @@
 src/webgpu/shader/validation/types/vector.spec.ts
 src/webgpu/shader/validation/uniformity/uniformity.spec.ts
 src/webgpu/util/copy_to_texture.ts
+src/webgpu/util/texture/color_space_conversions.spec.ts
 src/webgpu/util/texture/texel_data.spec.ts
 src/webgpu/util/texture/texture_ok.spec.ts
 src/webgpu/web_platform/canvas/configure.spec.ts
@@ -764,6 +793,7 @@
 src/webgpu/web_platform/reftests/canvas_composite_alpha.html.ts
 src/webgpu/web_platform/reftests/canvas_image_rendering.html.ts
 src/webgpu/web_platform/reftests/create-pattern-data-url.ts
+src/webgpu/web_platform/reftests/delay_get_texture.html.ts
 src/webgpu/web_platform/reftests/resize_observer.html.ts
 src/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html.ts
 src/webgpu/web_platform/worker/worker.spec.ts
diff --git a/third_party/webrtc b/third_party/webrtc
index aaa123d..fe6178e 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit aaa123debbd106669fc849425b92607edbd5d26f
+Subproject commit fe6178ef7d7f4f25ce93253bdda0b7daae0769a6
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 74bd19f..5ef5b54 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -20817,6 +20817,7 @@
   <int value="56900498" label="OmniboxOneClickUnelide:enabled"/>
   <int value="57255632" label="site-isolation-for-password-sites:disabled"/>
   <int value="57639188" label="SoundContentSetting:disabled"/>
+  <int value="57730593" label="FileSystemProviderCloudFileSystem:enabled"/>
   <int value="57791920" label="MemoryCoordinator:enabled"/>
   <int value="57874550"
       label="AutofillOfferToSaveCardWithSameLastFour:disabled"/>
@@ -24409,6 +24410,7 @@
   <int value="1717987538" label="NTPTilesLowerResolutionFavicons:enabled"/>
   <int value="1718341860" label="NTPButton:enabled"/>
   <int value="1718421370" label="SharingHubLinkToggle:enabled"/>
+  <int value="1719019077" label="FileSystemProviderCloudFileSystem:disabled"/>
   <int value="1719189460" label="EnablePasswordSelection:disabled"/>
   <int value="1719958026" label="QueryTiles:enabled"/>
   <int value="1720399117" label="SkiaGraphite:enabled"/>
diff --git a/tools/metrics/histograms/metadata/cros_ml/enums.xml b/tools/metrics/histograms/metadata/cros_ml/enums.xml
index fc496f7..644f356 100644
--- a/tools/metrics/histograms/metadata/cros_ml/enums.xml
+++ b/tools/metrics/histograms/metadata/cros_ml/enums.xml
@@ -74,6 +74,12 @@
   <int value="0" label="OK"/>
   <int value="1" label="MODEL_INTERPRETATION_ERROR"/>
   <int value="2" label="MEMORY_ALLOCATION_ERROR"/>
+  <int value="3" label="NNAPI_UNAVAILABLE"/>
+  <int value="4" label="NNAPI_USE_ERROR"/>
+  <int value="5" label="GPU_UNAVAILABLE"/>
+  <int value="6" label="GPU_USE_ERROR"/>
+  <int value="7" label="DELEGATE_CONFIG_ERROR"/>
+  <int value="8" label="NOT_FULLY_DELEGABLE"/>
 </enum>
 
 <enum name="MachineLearningServiceDocumentScannerResultEvent">
@@ -121,6 +127,8 @@
   <int value="0" label="OK"/>
   <int value="1" label="MODEL_SPEC_ERROR"/>
   <int value="2" label="LOAD_MODEL_ERROR"/>
+  <int value="3" label="FEATURE_NOT_SUPPORTED_ERROR"/>
+  <int value="4" label="LANGUAGE_NOT_SUPPORTED_ERROR"/>
 </enum>
 
 <enum name="MachineLearningServiceMojoConnectionEvent">
diff --git a/tools/metrics/histograms/metadata/cros_ml/histograms.xml b/tools/metrics/histograms/metadata/cros_ml/histograms.xml
index 44aaaae..cbc2b18 100644
--- a/tools/metrics/histograms/metadata/cros_ml/histograms.xml
+++ b/tools/metrics/histograms/metadata/cros_ml/histograms.xml
@@ -559,6 +559,9 @@
     <variant name="DocumentScanner.LoadModelResult"/>
     <variant name="HandwritingModel.LoadModelResult"/>
     <variant name="HandwritingModel.Recognize"/>
+    <variant name="ImageAnnotator.AnnotateEncodedImage"/>
+    <variant name="ImageAnnotator.AnnotateRawImage"/>
+    <variant name="ImageAnnotator.LoadModelResult"/>
     <variant name="SmartDimModel.CreateGraphExecutorResult"/>
     <variant name="SmartDimModel.ExecuteResult"/>
     <variant name="SmartDimModel.LoadModelResult"/>
@@ -620,6 +623,7 @@
   </summary>
   <token key="RequestName">
     <variant name="DocumentScanner"/>
+    <variant name="ImageAnnotator"/>
     <variant name="SmartDimModel"/>
     <variant name="TestModel"/>
     <variant name="TextClassifier"/>
@@ -639,6 +643,9 @@
     <variant name="DocumentScanner.LoadModelResult"/>
     <variant name="HandwritingModel.LoadModelResult"/>
     <variant name="HandwritingModel.Recognize"/>
+    <variant name="ImageAnnotator.AnnotateEncodedImage"/>
+    <variant name="ImageAnnotator.AnnotateRawImage"/>
+    <variant name="ImageAnnotator.LoadModelResult"/>
     <variant name="SmartDimModel.CreateGraphExecutorResult"/>
     <variant name="SmartDimModel.ExecuteResult"/>
     <variant name="SmartDimModel.LoadModelResult"/>
diff --git a/tools/metrics/histograms/metadata/file/enums.xml b/tools/metrics/histograms/metadata/file/enums.xml
index 7816590a..958f37f 100644
--- a/tools/metrics/histograms/metadata/file/enums.xml
+++ b/tools/metrics/histograms/metadata/file/enums.xml
@@ -455,6 +455,8 @@
   <int value="11" label="Metered connection"/>
   <int value="12" label="Empty alternate URL"/>
   <int value="13" label="Local file waiting for upload"/>
+  <int value="14" label="Disable Drive preference set"/>
+  <int value="15" label="Drive disabled for account type"/>
 </enum>
 
 <enum name="OfficeFileHandler">
diff --git a/tools/metrics/histograms/metadata/input/enums.xml b/tools/metrics/histograms/metadata/input/enums.xml
index b522625..15c458ff 100644
--- a/tools/metrics/histograms/metadata/input/enums.xml
+++ b/tools/metrics/histograms/metadata/input/enums.xml
@@ -227,6 +227,8 @@
   <int value="39" label="PROMO_CARD_IMPRESSION"/>
   <int value="40" label="PROMO_CARD_EXPLICIT_DISMISSAL"/>
   <int value="41" label="CONSENT_SCREEN_IMPRESSION"/>
+  <int value="42" label="TEXT_INSERTION_REQUESTED"/>
+  <int value="43" label="TEXT_QUEUED_FOR_INSERTION"/>
 </enum>
 
 <enum name="IMEGrammarActions">
diff --git a/tools/metrics/histograms/metadata/profile/enums.xml b/tools/metrics/histograms/metadata/profile/enums.xml
index df0b151..4c2fa163 100644
--- a/tools/metrics/histograms/metadata/profile/enums.xml
+++ b/tools/metrics/histograms/metadata/profile/enums.xml
@@ -397,6 +397,7 @@
   <int value="36" label="kWebAppUninstall"/>
   <int value="37" label="kOsIntegrationForceUnregistration"/>
   <int value="38" label="kRemoteDebugging"/>
+  <int value="39" label="kHeadlessCommand"/>
 </enum>
 
 <enum name="ProfileMenuActionableItem">
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index fa71b789..cd99207 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -289,6 +289,17 @@
   </summary>
 </histogram>
 
+<histogram name="Variations.LimitedEntropyTrial.AshSeedIsValid.OnSyncToLacros"
+    enum="BooleanValid" expires_after="2025-01-07">
+  <owner>yulunz@chromium.org</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether the seed value to randomize the limited entropy synthetic trial is
+    valid. This is only emitted from LaCrOS clients when reading the seed value
+    from the Ash Chrome client.
+  </summary>
+</histogram>
+
 <histogram name="Variations.Limits.VariationKeySize.{Size}" units="KiB"
     expires_after="2024-11-01">
   <owner>dalerogerson@google.com</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 75c15de..4ed0202 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v42.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "59e4abe43023a57a761547b00f0fe42414594049",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/9c3cfdb0dacc59243527a5cef0989a3db5c30c69/trace_processor_shell.exe"
+            "hash": "db063bbed4ddda62cdf181242abeb02fa44ce049",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/6821c955bdf682556ec4828a174a9cd916d0a02a/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "46739eeb4b8f2a65a8a0aac57743767e6407f7bb",
             "full_remote_path": "perfetto-luci-artifacts/v42.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "d5f9f140e7d3ae096bba9b9008cb9589bd5912b6",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/74cdba3b28c2a48bb3c5b5cae32812eb6c76a8da/trace_processor_shell"
+            "hash": "4c06710a5566077117ad084bcf92bb8955c2931e",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/6821c955bdf682556ec4828a174a9cd916d0a02a/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "789f24a091d50faafdd5d7c5096bb923d073f138",
             "full_remote_path": "perfetto-luci-artifacts/v42.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "e483814f76f9b7eec355f7b8a0ef6a2d191c25bd",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/74cdba3b28c2a48bb3c5b5cae32812eb6c76a8da/trace_processor_shell"
+            "hash": "c17caab00d94d8c803df62c76920f29c529de4d4",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6821c955bdf682556ec4828a174a9cd916d0a02a/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/file_manager/file_manager/common/js/filtered_volume_manager.ts b/ui/file_manager/file_manager/common/js/filtered_volume_manager.ts
index bf01e623..862aca2 100644
--- a/ui/file_manager/file_manager/common/js/filtered_volume_manager.ts
+++ b/ui/file_manager/file_manager/common/js/filtered_volume_manager.ts
@@ -267,9 +267,7 @@
     // runtime "The event is already being dispatched." error.
     switch (event.type) {
       case 'drive-connection-changed':
-        if (this.isAllowedVolumeType_(VolumeType.DRIVE)) {
-          this.dispatchEvent(new CustomEvent('drive-connection-changed'));
-        }
+        this.dispatchEvent(new CustomEvent('drive-connection-changed'));
         break;
       case 'externally-unmounted':
         if (this.isAllowedVolume(event.detail)) {
@@ -337,7 +335,7 @@
    */
   override getDriveConnectionState():
       chrome.fileManagerPrivate.DriveConnectionState {
-    if (!this.isAllowedVolumeType_(VolumeType.DRIVE) || !this.volumeManager_) {
+    if (!this.volumeManager_) {
       return {
         type: chrome.fileManagerPrivate.DriveConnectionStateType.OFFLINE,
         reason: chrome.fileManagerPrivate.DriveOfflineReason.NO_SERVICE,
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/content_metadata_provider.ts b/ui/file_manager/file_manager/foreground/js/metadata/content_metadata_provider.ts
index c163b63a..4b73d252 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/content_metadata_provider.ts
+++ b/ui/file_manager/file_manager/foreground/js/metadata/content_metadata_provider.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {ImageLoaderClient} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_loader_client.js';
-import {LoadImageRequest, type LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
+import {createForUrl, type LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 
 import {getContentMetadata, getContentMimeType} from '../../../common/js/api.js';
@@ -168,7 +168,7 @@
         (entry as FileEntry)
             .file(
                 file => {
-                  const request = LoadImageRequest.createForUrl(entry.toURL());
+                  const request = createForUrl(entry.toURL());
                   request.maxWidth = THUMBNAIL_MAX_WIDTH;
                   request.maxHeight = THUMBNAIL_MAX_HEIGHT;
                   request.timestamp = file.lastModified;
diff --git a/ui/file_manager/file_manager/foreground/js/quick_view_controller.ts b/ui/file_manager/file_manager/foreground/js/quick_view_controller.ts
index afb18b2..40dec9d 100644
--- a/ui/file_manager/file_manager/foreground/js/quick_view_controller.ts
+++ b/ui/file_manager/file_manager/foreground/js/quick_view_controller.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {ImageLoaderClient} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_loader_client.js';
-import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
+import {createForUrl, LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
 import {assert} from 'chrome://resources/js/assert.js';
 
 import type {VolumeManager} from '../../background/js/volume_manager.js';
@@ -635,7 +635,7 @@
   private async loadThumbnailFromDrive_(url: string, modificationTime?: Date):
       Promise<LoadImageResponse> {
     const client = ImageLoaderClient.getInstance();
-    const request = LoadImageRequest.createForUrl(url);
+    const request = createForUrl(url);
     request.cache = true;
     request.timestamp =
         modificationTime ? modificationTime.valueOf() : undefined;
@@ -652,7 +652,7 @@
       Promise<LoadImageResponse> {
     return new Promise((resolve, reject) => {
       entry.file((file) => {
-        const request = LoadImageRequest.createForUrl(entry.toURL());
+        const request = createForUrl(entry.toURL());
         request.maxWidth = THUMBNAIL_MAX_WIDTH;
         request.maxHeight = THUMBNAIL_MAX_HEIGHT;
         request.timestamp = file.lastModified;
diff --git a/ui/file_manager/file_manager/foreground/js/thumbnail_loader.ts b/ui/file_manager/file_manager/foreground/js/thumbnail_loader.ts
index 103e5deb..af11994 100644
--- a/ui/file_manager/file_manager/foreground/js/thumbnail_loader.ts
+++ b/ui/file_manager/file_manager/foreground/js/thumbnail_loader.ts
@@ -4,7 +4,7 @@
 
 import {ImageLoaderClient} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_loader_client.js';
 import type {ImageTransformParam} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_orientation.js';
-import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
+import {createRequest, LoadImageResponse, LoadImageResponseStatus} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 
 import {getMediaType, isImage, isPDF, isRaw, isVideo} from '../../common/js/file_type.js';
@@ -28,7 +28,7 @@
   /**
    * The image transform from metadata.
    */
-  private transform_: ImageTransformParam|null = null;
+  private transform_?: ImageTransformParam = undefined;
   private loadTarget_: LoadTarget|null = null;
   private thumbnailUrl_: string|null;
   private fallbackUrl_: string|null = null;
@@ -79,7 +79,7 @@
             this.thumbnailUrl_ = this.metadata_.thumbnail.url;
             this.transform_ = (this.metadata_.thumbnail &&
                                this.metadata_.thumbnail.transform) ??
-                null;
+                undefined;
             this.loadTarget_ = LoadTarget.CONTENT_METADATA;
           }
           break;
@@ -100,7 +100,7 @@
             this.thumbnailUrl_ = this.entry_.toURL();
             this.transform_ =
                 (this.metadata_.media && this.metadata_.media.imageTransform) ??
-                null;
+                undefined;
             this.loadTarget_ = LoadTarget.FILE_ENTRY;
           }
           break;
@@ -177,7 +177,7 @@
         this.metadata_.filesystem.modificationTime &&
         this.metadata_.filesystem.modificationTime.getTime();
     this.taskId_ = ImageLoaderClient.loadToImage(
-        LoadImageRequest.createRequest({
+        createRequest({
           url: this.thumbnailUrl_,
           maxWidth: THUMBNAIL_MAX_WIDTH,
           maxHeight: THUMBNAIL_MAX_HEIGHT,
@@ -226,7 +226,7 @@
           this.metadata_.filesystem.modificationTime.getTime();
 
       // Load using ImageLoaderClient.
-      const request = LoadImageRequest.createRequest({
+      const request = createRequest({
         url: requestUrl,
         maxWidth: THUMBNAIL_MAX_WIDTH,
         maxHeight: THUMBNAIL_MAX_HEIGHT,
diff --git a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.ts b/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.ts
index a66bd7a..8b66c90 100644
--- a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.ts
+++ b/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.ts
@@ -4,7 +4,7 @@
 
 import {ImageLoaderClient} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_loader_client.js';
 import type {ImageOrientation, ImageTransformParam} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/image_orientation.js';
-import {LoadImageRequest, LoadImageResponse} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
+import {type LoadImageRequest, LoadImageResponse} from 'chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp/load_image_request.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
 import {MockEntry, MockFileSystem} from '../../common/js/mock_entry.js';
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index adac2dac..60ba336 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -2,7 +2,6 @@
 image_loader_static_js_files = [
   "image_loader/image_loader_util.js",
   "image_loader/image_request_task.js",
-  "image_loader/load_image_request.js",
 ]
 
 image_loader_ts = [
@@ -17,7 +16,7 @@
 
   # "image_loader/image_request_task.ts",
 
-  # "image_loader/load_image_request.ts",
+  "image_loader/load_image_request.ts",
 
   "image_loader/piex_loader.ts",
 
diff --git a/ui/file_manager/image_loader/BUILD.gn b/ui/file_manager/image_loader/BUILD.gn
index 4201e056..9914352 100644
--- a/ui/file_manager/image_loader/BUILD.gn
+++ b/ui/file_manager/image_loader/BUILD.gn
@@ -39,7 +39,7 @@
     "image_loader_util.js",
     "image_orientation.ts",
     "image_request_task.js",
-    "load_image_request.js",
+    "load_image_request.ts",
     "piex_loader.ts",
     "scheduler.ts",
   ]
diff --git a/ui/file_manager/image_loader/cache_unittest.ts b/ui/file_manager/image_loader/cache_unittest.ts
index a506bec..bf4f656 100644
--- a/ui/file_manager/image_loader/cache_unittest.ts
+++ b/ui/file_manager/image_loader/cache_unittest.ts
@@ -4,18 +4,18 @@
 
 import {assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
-import {LoadImageRequest} from './load_image_request.js';
+import {cacheKey} from './load_image_request.js';
 
 
 export function testCreateCacheKey() {
-  const key = LoadImageRequest.cacheKey({url: 'http://example.com/image.jpg'});
+  const key = cacheKey({url: 'http://example.com/image.jpg'});
   assertTrue(!!key);
 }
 
 export function testNotCreateCacheKey() {
-  let key = LoadImageRequest.cacheKey({url: 'data:xxx'});
+  let key = cacheKey({url: 'data:xxx'});
   assertFalse(!!key);
 
-  key = LoadImageRequest.cacheKey({url: 'DaTa:xxx'});
+  key = cacheKey({url: 'DaTa:xxx'});
   assertFalse(!!key);
 }
diff --git a/ui/file_manager/image_loader/image_loader.ts b/ui/file_manager/image_loader/image_loader.ts
index 9a4ce4d..1aadb6b 100644
--- a/ui/file_manager/image_loader/image_loader.ts
+++ b/ui/file_manager/image_loader/image_loader.ts
@@ -5,10 +5,10 @@
 import {assert} from 'chrome://resources/js/assert.js';
 
 import {ImageCache} from './cache.js';
-import {ImageOrientation} from './image_orientation.js';
 import type {ImageTransformParam} from './image_orientation.js';
+import {ImageOrientation} from './image_orientation.js';
 import {ImageRequestTask} from './image_request_task.js';
-import {LoadImageRequest, LoadImageResponse} from './load_image_request.js';
+import {type LoadImageRequest, LoadImageResponse} from './load_image_request.js';
 import {Scheduler} from './scheduler.js';
 
 let instance: ImageLoader|null = null;
diff --git a/ui/file_manager/image_loader/image_loader_client.ts b/ui/file_manager/image_loader/image_loader_client.ts
index dfdca8f..b5ddf937 100644
--- a/ui/file_manager/image_loader/image_loader_client.ts
+++ b/ui/file_manager/image_loader/image_loader_client.ts
@@ -4,17 +4,7 @@
 
 import {LruCache} from 'chrome://file-manager/common/js/lru_cache.js';
 
-import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
-
-// TODO(b/319188711): Share this definition with load_image_request.js when it's
-// converted to TS.
-interface CacheValue {
-  timestamp: number|null;
-  width: number;
-  height: number;
-  ifd: string|null;
-  data: string;
-}
+import {cacheKey, type CacheValue, createCancel, type LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
 
 let instance: ImageLoaderClient|null = null;
 
@@ -68,16 +58,16 @@
     request.url = request.url.replace(CLIENT_SWA_REGEX, IMAGE_LOADER_URL);
 
     // Try to load from cache, if available.
-    const cacheKey = LoadImageRequest.cacheKey(request);
-    if (cacheKey) {
+    const key = cacheKey(request);
+    if (key) {
       if (request.cache) {
         // Load from cache.
-        let cachedValue: CacheValue|null = this.cache_.get(cacheKey);
+        let cachedValue: CacheValue|null = this.cache_.get(key);
         // Check if the image in cache is up to date. If not, then remove it.
         // It relies on comparing `null` equals to `undefined`.
         // eslint-disable-next-line eqeqeq
         if (cachedValue && cachedValue.timestamp != request.timestamp) {
-          this.cache_.remove(cacheKey);
+          this.cache_.remove(key);
           cachedValue = null;
         }
         if (cachedValue && cachedValue.data && cachedValue.width &&
@@ -93,7 +83,7 @@
         }
       } else {
         // Remove from cache.
-        this.cache_.remove(cacheKey);
+        this.cache_.remove(key);
       }
     }
 
@@ -110,11 +100,11 @@
       }
       const result = resultData;
       // Save to cache.
-      if (cacheKey && request.cache) {
+      if (key && request.cache) {
         const value: CacheValue|null =
             LoadImageResponse.cacheValue(result, request.timestamp);
         if (value) {
-          this.cache_.put(cacheKey, value, value.data.length);
+          this.cache_.put(key, value, value.data.length);
         }
       }
       callback(result);
@@ -128,8 +118,7 @@
    * @param taskId Task id returned by ImageLoaderClient.load().
    */
   cancel(taskId: number) {
-    ImageLoaderClient.sendMessage_(
-        LoadImageRequest.createCancel(taskId), (_result) => {});
+    ImageLoaderClient.sendMessage_(createCancel(taskId), (_result) => {});
   }
 
   // Helper functions.
diff --git a/ui/file_manager/image_loader/image_loader_client_unittest.ts b/ui/file_manager/image_loader/image_loader_client_unittest.ts
index bcbabe7..6f617f5 100644
--- a/ui/file_manager/image_loader/image_loader_client_unittest.ts
+++ b/ui/file_manager/image_loader/image_loader_client_unittest.ts
@@ -5,7 +5,7 @@
 import {assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
 import {ImageLoaderClient} from './image_loader_client.js';
-import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
+import {createForUrl, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
 
 /**
  * Lets the client to load URL and returns the local cache (not caches in the
@@ -26,7 +26,7 @@
         {width: 100, height: 100, ifd: null, data: 'ImageData'}));
   };
 
-  const request = LoadImageRequest.createForUrl(url);
+  const request = createForUrl(url);
   request.cache = cache;
 
   return new Promise((fulfill) => {
diff --git a/ui/file_manager/image_loader/image_loader_unittest.js b/ui/file_manager/image_loader/image_loader_unittest.js
index b127c3e2..7010c96 100644
--- a/ui/file_manager/image_loader/image_loader_unittest.js
+++ b/ui/file_manager/image_loader/image_loader_unittest.js
@@ -6,7 +6,6 @@
 
 import {ImageLoaderUtil} from './image_loader_util.js';
 import {ImageOrientation} from './image_orientation.js';
-import {LoadImageRequest} from './load_image_request.js';
 
 
 /**
@@ -17,8 +16,7 @@
  * @return {!ImageLoaderUtil.CopyParameters} Calculated copy parameters.
  */
 function calculateCopyParametersFromOptions(source, options) {
-  return ImageLoaderUtil.calculateCopyParameters(
-      source, /** @type{!LoadImageRequest} */ (options));
+  return ImageLoaderUtil.calculateCopyParameters(source, options);
 }
 
 /**
diff --git a/ui/file_manager/image_loader/image_loader_util.js b/ui/file_manager/image_loader/image_loader_util.js
index 9904b6185..b3b9d12 100644
--- a/ui/file_manager/image_loader/image_loader_util.js
+++ b/ui/file_manager/image_loader/image_loader_util.js
@@ -4,8 +4,6 @@
 
 import {assert} from 'chrome://resources/ash/common/assert.js';
 
-import {LoadImageRequest} from './load_image_request.js';
-
 export function ImageLoaderUtil() {}
 
 /**
@@ -13,7 +11,8 @@
  *
  * @param {number} width Source width.
  * @param {number} height Source height.
- * @param {!LoadImageRequest} request The request, containing resizing options.
+ * @param {!import('./load_image_request.js').LoadImageRequest} request The
+ *     request, containing resizing options.
  * @return {boolean} True if yes, false if not.
  */
 ImageLoaderUtil.shouldProcess = function(width, height, request) {
@@ -45,7 +44,8 @@
  *
  * @param {number} width Source width.
  * @param {number} height Source height.
- * @param {!LoadImageRequest} request The request, containing resizing options.
+ * @param {!import('./load_image_request.js').LoadImageRequest} request The
+ *     request, containing resizing options.
  * @return {!{width: number, height:number}} Dimensions.
  */
 ImageLoaderUtil.resizeDimensions = function(width, height, request) {
@@ -88,7 +88,8 @@
  *
  * @param {HTMLCanvasElement|HTMLImageElement} source Source image or canvas.
  * @param {HTMLCanvasElement} target Target canvas.
- * @param {!LoadImageRequest} request The request, containing resizing options.
+ * @param {!import('./load_image_request.js').LoadImageRequest} request The
+ *     request, containing resizing options.
  */
 ImageLoaderUtil.resizeAndCrop = function(source, target, request) {
   // Calculates copy parameters.
@@ -131,7 +132,8 @@
  * Calculates copy parameters.
  *
  * @param {HTMLCanvasElement|HTMLImageElement} source Source image or canvas.
- * @param {!LoadImageRequest} request The request, containing resizing options.
+ * @param {!import('./load_image_request.js').LoadImageRequest} request The
+ *     request, containing resizing options.
  * @return {!ImageLoaderUtil.CopyParameters} Calculated copy parameters.
  */
 ImageLoaderUtil.calculateCopyParameters = function(source, request) {
diff --git a/ui/file_manager/image_loader/image_orientation.ts b/ui/file_manager/image_loader/image_orientation.ts
index 88cfe03c..351aade 100644
--- a/ui/file_manager/image_loader/image_orientation.ts
+++ b/ui/file_manager/image_loader/image_orientation.ts
@@ -12,7 +12,6 @@
 
 /**
  * Class representing image orientation.
- * @final
  */
 export class ImageOrientation {
   /**
diff --git a/ui/file_manager/image_loader/image_request_task.js b/ui/file_manager/image_loader/image_request_task.js
index 2c21e34..1283073 100644
--- a/ui/file_manager/image_loader/image_request_task.js
+++ b/ui/file_manager/image_loader/image_request_task.js
@@ -8,7 +8,7 @@
 import {ImageCache} from './cache.js';
 import {ImageLoaderUtil} from './image_loader_util.js';
 import {ImageOrientation} from './image_orientation.js';
-import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
+import {cacheKey, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
 import {PiexLoader} from './piex_loader.js';
 
 /**
@@ -19,7 +19,8 @@
   /**
    * @param {string} id Request ID.
    * @param {ImageCache} cache Cache object.
-   * @param {LoadImageRequest} request Request message as a hash array.
+   * @param {import('./load_image_request.js').LoadImageRequest} request Request
+   *     message as a hash array.
    * @param {(a: LoadImageResponse)=> void} callback Response handler.
    */
   constructor(id, cache, request, callback) {
@@ -37,7 +38,7 @@
     this.cache_ = cache;
 
     /**
-     * @type {!LoadImageRequest}
+     * @type {!import('./load_image_request.js').LoadImageRequest}
      * @private
      */
     this.request_ = request;
@@ -193,9 +194,9 @@
    * @private
    */
   loadFromCache_(onSuccess, onFailure) {
-    const cacheKey = LoadImageRequest.cacheKey(this.request_);
+    const key = cacheKey(this.request_);
 
-    if (!cacheKey) {
+    if (!key) {
       // Cache key is not provided for the request.
       onFailure();
       return;
@@ -204,7 +205,7 @@
     if (!this.request_.cache) {
       // Cache is disabled for this request; therefore, remove it from cache
       // if existed.
-      this.cache_.removeImage(cacheKey);
+      this.cache_.removeImage(key);
       onFailure();
       return;
     }
@@ -220,7 +221,7 @@
     // string | null, d: string) => void' is not assignable to parameter of type
     // '(width: number, height: number, ifd?: string | undefined, data?: string
     // | undefined) => void'.
-    this.cache_.loadImage(cacheKey, timestamp, onSuccess, onFailure);
+    this.cache_.loadImage(key, timestamp, onSuccess, onFailure);
   }
 
   /**
@@ -239,15 +240,15 @@
       return;
     }
 
-    const cacheKey = LoadImageRequest.cacheKey(this.request_);
-    if (!cacheKey) {
+    const key = cacheKey(this.request_);
+    if (!key) {
       // Cache key is not provided for the request.
       return;
     }
 
     // @ts-ignore: error TS2345: Argument of type 'string | null' is not
     // assignable to parameter of type 'string | undefined'.
-    this.cache_.saveImage(cacheKey, timestamp, width, height, this.ifd_, data);
+    this.cache_.saveImage(key, timestamp, width, height, this.ifd_, data);
   }
 
   /**
diff --git a/ui/file_manager/image_loader/load_image_request.js b/ui/file_manager/image_loader/load_image_request.js
deleted file mode 100644
index ec4b563c..0000000
--- a/ui/file_manager/image_loader/load_image_request.js
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/ash/common/assert.js';
-
-
-/**
- * Response status.
- *
- * @enum {string}
- */
-export const LoadImageResponseStatus = {
-  SUCCESS: 'success',
-  ERROR: 'error',
-};
-
-/**
- * Structure of the response object passed to the LoadImageRequest callback.
- * All methods must be static since this is passed between isolated contexts.
- *
- * @struct
- */
-export class LoadImageResponse {
-  /**
-   * @param {!LoadImageResponseStatus} status
-   * @param {?number} taskId or null if fulfilled by the client-side cache.
-   * @param {{width:number, height:number, ifd:?string, data:string}=}
-   *    opt_result
-   */
-  constructor(status, taskId, opt_result) {
-    /** @type {!LoadImageResponseStatus} */
-    this.status = status;
-    /** @type {?number} */
-    this.taskId = taskId;
-
-    if (status === LoadImageResponseStatus.ERROR) {
-      return;
-    }
-
-    // Response result defined only when status === SUCCESS.
-    assert(opt_result);
-
-    /** @type {number|undefined} */
-    // @ts-ignore: error TS18048: 'opt_result' is possibly 'undefined'.
-    this.width = opt_result.width;
-    /** @type {number|undefined} */
-    // @ts-ignore: error TS18048: 'opt_result' is possibly 'undefined'.
-    this.height = opt_result.height;
-    /** @type {?string} */
-    // @ts-ignore: error TS18048: 'opt_result' is possibly 'undefined'.
-    this.ifd = opt_result.ifd;
-
-    /**
-     * The (compressed) image data as a data URL.
-     * @type {string|undefined}
-     */
-    // @ts-ignore: error TS18048: 'opt_result' is possibly 'undefined'.
-    this.data = opt_result.data;
-  }
-
-  /**
-   * Returns the cacheable result value for |response|, or null for an error.
-   *
-   * @param {!LoadImageResponse} response Response data from the ImageLoader.
-   * @param {number|undefined} timestamp The request timestamp. If undefined,
-   *        then null is used. Currently this disables any caching in the
-   *        ImageLoader, but disables only *expiration* in the client unless a
-   *        timestamp is presented on a later request.
-   * @return {?{
-   *   timestamp: ?number,
-   *   width: number,
-   *   height: number,
-   *   ifd: ?string,
-   *   data: string
-   * }}
-   */
-  static cacheValue(response, timestamp) {
-    if (!response || response.status === LoadImageResponseStatus.ERROR) {
-      return null;
-    }
-
-    // Response result defined only when status === SUCCESS.
-    assert(response.width);
-    assert(response.height);
-    assert(response.data);
-
-    return {
-      timestamp: timestamp || null,
-      // @ts-ignore: error TS2322: Type 'number | undefined' is not assignable
-      // to type 'number'.
-      width: response.width,
-      // @ts-ignore: error TS2322: Type 'number | undefined' is not assignable
-      // to type 'number'.
-      height: response.height,
-      ifd: response.ifd,
-      // @ts-ignore: error TS2322: Type 'string | undefined' is not assignable
-      // to type 'string'.
-      data: response.data,
-    };
-  }
-}
-
-/**
- * Encapsulates a request to load an image.
- * All methods must be static since this is passed between isolated contexts.
- *
- * @struct
- */
-export class LoadImageRequest {
-  constructor() {
-    // Parts that uniquely identify the request.
-
-    /**
-     * Url of the requested image. Undefined only for cancellations.
-     * @type {string|undefined}
-     */
-    this.url;
-
-    /**
-     * @type{import('./image_orientation.js').ImageOrientation|import('./image_orientation.js').ImageTransformParam|undefined}
-     */
-    this.orientation;
-    /** @type {number|undefined} */
-    this.scale;
-    /** @type {number|undefined} */
-    this.width;
-    /** @type {number|undefined} */
-    this.height;
-    /** @type {number|undefined} */
-    this.maxWidth;
-    /** @type {number|undefined} */
-    this.maxHeight;
-
-    // Parts that control the request flow.
-
-    /** @type {number|undefined} */
-    this.taskId;
-    /** @type {boolean|undefined} */
-    this.cancel;
-    /** @type {boolean|undefined} */
-    this.crop;
-    /** @type {number|undefined} */
-    this.timestamp;
-    /** @type {boolean|undefined} */
-    this.cache;
-    /** @type {number|undefined} */
-    this.priority;
-  }
-
-  /**
-   * Creates a cache key.
-   *
-   * @return {?string} Cache key. It may be null if the cache does not support
-   *     the request. e.g. Data URI.
-   */
-  // @ts-ignore: error TS7006: Parameter 'request' implicitly has an 'any' type.
-  static cacheKey(request) {
-    if (/^data:/i.test(request.url)) {
-      return null;
-    }
-    return JSON.stringify({
-      url: request.url,
-      orientation: request.orientation,
-      scale: request.scale,
-      width: request.width,
-      height: request.height,
-      maxWidth: request.maxWidth,
-      maxHeight: request.maxHeight,
-    });
-  }
-
-  /**
-   * Creates a cancel request.
-   *
-   * @param{number} taskId The task to cancel.
-   * @return {!LoadImageRequest}
-   */
-  static createCancel(taskId) {
-    return /** @type {!LoadImageRequest} */ ({taskId: taskId, cancel: true});
-  }
-
-  /**
-   * Creates a load request from an option map.
-   * Only the timestamp may be undefined.
-   *
-   * @param {{
-   *   url: !string,
-   *   maxWidth: number,
-   *   maxHeight: number,
-   *   cache: boolean,
-   *   priority: number,
-   *   timestamp: (number|undefined),
-   *   orientation: ?import('./image_orientation.js').ImageTransformParam,
-   * }} params Request parameters.
-   * @return {!LoadImageRequest}
-   */
-  static createRequest(params) {
-    return /** @type {!LoadImageRequest} */ (params);
-  }
-
-  /**
-   * Creates a request to load a full-sized image.
-   * Only the timestamp may be undefined.
-   *
-   * @param {{
-   *   url: !string,
-   *   cache: boolean,
-   *   priority: number,
-   *   timestamp: (?number|undefined),
-   * }} params Request parameters.
-   * @return {!LoadImageRequest}
-   */
-  static createFullImageRequest(params) {
-    return /** @type {!LoadImageRequest} */ (params);
-  }
-
-  /**
-   * Creates a load request from a url string. All options are undefined.
-   *
-   * @param {string} url
-   * @return {!LoadImageRequest}
-   */
-  static createForUrl(url) {
-    return /** @type {!LoadImageRequest} */ ({url: url});
-  }
-}
diff --git a/ui/file_manager/image_loader/load_image_request.ts b/ui/file_manager/image_loader/load_image_request.ts
new file mode 100644
index 0000000..7ddc3d5
--- /dev/null
+++ b/ui/file_manager/image_loader/load_image_request.ts
@@ -0,0 +1,180 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.js';
+
+import type {ImageOrientation, ImageTransformParam} from './image_orientation.js';
+
+export interface CacheValue {
+  timestamp: number|null;
+  width: number;
+  height: number;
+  ifd: string|null;
+  data: string;
+}
+
+/**
+ * Response status.
+ */
+export enum LoadImageResponseStatus {
+  SUCCESS = 'success',
+  ERROR = 'error',
+}
+
+/**
+ * Structure of the response object passed to the LoadImageRequest callback.
+ * All methods must be static since this is passed between isolated contexts.
+ */
+export class LoadImageResponse {
+  status: LoadImageResponseStatus;
+  taskId: number|null;
+  width?: number;
+  height?: number;
+  ifd: string|null = null;
+  /** The (compressed) image data as a data URL.  */
+  data?: string;
+
+  /** @param taskId or null if fulfilled by the client-side cache.  */
+  constructor(
+      status: LoadImageResponseStatus, taskId: number|null,
+      result?:
+          {width: number, height: number, ifd: string|null, data: string}) {
+    this.status = status;
+    this.taskId = taskId;
+
+    if (status === LoadImageResponseStatus.ERROR) {
+      return;
+    }
+
+    // Response result defined only when status === SUCCESS.
+    assert(result);
+
+    this.width = result.width;
+    this.height = result.height;
+    this.ifd = result.ifd;
+
+    this.data = result.data;
+  }
+
+  /**
+   * Returns the cacheable result value for |response|, or null for an error.
+   *
+   * @param response Response data from the ImageLoader.
+   * @param timestamp The request timestamp. If undefined, then null is used.
+   *     Currently this disables any caching in the ImageLoader, but disables
+   *     only *expiration* in the client unless a timestamp is presented on a
+   *     later request.
+   */
+  static cacheValue(response: LoadImageResponse, timestamp: number|undefined):
+      CacheValue|null {
+    if (!response || response.status === LoadImageResponseStatus.ERROR) {
+      return null;
+    }
+
+    // Response result defined only when status === SUCCESS.
+    assert(response.width);
+    assert(response.height);
+    assert(response.data);
+
+    return {
+      timestamp: timestamp || null,
+      width: response.width,
+      height: response.height,
+      ifd: response.ifd,
+      data: response.data,
+    };
+  }
+}
+
+/**
+ * Encapsulates a request to load an image.
+ * All methods must be static since this is passed between isolated contexts.
+ */
+export interface LoadImageRequest {
+  // Parts that uniquely identify the request.
+  /** Url of the requested image. Undefined only for cancellations.  */
+  url?: string;
+  orientation?: ImageOrientation|ImageTransformParam;
+  scale?: number;
+  width?: number;
+  height?: number;
+  maxWidth?: number;
+  maxHeight?: number;
+
+  // Parts that control the request flow.
+  taskId?: number;
+  cancel?: boolean;
+  crop?: boolean;
+  timestamp?: number;
+  cache?: boolean;
+  priority?: number;
+}
+
+/**
+ * Creates a cache key.
+ *
+ * @return Cache key. It may be null if the cache does not support the request.
+ *     e.g. Data URI.
+ */
+export function cacheKey(request: LoadImageRequest): string|null {
+  if (/^data:/i.test(request.url ?? '')) {
+    return null;
+  }
+  return JSON.stringify({
+    url: request.url,
+    orientation: request.orientation,
+    scale: request.scale,
+    width: request.width,
+    height: request.height,
+    maxWidth: request.maxWidth,
+    maxHeight: request.maxHeight,
+  });
+}
+
+/**
+ * Creates a cancel request.
+ *
+ * @param taskId The task to cancel.
+ */
+export function createCancel(taskId: number): LoadImageRequest {
+  return {taskId: taskId, cancel: true};
+}
+
+/**
+ * Creates a load request from an option map.
+ * Only the timestamp may be undefined.
+ *
+ * @param params Request parameters.
+ */
+export function createRequest(params: {
+  url: string,
+  maxWidth: number,
+  maxHeight: number,
+  cache: boolean,
+  priority: number,
+  timestamp?: number,
+  orientation?: ImageTransformParam,
+}): LoadImageRequest {
+  return params;
+}
+
+/**
+ * Creates a request to load a full-sized image.
+ * Only the timestamp may be undefined.
+ *
+ * @param params Request parameters.
+ */
+export function createFullImageRequest(params: {
+  url: string,
+  cache: boolean,
+  priority: number,
+  timestamp?: number,
+}): LoadImageRequest {
+  return params;
+}
+
+/** Creates a load request from a url string. All options are undefined. */
+export function createForUrl(url: string): LoadImageRequest {
+  return {url: url};
+}
diff --git a/ui/gfx/linux/fontconfig_util.cc b/ui/gfx/linux/fontconfig_util.cc
index 8aed8ede..cd3589c2 100644
--- a/ui/gfx/linux/fontconfig_util.cc
+++ b/ui/gfx/linux/fontconfig_util.cc
@@ -30,6 +30,13 @@
     FILE_PATH_LITERAL("/usr/share/fonts/google-sans/static");
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// This should match `imageloader::kImageloaderMountBase` from
+// //third_party/cros_system_api/constants/imageloader.h.
+constexpr base::FilePath::CharType kImageloaderMountBase[] =
+    FILE_PATH_LITERAL("/run/imageloader/");
+#endif
+
 // A singleton class to wrap a global font-config configuration. The
 // configuration reference counter is incremented to avoid the deletion of the
 // structure while being used. This class is single-threaded and should only be
@@ -274,4 +281,28 @@
   }
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+bool AddAppFontDir(base::FilePath dir) {
+  if (dir.ReferencesParent()) {
+    // Possible path traversal.
+    return false;
+  }
+  if (!base::FilePath(kImageloaderMountBase).IsParent(dir)) {
+    // Not a DLC path.
+    return false;
+  }
+
+  FcConfig* config = GetGlobalFontConfig();
+
+  // Points to memory owned by `dir`.
+  const FcChar8* dir_fcstring =
+      reinterpret_cast<const FcChar8*>(dir.value().c_str());
+
+  // Adds the folder to the available fonts in the application. Returns
+  // false only when fonts cannot be added due to "allocation failure".
+  // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfigappfontadddir.html
+  return FcConfigAppFontAddDir(config, dir_fcstring);
+}
+#endif
+
 }  // namespace gfx
diff --git a/ui/gfx/linux/fontconfig_util.h b/ui/gfx/linux/fontconfig_util.h
index e17acaa..2caf517 100644
--- a/ui/gfx/linux/fontconfig_util.h
+++ b/ui/gfx/linux/fontconfig_util.h
@@ -47,6 +47,13 @@
 GFX_EXPORT void GetFontRenderParamsFromFcPattern(FcPattern* pattern,
                                                  FontRenderParams* param_out);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Adds a given directory to the available fonts in the application.
+// Directory must start with `/run/imageloader/` (guaranteed by DLC).
+// Returns whether the fonts were added or not.
+GFX_EXPORT bool AddAppFontDir(base::FilePath dir);
+#endif
+
 }  // namespace gfx
 
 #endif  // UI_GFX_LINUX_FONTCONFIG_UTIL_H_
diff --git a/ui/gl/gl_surface_stub.cc b/ui/gl/gl_surface_stub.cc
index 9d2b7dd58..d68f172 100644
--- a/ui/gl/gl_surface_stub.cc
+++ b/ui/gl/gl_surface_stub.cc
@@ -23,7 +23,7 @@
 }
 
 bool GLSurfaceStub::IsOffscreen() {
-  return false;
+  return true;
 }
 
 gfx::SwapResult GLSurfaceStub::SwapBuffers(PresentationCallback callback,
diff --git a/ui/gl/gl_switches.cc b/ui/gl/gl_switches.cc
index 28dd264c..239ff372 100644
--- a/ui/gl/gl_switches.cc
+++ b/ui/gl/gl_switches.cc
@@ -250,7 +250,7 @@
 // Default to using ANGLE's Metal backend.
 BASE_FEATURE(kDefaultANGLEMetal,
              "DefaultANGLEMetal",
-#if BUILDFLAG(IS_IOS) || (BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64))
+#if BUILDFLAG(IS_IOS)
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/ui/gl/init/gl_factory_android.cc b/ui/gl/init/gl_factory_android.cc
index 28b4dd1..b12f931f 100644
--- a/ui/gl/init/gl_factory_android.cc
+++ b/ui/gl/init/gl_factory_android.cc
@@ -95,11 +95,15 @@
   TRACE_EVENT0("gpu", "gl::init::CreateGLContext");
   switch (GetGLImplementation()) {
     case kGLImplementationMockGL:
-      return scoped_refptr<GLContext>(new GLContextStub(share_group));
     case kGLImplementationStubGL: {
       scoped_refptr<GLContextStub> stub_context =
-          new GLContextStub(share_group);
-      stub_context->SetUseStubApi(true);
+          MakeRefCounted<GLContextStub>(share_group);
+      if (GetGLImplementation() == kGLImplementationStubGL) {
+        stub_context->SetUseStubApi(true);
+      }
+      // The stub ctx needs to be initialized so that the gl::GLContext can
+      // store the |compatible_surface|.
+      stub_context->Initialize(compatible_surface, attribs);
       return stub_context;
     }
     case kGLImplementationDisabled:
diff --git a/ui/gl/init/gl_factory_mac.cc b/ui/gl/init/gl_factory_mac.cc
index 64aee97..2726747 100644
--- a/ui/gl/init/gl_factory_mac.cc
+++ b/ui/gl/init/gl_factory_mac.cc
@@ -45,11 +45,15 @@
       return InitializeGLContext(new GLContextEGL(share_group),
                                  compatible_surface, attribs);
     case kGLImplementationMockGL:
-      return new GLContextStub(share_group);
     case kGLImplementationStubGL: {
       scoped_refptr<GLContextStub> stub_context =
-          new GLContextStub(share_group);
-      stub_context->SetUseStubApi(true);
+          base::MakeRefCounted<GLContextStub>(share_group);
+      if (GetGLImplementation() == kGLImplementationStubGL) {
+        stub_context->SetUseStubApi(true);
+      }
+      // The stub ctx needs to be initialized so that the gl::GLContext can
+      // store the |compatible_surface|.
+      stub_context->Initialize(compatible_surface, attribs);
       return stub_context;
     }
     default:
diff --git a/ui/gl/init/gl_factory_ozone.cc b/ui/gl/init/gl_factory_ozone.cc
index 2c02251b..2d10c7c 100644
--- a/ui/gl/init/gl_factory_ozone.cc
+++ b/ui/gl/init/gl_factory_ozone.cc
@@ -42,17 +42,22 @@
                                          attribs);
   }
 
+  scoped_refptr<GLContextStub> context;
   switch (GetGLImplementation()) {
     case kGLImplementationMockGL:
-      return scoped_refptr<GLContext>(new GLContextStub(share_group));
     case kGLImplementationStubGL: {
       scoped_refptr<GLContextStub> stub_context =
-          new GLContextStub(share_group);
-      stub_context->SetUseStubApi(true);
+          base::MakeRefCounted<GLContextStub>(share_group);
+      if (GetGLImplementation() == kGLImplementationStubGL) {
+        stub_context->SetUseStubApi(true);
+      }
+      // The stub ctx needs to be initialized so that the gl::GLContext can
+      // store the |compatible_surface|.
+      stub_context->Initialize(compatible_surface, attribs);
       return stub_context;
     }
     case kGLImplementationDisabled:
-      return nullptr;
+      break;
     default:
       NOTREACHED() << "Expected Mock or Stub, actual:" << GetGLImplementation();
   }
diff --git a/ui/gl/init/gl_factory_win.cc b/ui/gl/init/gl_factory_win.cc
index 3398765..2d6ba9d 100644
--- a/ui/gl/init/gl_factory_win.cc
+++ b/ui/gl/init/gl_factory_win.cc
@@ -46,11 +46,15 @@
       return InitializeGLContext(new GLContextEGL(share_group),
                                  compatible_surface, attribs);
     case kGLImplementationMockGL:
-      return new GLContextStub(share_group);
     case kGLImplementationStubGL: {
       scoped_refptr<GLContextStub> stub_context =
-          new GLContextStub(share_group);
-      stub_context->SetUseStubApi(true);
+          base::MakeRefCounted<GLContextStub>(share_group);
+      if (GetGLImplementation() == kGLImplementationStubGL) {
+        stub_context->SetUseStubApi(true);
+      }
+      // The stub ctx needs to be initialized so that the gl::GLContext can
+      // store the |compatible_surface|.
+      stub_context->Initialize(compatible_surface, attribs);
       return stub_context;
     }
     case kGLImplementationDisabled:
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index 1aad752..593009c1 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -1321,6 +1321,7 @@
                                 wl::EnableAuraShellProtocol::kEnabled},
            wl::ServerConfig{
                .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-               .use_aura_output_manager = true}));
+               .aura_output_manager_protocol =
+                   wl::AuraOutputManagerProtocol::kEnabledV1}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_output_unittest.cc b/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
index 0448397..4aeee57 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output_unittest.cc
@@ -144,7 +144,8 @@
   WaylandOutputWithAuraOutputManagerTest()
       : WaylandTestSimple(wl::ServerConfig{
             .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-            .use_aura_output_manager = true}) {}
+            .aura_output_manager_protocol =
+                wl::AuraOutputManagerProtocol::kEnabledV1}) {}
 
   WaylandOutputManager* wayland_output_manager() {
     auto* wayland_output_manager = connection_->wayland_output_manager();
diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
index 3b4b9bb9..1c06317 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
@@ -1421,7 +1421,8 @@
                                 wl::EnableAuraShellProtocol::kEnabled},
            wl::ServerConfig{
                .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-               .use_aura_output_manager = true}));
+               .aura_output_manager_protocol =
+                   wl::AuraOutputManagerProtocol::kEnabledV1}));
 
 INSTANTIATE_TEST_SUITE_P(
     XdgVersionStableTest,
@@ -1430,7 +1431,8 @@
                                 wl::EnableAuraShellProtocol::kEnabled},
            wl::ServerConfig{
                .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-               .use_aura_output_manager = true}));
+               .aura_output_manager_protocol =
+                   wl::AuraOutputManagerProtocol::kEnabledV1}));
 
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
diff --git a/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc b/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
index d2f197c..df8f8b32 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_surface_unittest.cc
@@ -59,7 +59,8 @@
             .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled},
         wl::ServerConfig{
             .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-            .use_aura_output_manager = true}));
+            .aura_output_manager_protocol =
+                wl::AuraOutputManagerProtocol::kEnabledV1}));
 
 }  // namespace
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index 1d4a1daf..3964d3f 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -1888,6 +1888,7 @@
     WaylandWindowDragControllerTest,
     Values(wl::ServerConfig{
         .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-        .use_aura_output_manager = true}));
+        .aura_output_manager_protocol =
+            wl::AuraOutputManagerProtocol::kEnabledV1}));
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index f89eb1d..f3458e2 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -5238,7 +5238,8 @@
                                 wl::EnableAuraShellProtocol::kEnabled},
            wl::ServerConfig{
                .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-               .use_aura_output_manager = true}));
+               .aura_output_manager_protocol =
+                   wl::AuraOutputManagerProtocol::kEnabledV1}));
 #endif
 
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
@@ -5253,7 +5254,8 @@
                                 wl::EnableAuraShellProtocol::kEnabled},
            wl::ServerConfig{
                .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-               .use_aura_output_manager = true}));
+               .aura_output_manager_protocol =
+                   wl::AuraOutputManagerProtocol::kEnabledV1}));
 #endif
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_output_manager_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_output_manager_unittest.cc
index 98ef0ead..af5f7bded 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_output_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_output_manager_unittest.cc
@@ -50,7 +50,8 @@
   WaylandZAuraOutputManagerTest()
       : WaylandTestSimple(wl::ServerConfig{
             .enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled,
-            .use_aura_output_manager = true}) {}
+            .aura_output_manager_protocol =
+                wl::AuraOutputManagerProtocol::kEnabledV1}) {}
 
  protected:
   // Sends sample metrics to the primary output configured for this fixture.
@@ -273,14 +274,14 @@
 
   // Activate the secondary output.
   PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
-    server->zaura_output_manager()->SendActivated(secondary_output->resource());
+    server->zaura_output_manager()->SendActivated(secondary_output);
   });
   EXPECT_EQ(secondary_id,
             display::Screen::GetScreen()->GetDisplayForNewWindows().id());
 
   // Activate the primary output.
   PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
-    server->zaura_output_manager()->SendActivated(primary_output->resource());
+    server->zaura_output_manager()->SendActivated(primary_output);
   });
   EXPECT_EQ(primary_id,
             display::Screen::GetScreen()->GetDisplayForNewWindows().id());
diff --git a/ui/ozone/platform/wayland/test/test_output.cc b/ui/ozone/platform/wayland/test/test_output.cc
index 743ee11c..e26271f 100644
--- a/ui/ozone/platform/wayland/test/test_output.cc
+++ b/ui/ozone/platform/wayland/test/test_output.cc
@@ -94,7 +94,7 @@
 }
 
 void TestOutput::Flush() {
-  flush_metrics_callback_.Run(resource(), metrics_);
+  flush_metrics_callback_.Run(this, metrics_);
 
   constexpr char kUnknownMake[] = "unknown_make";
   constexpr char kUnknownModel[] = "unknown_model";
diff --git a/ui/ozone/platform/wayland/test/test_output.h b/ui/ozone/platform/wayland/test/test_output.h
index 8dfada2..879eb405 100644
--- a/ui/ozone/platform/wayland/test/test_output.h
+++ b/ui/ozone/platform/wayland/test/test_output.h
@@ -29,10 +29,9 @@
  public:
   // A callback that allows clients to respond to a Flush() of a given
   // TestOutput's metrics_. This is called immediately before Flush() sends
-  // metrics events to clients. The output_resource is the wl_resource
-  // associated with this output.
+  // metrics events to clients.
   using FlushMetricsCallback =
-      base::RepeatingCallback<void(wl_resource* output_resource,
+      base::RepeatingCallback<void(TestOutput* TestOutput,
                                    const TestOutputMetrics& metrics)>;
 
   explicit TestOutput(FlushMetricsCallback flush_metrics_callback);
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
index f8e4dfc5..c94f23d 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
@@ -110,7 +110,8 @@
     return false;
 
   if (config_.enable_aura_shell == EnableAuraShellProtocol::kEnabled) {
-    if (config_.use_aura_output_manager) {
+    if (config_.aura_output_manager_protocol ==
+        AuraOutputManagerProtocol::kEnabledV1) {
       // zaura_output_manager should be initialized before any wl_output
       // globals.
       if (!zaura_output_manager_.Initialize(display_.get())) {
@@ -234,10 +235,10 @@
 }
 
 void TestWaylandServerThread::OnTestOutputMetricsFlush(
-    wl_resource* output_resource,
+    TestOutput* test_output,
     const TestOutputMetrics& metrics) {
   if (zaura_output_manager_.resource()) {
-    zaura_output_manager_.SendOutputMetrics(output_resource, metrics);
+    zaura_output_manager_.SendOutputMetrics(test_output, metrics);
   }
 }
 
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index ece72a3..00846d7 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -55,6 +55,7 @@
 enum class PrimarySelectionProtocol { kNone, kGtk, kZwp };
 enum class ShouldUseExplicitSynchronizationProtocol { kNone, kUse };
 enum class EnableAuraShellProtocol { kEnabled, kDisabled };
+enum class AuraOutputManagerProtocol { kDisabled, kEnabledV1 };
 
 struct ServerConfig {
   TestZcrTextInputExtensionV1::Version text_input_extension_version =
@@ -68,7 +69,8 @@
       EnableAuraShellProtocol::kDisabled;
   bool surface_submission_in_pixel_coordinates = true;
   bool supports_viewporter_surface_scaling = false;
-  bool use_aura_output_manager = false;
+  AuraOutputManagerProtocol aura_output_manager_protocol =
+      AuraOutputManagerProtocol::kDisabled;
 };
 
 class TestWaylandServerThread;
@@ -138,10 +140,10 @@
     return output_ptr;
   }
 
-  // Called when the Flush() is called for a TestOutput associated with
-  // `output_resource`. When called sends the corresponding events for the
-  // `metrics` to clients of the zaura_output_manager.
-  void OnTestOutputMetricsFlush(wl_resource* output_resource,
+  // Called when the Flush() is called for a `test_output`. When called sends
+  // the corresponding events for the `metrics` to clients of the
+  // aura output manager.
+  void OnTestOutputMetricsFlush(TestOutput* test_output,
                                 const TestOutputMetrics& metrics);
 
   TestDataDeviceManager* data_device_manager() { return &data_device_manager_; }
diff --git a/ui/ozone/platform/wayland/test/test_zaura_output_manager.cc b/ui/ozone/platform/wayland/test/test_zaura_output_manager.cc
index 944268c..e5faeec6 100644
--- a/ui/ozone/platform/wayland/test/test_zaura_output_manager.cc
+++ b/ui/ozone/platform/wayland/test/test_zaura_output_manager.cc
@@ -8,6 +8,7 @@
 
 #include "base/bit_cast.h"
 #include "ui/base/wayland/wayland_display_util.h"
+#include "ui/ozone/platform/wayland/test/test_output.h"
 #include "ui/ozone/platform/wayland/test/test_output_metrics.h"
 
 namespace wl {
@@ -25,8 +26,10 @@
 TestZAuraOutputManager::~TestZAuraOutputManager() = default;
 
 void TestZAuraOutputManager::SendOutputMetrics(
-    wl_resource* output_resource,
+    TestOutput* test_output,
     const TestOutputMetrics& metrics) {
+  wl_resource* output_resource = test_output->resource();
+
   const auto& physical_size = metrics.wl_physical_size;
   zaura_output_manager_send_physical_size(resource(), output_resource,
                                           physical_size.width(),
@@ -63,7 +66,8 @@
   zaura_output_manager_send_done(resource(), output_resource);
 }
 
-void TestZAuraOutputManager::SendActivated(wl_resource* output_resource) {
+void TestZAuraOutputManager::SendActivated(TestOutput* test_output) {
+  wl_resource* output_resource = test_output->resource();
   zaura_output_manager_send_activated(resource(), output_resource);
 }
 
diff --git a/ui/ozone/platform/wayland/test/test_zaura_output_manager.h b/ui/ozone/platform/wayland/test/test_zaura_output_manager.h
index 800fa8d2..95e7a1a 100644
--- a/ui/ozone/platform/wayland/test/test_zaura_output_manager.h
+++ b/ui/ozone/platform/wayland/test/test_zaura_output_manager.h
@@ -7,9 +7,9 @@
 
 #include "ui/ozone/platform/wayland/test/global_object.h"
 
-struct wl_resource;
-
 namespace wl {
+
+class TestOutput;
 struct TestOutputMetrics;
 
 class TestZAuraOutputManager : public GlobalObject {
@@ -20,11 +20,11 @@
   ~TestZAuraOutputManager() override;
 
   // Propagates events for metrics to bound clients for the output.
-  void SendOutputMetrics(wl_resource* output_resource,
+  void SendOutputMetrics(TestOutput* test_output,
                          const TestOutputMetrics& metrics);
 
   // Sends the activated event for the given output.
-  void SendActivated(wl_resource* output_resource);
+  void SendActivated(TestOutput* test_output);
 };
 
 }  // namespace wl
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index c479b90..a4aa2a0 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -285,8 +285,10 @@
                      : ScrollWithLayers::kDisabled) {}
 
 ScrollView::ScrollView(ScrollWithLayers scroll_with_layers)
-    : horiz_sb_(AddChildView(PlatformStyle::CreateScrollBar(true))),
-      vert_sb_(AddChildView(PlatformStyle::CreateScrollBar(false))),
+    : horiz_sb_(AddChildView(
+          PlatformStyle::CreateScrollBar(ScrollBar::Orientation::kHorizontal))),
+      vert_sb_(AddChildView(
+          PlatformStyle::CreateScrollBar(ScrollBar::Orientation::kVertical))),
       corner_view_(std::make_unique<ScrollCornerView>()),
       scroll_with_layers_enabled_(scroll_with_layers ==
                                   ScrollWithLayers::kEnabled) {
@@ -1029,7 +1031,8 @@
 int ScrollView::GetScrollIncrement(ScrollBar* source,
                                    bool is_page,
                                    bool is_positive) {
-  bool is_horizontal = source->IsHorizontal();
+  bool is_horizontal =
+      source->GetOrientation() == ScrollBar::Orientation::kHorizontal;
   if (is_page) {
     return is_horizontal ? contents_viewport_->width()
                          : contents_viewport_->height();
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index ae38875..26112f2 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -251,8 +251,8 @@
   METADATA_HEADER(TestScrollBar, ScrollBar)
 
  public:
-  TestScrollBar(bool horizontal, bool overlaps_content, int thickness)
-      : ScrollBar(horizontal),
+  TestScrollBar(Orientation orientation, bool overlaps_content, int thickness)
+      : ScrollBar(orientation),
         overlaps_content_(overlaps_content),
         thickness_(thickness) {
     SetThumb(new TestScrollBarThumb(this));
@@ -2137,11 +2137,13 @@
 
   constexpr int kThickness = 1;
   // Assume horizontal scroll bar is the default and is overlapping.
-  scroll_view_->SetHorizontalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/true, /*overlaps_content=*/true, kThickness));
+  scroll_view_->SetHorizontalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kHorizontal,
+                                      /*overlaps_content=*/true, kThickness));
   // Assume vertical scroll bar is custom and it we want it to not overlap.
-  scroll_view_->SetVerticalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/false, /*overlaps_content=*/false, kThickness));
+  scroll_view_->SetVerticalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kVertical,
+                                      /*overlaps_content=*/false, kThickness));
 
   // Also, let's turn off horizontal scroll bar.
   scroll_view_->SetHorizontalScrollBarMode(
@@ -2163,11 +2165,13 @@
 
   constexpr int kThickness = 1;
   // Assume horizontal scroll bar is the default and is overlapping.
-  scroll_view_->SetHorizontalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/true, /*overlaps_content=*/true, kThickness));
+  scroll_view_->SetHorizontalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kHorizontal,
+                                      /*overlaps_content=*/true, kThickness));
   // Assume vertical scroll bar is custom and it we want it to not overlap.
-  scroll_view_->SetVerticalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/false, /*overlaps_content=*/false, kThickness));
+  scroll_view_->SetVerticalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kVertical,
+                                      /*overlaps_content=*/false, kThickness));
 
   // Also, let's turn off horizontal scroll bar.
   scroll_view_->SetHorizontalScrollBarMode(
@@ -2189,11 +2193,13 @@
 
   constexpr int kThickness = 1;
   // Assume horizontal scroll bar is custom and it we want it to not overlap.
-  scroll_view_->SetHorizontalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/true, /*overlaps_content=*/false, kThickness));
+  scroll_view_->SetHorizontalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kHorizontal,
+                                      /*overlaps_content=*/false, kThickness));
   // Assume vertical scroll bar is the default and is overlapping.
-  scroll_view_->SetVerticalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/false, /*overlaps_content=*/true, kThickness));
+  scroll_view_->SetVerticalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kVertical,
+                                      /*overlaps_content=*/true, kThickness));
 
   // Also, let's turn off horizontal scroll bar.
   scroll_view_->SetVerticalScrollBarMode(ScrollView::ScrollBarMode::kDisabled);
@@ -2214,11 +2220,13 @@
 
   constexpr int kThickness = 1;
   // Assume horizontal scroll bar is custom and it we want it to not overlap.
-  scroll_view_->SetHorizontalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/true, /*overlaps_content=*/false, kThickness));
+  scroll_view_->SetHorizontalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kHorizontal,
+                                      /*overlaps_content=*/false, kThickness));
   // Assume vertical scroll bar is the default and is overlapping.
-  scroll_view_->SetVerticalScrollBar(std::make_unique<TestScrollBar>(
-      /*horizontal=*/false, /*overlaps_content=*/true, kThickness));
+  scroll_view_->SetVerticalScrollBar(
+      std::make_unique<TestScrollBar>(ScrollBar::Orientation::kVertical,
+                                      /*overlaps_content=*/true, kThickness));
 
   // Also, let's turn off horizontal scroll bar.
   scroll_view_->SetVerticalScrollBarMode(
diff --git a/ui/views/controls/scrollbar/base_scroll_bar_thumb.cc b/ui/views/controls/scrollbar/base_scroll_bar_thumb.cc
index 2e6749d..5aa83dc 100644
--- a/ui/views/controls/scrollbar/base_scroll_bar_thumb.cc
+++ b/ui/views/controls/scrollbar/base_scroll_bar_thumb.cc
@@ -136,7 +136,7 @@
 }
 
 bool BaseScrollBarThumb::IsHorizontal() const {
-  return scroll_bar_->IsHorizontal();
+  return scroll_bar_->GetOrientation() == ScrollBar::Orientation::kHorizontal;
 }
 
 BEGIN_METADATA(BaseScrollBarThumb)
diff --git a/ui/views/controls/scrollbar/cocoa_scroll_bar.h b/ui/views/controls/scrollbar/cocoa_scroll_bar.h
index c264ff8..0481be8d 100644
--- a/ui/views/controls/scrollbar/cocoa_scroll_bar.h
+++ b/ui/views/controls/scrollbar/cocoa_scroll_bar.h
@@ -24,7 +24,7 @@
   METADATA_HEADER(CocoaScrollBar, ScrollBar)
 
  public:
-  explicit CocoaScrollBar(bool horizontal);
+  explicit CocoaScrollBar(ScrollBar::Orientation orientation);
 
   CocoaScrollBar(const CocoaScrollBar&) = delete;
   CocoaScrollBar& operator=(const CocoaScrollBar&) = delete;
diff --git a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
index c07b45f..99fb29c0 100644
--- a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
+++ b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
@@ -144,8 +144,8 @@
 //////////////////////////////////////////////////////////////////
 // CocoaScrollBar class
 
-CocoaScrollBar::CocoaScrollBar(bool horizontal)
-    : ScrollBar(horizontal),
+CocoaScrollBar::CocoaScrollBar(ScrollBar::Orientation orientation)
+    : ScrollBar(orientation),
       hide_scrollbar_timer_(FROM_HERE,
                             base::Milliseconds(500),
                             base::BindRepeating(&CocoaScrollBar::HideScrollbar,
@@ -192,7 +192,7 @@
   // The length of the thumb is set by ScrollBar::Update().
   gfx::Rect thumb_bounds(GetThumb()->bounds());
   gfx::Rect track_bounds(GetTrackBounds());
-  if (IsHorizontal()) {
+  if (GetOrientation() == Orientation::kHorizontal) {
     GetThumb()->SetBounds(thumb_bounds.x(),
                           track_bounds.y(),
                           thumb_bounds.width(),
@@ -327,8 +327,10 @@
   // hide timer check because Update() is called asynchronously, after event
   // processing. So when |event| is the first event in a particular direction
   // the hide timer will not have started.
-  if ((IsHorizontal() ? event.x_offset() : event.y_offset()) != 0)
+  if ((GetOrientation() == Orientation::kHorizontal ? event.x_offset()
+                                                    : event.y_offset()) != 0) {
     return;
+  }
 
   // Otherwise, scrolling has started, but not in this scroller direction. If
   // already faded out, don't start another fade animation since that would
@@ -415,7 +417,7 @@
 
 ui::NativeTheme::ExtraParams CocoaScrollBar::GetPainterParams() const {
   ui::NativeTheme::ScrollbarExtraParams scrollbar;
-  if (IsHorizontal()) {
+  if (GetOrientation() == Orientation::kHorizontal) {
     scrollbar.orientation = ui::NativeTheme::ScrollbarOrientation::kHorizontal;
   } else if (base::i18n::IsRTL()) {
     scrollbar.orientation =
@@ -479,10 +481,11 @@
 
 void CocoaScrollBar::UpdateScrollbarThickness() {
   int thickness = ScrollbarThickness();
-  if (IsHorizontal())
+  if (GetOrientation() == Orientation::kHorizontal) {
     SetBounds(x(), bounds().bottom() - thickness, width(), thickness);
-  else
+  } else {
     SetBounds(bounds().right() - thickness, y(), thickness, height());
+  }
 }
 
 void CocoaScrollBar::ResetOverlayScrollbar() {
diff --git a/ui/views/controls/scrollbar/overlay_scroll_bar.cc b/ui/views/controls/scrollbar/overlay_scroll_bar.cc
index 37dee14..909b643 100644
--- a/ui/views/controls/scrollbar/overlay_scroll_bar.cc
+++ b/ui/views/controls/scrollbar/overlay_scroll_bar.cc
@@ -136,7 +136,8 @@
 BEGIN_METADATA(OverlayScrollBar, Thumb, BaseScrollBarThumb)
 END_METADATA
 
-OverlayScrollBar::OverlayScrollBar(bool horizontal) : ScrollBar(horizontal) {
+OverlayScrollBar::OverlayScrollBar(Orientation orientation)
+    : ScrollBar(orientation) {
   SetNotifyEnterExitOnChild(true);
   SetPaintToLayer();
   layer()->SetMasksToBounds(true);
@@ -154,8 +155,9 @@
 OverlayScrollBar::~OverlayScrollBar() = default;
 
 gfx::Insets OverlayScrollBar::GetInsets() const {
-  return IsHorizontal() ? gfx::Insets::TLBR(-kThumbHoverOffset, 0, 0, 0)
-                        : gfx::Insets::TLBR(0, -kThumbHoverOffset, 0, 0);
+  return GetOrientation() == Orientation::kHorizontal
+             ? gfx::Insets::TLBR(-kThumbHoverOffset, 0, 0, 0)
+             : gfx::Insets::TLBR(0, -kThumbHoverOffset, 0, 0);
 }
 
 void OverlayScrollBar::OnMouseEntered(const ui::MouseEvent& event) {
diff --git a/ui/views/controls/scrollbar/overlay_scroll_bar.h b/ui/views/controls/scrollbar/overlay_scroll_bar.h
index 893a182..474ed7f 100644
--- a/ui/views/controls/scrollbar/overlay_scroll_bar.h
+++ b/ui/views/controls/scrollbar/overlay_scroll_bar.h
@@ -17,7 +17,7 @@
   METADATA_HEADER(OverlayScrollBar, ScrollBar)
 
  public:
-  explicit OverlayScrollBar(bool horizontal);
+  explicit OverlayScrollBar(Orientation orientation);
 
   OverlayScrollBar(const OverlayScrollBar&) = delete;
   OverlayScrollBar& operator=(const OverlayScrollBar&) = delete;
diff --git a/ui/views/controls/scrollbar/scroll_bar.cc b/ui/views/controls/scrollbar/scroll_bar.cc
index 9ecb2515..00ec24c 100644
--- a/ui/views/controls/scrollbar/scroll_bar.cc
+++ b/ui/views/controls/scrollbar/scroll_bar.cc
@@ -34,8 +34,8 @@
 
 ScrollBar::~ScrollBar() = default;
 
-bool ScrollBar::IsHorizontal() const {
-  return is_horiz_;
+ScrollBar::Orientation ScrollBar::GetOrientation() const {
+  return orientation_;
 }
 
 void ScrollBar::SetThumb(BaseScrollBarThumb* thumb) {
@@ -170,7 +170,7 @@
 
     float scroll_amount_f;
     int scroll_amount;
-    if (IsHorizontal()) {
+    if (GetOrientation() == Orientation::kHorizontal) {
       scroll_amount_f = event->details().scroll_x() - roundoff_error_.x();
       scroll_amount = base::ClampRound(scroll_amount_f);
       roundoff_error_.set_x(scroll_amount - scroll_amount_f);
@@ -187,8 +187,12 @@
   if (event->type() == ui::ET_SCROLL_FLING_START) {
     scroll_status_ = ScrollStatus::kScrollInEnding;
     GetOrCreateScrollAnimator()->Start(
-        IsHorizontal() ? event->details().velocity_x() : 0.f,
-        IsHorizontal() ? 0.f : event->details().velocity_y());
+        GetOrientation() == Orientation::kHorizontal
+            ? event->details().velocity_x()
+            : 0.f,
+        GetOrientation() == Orientation::kHorizontal
+            ? 0.f
+            : event->details().velocity_y());
     event->SetHandled();
   }
 }
@@ -197,8 +201,8 @@
 // ScrollBar, ScrollDelegate implementation:
 
 bool ScrollBar::OnScroll(float dx, float dy) {
-  return IsHorizontal() ? ScrollByContentsOffset(dx)
-                        : ScrollByContentsOffset(dy);
+  return ScrollByContentsOffset(
+      GetOrientation() == Orientation::kHorizontal ? dx : dy);
 }
 
 void ScrollBar::OnFlingScrollEnded() {
@@ -226,7 +230,8 @@
   gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen();
   gfx::Point temp_pt(p.x() - widget_bounds.x(), p.y() - widget_bounds.y());
   View::ConvertPointFromWidget(this, &temp_pt);
-  context_menu_mouse_position_ = IsHorizontal() ? temp_pt.x() : temp_pt.y();
+  context_menu_mouse_position_ =
+      GetOrientation() == Orientation::kHorizontal ? temp_pt.x() : temp_pt.y();
 
   if (!menu_model_) {
     menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
@@ -235,26 +240,30 @@
     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
     menu_model_->AddItemWithStringId(
         ScrollBarContextMenuCommand_ScrollStart,
-        IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFTEDGE
-                       : IDS_APP_SCROLLBAR_CXMENU_SCROLLHOME);
+        GetOrientation() == Orientation::kHorizontal
+            ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFTEDGE
+            : IDS_APP_SCROLLBAR_CXMENU_SCROLLHOME);
     menu_model_->AddItemWithStringId(
         ScrollBarContextMenuCommand_ScrollEnd,
-        IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHTEDGE
-                       : IDS_APP_SCROLLBAR_CXMENU_SCROLLEND);
+        GetOrientation() == Orientation::kHorizontal
+            ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHTEDGE
+            : IDS_APP_SCROLLBAR_CXMENU_SCROLLEND);
     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
     menu_model_->AddItemWithStringId(ScrollBarContextMenuCommand_ScrollPageUp,
                                      IDS_APP_SCROLLBAR_CXMENU_SCROLLPAGEUP);
     menu_model_->AddItemWithStringId(ScrollBarContextMenuCommand_ScrollPageDown,
                                      IDS_APP_SCROLLBAR_CXMENU_SCROLLPAGEDOWN);
     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
-    menu_model_->AddItemWithStringId(ScrollBarContextMenuCommand_ScrollPrev,
-                                     IsHorizontal()
-                                         ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFT
-                                         : IDS_APP_SCROLLBAR_CXMENU_SCROLLUP);
-    menu_model_->AddItemWithStringId(ScrollBarContextMenuCommand_ScrollNext,
-                                     IsHorizontal()
-                                         ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHT
-                                         : IDS_APP_SCROLLBAR_CXMENU_SCROLLDOWN);
+    menu_model_->AddItemWithStringId(
+        ScrollBarContextMenuCommand_ScrollPrev,
+        GetOrientation() == Orientation::kHorizontal
+            ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFT
+            : IDS_APP_SCROLLBAR_CXMENU_SCROLLUP);
+    menu_model_->AddItemWithStringId(
+        ScrollBarContextMenuCommand_ScrollNext,
+        GetOrientation() == Orientation::kHorizontal
+            ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHT
+            : IDS_APP_SCROLLBAR_CXMENU_SCROLLDOWN);
   }
   menu_runner_ = std::make_unique<MenuRunner>(
       menu_model_.get(),
@@ -270,7 +279,7 @@
   switch (id) {
     case ScrollBarContextMenuCommand_ScrollPageUp:
     case ScrollBarContextMenuCommand_ScrollPageDown:
-      return !IsHorizontal();
+      return GetOrientation() == Orientation::kVertical;
   }
   return true;
 }
@@ -390,8 +399,8 @@
   GetOrCreateScrollAnimator()->set_velocity_multiplier(fling_multiplier_);
 }
 
-ScrollBar::ScrollBar(bool is_horiz)
-    : is_horiz_(is_horiz),
+ScrollBar::ScrollBar(Orientation orientation)
+    : orientation_(orientation),
       repeater_(base::BindRepeating(&ScrollBar::TrackClicked,
                                     base::Unretained(this))) {
   set_context_menu_controller(this);
@@ -415,7 +424,7 @@
 
 void ScrollBar::ProcessPressEvent(const ui::LocatedEvent& event) {
   gfx::Rect thumb_bounds = thumb_->bounds();
-  if (IsHorizontal()) {
+  if (GetOrientation() == Orientation::kHorizontal) {
     if (GetMirroredXInView(event.x()) < thumb_bounds.x()) {
       last_scroll_amount_ = ScrollAmount::kPrevPage;
     } else if (GetMirroredXInView(event.x()) > thumb_bounds.right()) {
@@ -443,7 +452,8 @@
 
 int ScrollBar::GetTrackSize() const {
   gfx::Rect track_bounds = GetTrackBounds();
-  return IsHorizontal() ? track_bounds.width() : track_bounds.height();
+  return GetOrientation() == Orientation::kHorizontal ? track_bounds.width()
+                                                      : track_bounds.height();
 }
 
 int ScrollBar::CalculateThumbPosition(int contents_scroll_offset) const {
@@ -484,9 +494,11 @@
 ScrollBar::ScrollAmount ScrollBar::DetermineScrollAmountByKeyCode(
     const ui::KeyboardCode& keycode) const {
   // Reject arrows that don't match the scrollbar orientation.
-  if (IsHorizontal() ? (keycode == ui::VKEY_UP || keycode == ui::VKEY_DOWN)
-                     : (keycode == ui::VKEY_LEFT || keycode == ui::VKEY_RIGHT))
+  if (GetOrientation() == Orientation::kHorizontal
+          ? (keycode == ui::VKEY_UP || keycode == ui::VKEY_DOWN)
+          : (keycode == ui::VKEY_LEFT || keycode == ui::VKEY_RIGHT)) {
     return ScrollAmount::kNone;
+  }
 
   static const base::NoDestructor<
       base::flat_map<ui::KeyboardCode, ScrollAmount>>
diff --git a/ui/views/controls/scrollbar/scroll_bar.h b/ui/views/controls/scrollbar/scroll_bar.h
index c9a3f0d..9a53a5e 100644
--- a/ui/views/controls/scrollbar/scroll_bar.h
+++ b/ui/views/controls/scrollbar/scroll_bar.h
@@ -93,13 +93,18 @@
     kNextPage,
   };
 
+  // Whether the scrollbar is horizontal or vertical.
+  enum class Orientation : bool {
+    kHorizontal,
+    kVertical,
+  };
+
   ScrollBar(const ScrollBar&) = delete;
   ScrollBar& operator=(const ScrollBar&) = delete;
 
   ~ScrollBar() override;
 
-  // Returns whether this scrollbar is horizontal.
-  bool IsHorizontal() const;
+  Orientation GetOrientation() const;
 
   void set_controller(ScrollBarController* controller) {
     controller_ = controller;
@@ -187,7 +192,7 @@
   // Create new scrollbar, either horizontal or vertical. These are protected
   // since you need to be creating either a NativeScrollBar or a
   // ImageScrollBar.
-  explicit ScrollBar(bool is_horiz);
+  explicit ScrollBar(Orientation orientation);
 
   BaseScrollBarThumb* GetThumb() const;
 
@@ -264,7 +269,7 @@
   // was invoked.
   int context_menu_mouse_position_ = 0;
 
-  const bool is_horiz_;
+  const Orientation orientation_;
 
   raw_ptr<BaseScrollBarThumb> thumb_ = nullptr;
 
diff --git a/ui/views/controls/scrollbar/scroll_bar_views.cc b/ui/views/controls/scrollbar/scroll_bar_views.cc
index f7dee9d8..d2eaad3 100644
--- a/ui/views/controls/scrollbar/scroll_bar_views.cc
+++ b/ui/views/controls/scrollbar/scroll_bar_views.cc
@@ -69,8 +69,9 @@
                           GetNativeThemePart(), theme_state, local_bounds,
                           extra_params);
   const ui::NativeTheme::Part gripper_part =
-      scroll_bar_->IsHorizontal() ? ui::NativeTheme::kScrollbarHorizontalGripper
-                                  : ui::NativeTheme::kScrollbarVerticalGripper;
+      scroll_bar_->GetOrientation() == ScrollBar::Orientation::kHorizontal
+          ? ui::NativeTheme::kScrollbarHorizontalGripper
+          : ui::NativeTheme::kScrollbarVerticalGripper;
   GetNativeTheme()->Paint(canvas->sk_canvas(), GetColorProvider(), gripper_part,
                           theme_state, local_bounds, extra_params);
 }
@@ -88,8 +89,9 @@
 }
 
 ui::NativeTheme::Part ScrollBarThumb::GetNativeThemePart() const {
-  if (scroll_bar_->IsHorizontal())
+  if (scroll_bar_->GetOrientation() == ScrollBar::Orientation::kHorizontal) {
     return ui::NativeTheme::kScrollbarHorizontalThumb;
+  }
   return ui::NativeTheme::kScrollbarVerticalThumb;
 }
 
@@ -113,13 +115,15 @@
 
 }  // namespace
 
-ScrollBarViews::ScrollBarViews(bool horizontal) : ScrollBar(horizontal) {
+ScrollBarViews::ScrollBarViews(Orientation orientation)
+    : ScrollBar(orientation) {
   SetFlipCanvasOnPaintForRTLUI(true);
   state_ = ui::NativeTheme::kNormal;
 
   auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  if (!horizontal)
+  if (orientation == ScrollBar::Orientation::kVertical) {
     layout->SetOrientation(views::LayoutOrientation::kVertical);
+  }
 
   const auto scroll_func = [](ScrollBarViews* scrollbar, ScrollAmount amount) {
     scrollbar->ScrollByAmount(amount);
@@ -128,7 +132,8 @@
   prev_button_ = AddChildView(std::make_unique<ScrollBarButton>(
       base::BindRepeating(scroll_func, base::Unretained(this),
                           ScrollAmount::kPrevLine),
-      horizontal ? Type::kLeft : Type::kUp));
+      orientation == ScrollBar::Orientation::kHorizontal ? Type::kLeft
+                                                         : Type::kUp));
   prev_button_->set_context_menu_controller(this);
 
   SetThumb(new ScrollBarThumb(this));
@@ -143,10 +148,12 @@
   next_button_ = AddChildView(std::make_unique<ScrollBarButton>(
       base::BindRepeating(scroll_func, base::Unretained(this),
                           ScrollBar::ScrollAmount::kNextLine),
-      horizontal ? Type::kRight : Type::kDown));
+      orientation == ScrollBar::Orientation::kHorizontal ? Type::kRight
+                                                         : Type::kDown));
   next_button_->set_context_menu_controller(this);
-  part_ = horizontal ? ui::NativeTheme::kScrollbarHorizontalTrack
-                     : ui::NativeTheme::kScrollbarVerticalTrack;
+  part_ = orientation == ScrollBar::Orientation::kHorizontal
+              ? ui::NativeTheme::kScrollbarHorizontalTrack
+              : ui::NativeTheme::kScrollbarVerticalTrack;
 }
 
 ScrollBarViews::~ScrollBarViews() = default;
@@ -184,22 +191,24 @@
   scrollbar_track.is_upper = true;
   ui::NativeTheme::ExtraParams params(scrollbar_track);
   gfx::Rect upper_bounds = bounds;
-  if (IsHorizontal())
+  if (GetOrientation() == ScrollBar::Orientation::kHorizontal) {
     upper_bounds.set_width(thumb->x() - upper_bounds.x());
-  else
+  } else {
     upper_bounds.set_height(thumb->y() - upper_bounds.y());
+  }
   if (!upper_bounds.IsEmpty()) {
     GetNativeTheme()->Paint(canvas->sk_canvas(), GetColorProvider(), part_,
                             state_, upper_bounds, params);
   }
 
   scrollbar_track.is_upper = false;
-  if (IsHorizontal())
+  if (GetOrientation() == ScrollBar::Orientation::kHorizontal) {
     bounds.Inset(
         gfx::Insets::TLBR(0, thumb->bounds().right() - bounds.x(), 0, 0));
-  else
+  } else {
     bounds.Inset(
         gfx::Insets::TLBR(thumb->bounds().bottom() - bounds.y(), 0, 0, 0));
+  }
   if (!bounds.IsEmpty()) {
     GetNativeTheme()->Paint(canvas->sk_canvas(), GetColorProvider(), part_,
                             state_, bounds, params);
@@ -208,7 +217,8 @@
 
 int ScrollBarViews::GetThickness() const {
   const gfx::Size size = GetPreferredSize();
-  return IsHorizontal() ? size.height() : size.width();
+  return GetOrientation() == ScrollBar::Orientation::kHorizontal ? size.height()
+                                                                 : size.width();
 }
 
 gfx::Rect ScrollBarViews::GetTrackBounds() const {
@@ -216,7 +226,7 @@
   gfx::Size size = prev_button_->GetPreferredSize();
   BaseScrollBarThumb* thumb = GetThumb();
 
-  if (IsHorizontal()) {
+  if (GetOrientation() == ScrollBar::Orientation::kHorizontal) {
     bounds.set_x(bounds.x() + size.width());
     bounds.set_width(std::max(0, bounds.width() - 2 * size.width()));
     bounds.set_height(thumb->GetPreferredSize().height());
diff --git a/ui/views/controls/scrollbar/scroll_bar_views.h b/ui/views/controls/scrollbar/scroll_bar_views.h
index 954309e..06bc7d4 100644
--- a/ui/views/controls/scrollbar/scroll_bar_views.h
+++ b/ui/views/controls/scrollbar/scroll_bar_views.h
@@ -23,7 +23,7 @@
 
  public:
   // Creates new scrollbar, either horizontal or vertical.
-  explicit ScrollBarViews(bool horizontal = true);
+  explicit ScrollBarViews(Orientation orientation = Orientation::kHorizontal);
 
   ScrollBarViews(const ScrollBarViews&) = delete;
   ScrollBarViews& operator=(const ScrollBarViews&) = delete;
diff --git a/ui/views/style/platform_style.cc b/ui/views/style/platform_style.cc
index f6bc21e..39a14eb 100644
--- a/ui/views/style/platform_style.cc
+++ b/ui/views/style/platform_style.cc
@@ -57,11 +57,12 @@
 #endif
 
 // static
-std::unique_ptr<ScrollBar> PlatformStyle::CreateScrollBar(bool is_horizontal) {
+std::unique_ptr<ScrollBar> PlatformStyle::CreateScrollBar(
+    ScrollBar::Orientation orientation) {
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
-  return std::make_unique<OverlayScrollBar>(is_horizontal);
+  return std::make_unique<OverlayScrollBar>(orientation);
 #else
-  return std::make_unique<ScrollBarViews>(is_horizontal);
+  return std::make_unique<ScrollBarViews>(orientation);
 #endif
 }
 
diff --git a/ui/views/style/platform_style.h b/ui/views/style/platform_style.h
index d3d97c3..b1d16005 100644
--- a/ui/views/style/platform_style.h
+++ b/ui/views/style/platform_style.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ui/views/controls/button/button.h"
+#include "ui/views/controls/scrollbar/scroll_bar.h"
 #include "ui/views/view.h"
 #include "ui/views/views_export.h"
 
@@ -17,8 +18,6 @@
 
 namespace views {
 
-class ScrollBar;
-
 // Cross-platform API for providing platform-specific styling for toolkit-views.
 class VIEWS_EXPORT PlatformStyle {
  public:
@@ -77,7 +76,8 @@
   static const View::FocusBehavior kDefaultFocusBehavior;
 
   // Creates the default scrollbar for the given orientation.
-  static std::unique_ptr<ScrollBar> CreateScrollBar(bool is_horizontal);
+  static std::unique_ptr<ScrollBar> CreateScrollBar(
+      ScrollBar::Orientation orientation);
 
   // Called whenever a textfield edit fails. Gives visual/audio feedback about
   // the failed edit if platform-appropriate.
diff --git a/ui/views/style/platform_style_mac.mm b/ui/views/style/platform_style_mac.mm
index fd780b3..ace1e3b 100644
--- a/ui/views/style/platform_style_mac.mm
+++ b/ui/views/style/platform_style_mac.mm
@@ -55,8 +55,9 @@
 const bool PlatformStyle::kReturnClicksFocusedControl = false;
 
 // static
-std::unique_ptr<ScrollBar> PlatformStyle::CreateScrollBar(bool is_horizontal) {
-  return std::make_unique<CocoaScrollBar>(is_horizontal);
+std::unique_ptr<ScrollBar> PlatformStyle::CreateScrollBar(
+    ScrollBar::Orientation orientation) {
+  return std::make_unique<CocoaScrollBar>(orientation);
 }
 
 // static
diff --git a/v8 b/v8
index 93c5bbc..4892aa3 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 93c5bbc582e38f3a543ae0e5a28e593f2e7cc6dd
+Subproject commit 4892aa3e774cdd84597e696d57b4468a6ae3d77f