diff --git a/DEPS b/DEPS
index a5f222d..ef77d071 100644
--- a/DEPS
+++ b/DEPS
@@ -304,15 +304,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': '60b76f3502c88409b8190bc66362666ffe6dce81',
+  'src_internal_revision': '1ec6eaf1463ef817385ef7fd9014c5e74ce4b91b',
   # 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': '3d3d12b3fed33cf7d43f69204c3b91656f2f59d9',
+  'skia_revision': '62f369c759947272dfdd2d8f060afadbcc361e79',
   # 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': '2a06d4e3534debe1294744c208e85d8c4f06d579',
+  'v8_revision': 'dbccf5638033c2f65fa8726313050ddecadda427',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -383,7 +383,7 @@
   # 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': '9e9b01285b3b1febec28c95040e7ed89e3b86bc8',
+  'chromium_variations_revision': '9485c21d7b149d1fd9a663762182a7838095913e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -399,7 +399,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'c1d688bcb2e1d1ebcebbe07e48ddcfa90228b138',
+  'devtools_frontend_revision': 'ecbae84d39c9a39365dfbcb791c6b78d4df6e373',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -423,7 +423,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '7ffb6e9573216af00374a8dd474598ce94f2d97a',
+  'dawn_revision': '538f02eecf1a80738f1ece234bbf6c947d127add',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1004,7 +1004,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'abd3ccee94ac82bc22053398886ba2dfb5a518fa',
+    'aa6befe0ff74c3a26e310d3e119191e064b6b197',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1163,7 +1163,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'JZ72YZlEwPXylEAvSLZX9OglfCwT4XpDJscRG8nioCkC',
+          'version': 'vAfIh85uzcaBMYEt3ZZIgV2Byl0NtbeE8DNHORAF1r0C',
       },
     ],
     'condition': 'checkout_android',
@@ -1425,7 +1425,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '8a31c4e2f3416b669a31b6e6b8be77bf4ec12532',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '8199e2bbaef867e258148454b1ee7a472fe3a638',
       'condition': 'checkout_chromeos',
   },
 
@@ -1460,13 +1460,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd32e1cb5717853a1837347884abc85149813c398',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0dd502813398cb44477df2c246ed4495c4a0c46d',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '701f4784e9e7d73927dcf4d60bc0f0db552b79b7',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '454bc68b84610f49e617ab997e056e7cb2429ad5',
     'condition': 'checkout_src_internal',
   },
 
@@ -1802,7 +1802,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '611d9ba0a55df154ff5cb7a97d41a41103265f5e',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '5b4cfe88e45a42fbdf22f7210ed253faec9c0fe6',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e4fbea0c9751ae8aa86629b197a28d8276a2b0da',
@@ -1930,7 +1930,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '17661211decceb52c9597a8bed5c5bdfcc44a2a2',
+    Var('chromium_git') + '/openscreen' + '@' + 'd77735d9e5ef30477139b0fc1ca3db6706ec8651',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '95fe35ffb383710a6e0567e958ead9a3b66e930c',
@@ -1993,7 +1993,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'a67d6c1d5308b0950a91fed8e03e5c048d22d5cd',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'a771d3fbe7c432dc4db68360c6c0004fdde5646b',
 
   'src/third_party/r8': {
       'packages': [
@@ -2173,7 +2173,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '007e1015875a0a91794edbc14feb3ad5fe60f90b',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f317f7106a7a15a04da7cd30c2e2ddb1b3025bc6',
+    Var('webrtc_git') + '/src.git' + '@' + 'fa4128eedbe427eecd28988f2585753645514296',
 
   # 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.
@@ -2307,7 +2307,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'kKc697kATRyvYNkrIemhsZB-hPhYXqiliFMKPeBuYqkC',
+        'version': '1SgwxREi0QXR9Sbc-MWrlCbZ98Byk7dZ09tHDL9j_N0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/cookie_manager.cc b/android_webview/browser/cookie_manager.cc
index e95e2a9..a31adce 100644
--- a/android_webview/browser/cookie_manager.cc
+++ b/android_webview/browser/cookie_manager.cc
@@ -178,10 +178,6 @@
 // Are cookies allowed for file:// URLs by default?
 const bool kDefaultFileSchemeAllowed = false;
 
-BASE_FEATURE(kWebViewCookieManagerUseThreadPool,
-             "WebViewCookieManagerUseThreadPool",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 }  // namespace
 
 // static
@@ -212,13 +208,9 @@
       cookie_store_client_thread_("CookieMonsterClient"),
       cookie_store_backend_thread_("CookieMonsterBackend"),
       setting_new_mojo_cookie_manager_(false) {
-  if (base::FeatureList::IsEnabled(kWebViewCookieManagerUseThreadPool)) {
-    cookie_store_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});
-  } else {
-    cookie_store_client_thread_.Start();
-    cookie_store_backend_thread_.Start();
-    cookie_store_task_runner_ = cookie_store_client_thread_.task_runner();
-  }
+  cookie_store_client_thread_.Start();
+  cookie_store_backend_thread_.Start();
+  cookie_store_task_runner_ = cookie_store_client_thread_.task_runner();
   cookie_store_path_ = GetContextPath().Append(FILE_PATH_LITERAL("Cookies"));
   if (!parent_context_) {
     // Default profile
@@ -336,13 +328,8 @@
         cookie_store_path_, /* restore_old_session_cookies= */ true,
         /* persist_session_cookies= */ true);
     cookie_config.client_task_runner = cookie_store_task_runner_;
-
-    // If `background_task_runner` is not set it will be created from the thread
-    // pool internally.
-    if (!base::FeatureList::IsEnabled(kWebViewCookieManagerUseThreadPool)) {
-      cookie_config.background_task_runner =
-          cookie_store_backend_thread_.task_runner();
-    }
+    cookie_config.background_task_runner =
+        cookie_store_backend_thread_.task_runner();
 
     {
       base::AutoLock lock(allow_file_scheme_cookies_lock_);
diff --git a/android_webview/browser/cookie_manager.h b/android_webview/browser/cookie_manager.h
index 74db4b8d..89ea066 100644
--- a/android_webview/browser/cookie_manager.h
+++ b/android_webview/browser/cookie_manager.h
@@ -22,6 +22,10 @@
 
 class GURL;
 
+namespace base {
+class SingleThreadTaskRunner;
+}
+
 namespace net {
 class CookieStore;
 class CanonicalCookie;
@@ -288,7 +292,7 @@
   base::Thread cookie_store_client_thread_;
   base::Thread cookie_store_backend_thread_;
 
-  scoped_refptr<base::SequencedTaskRunner> cookie_store_task_runner_;
+  scoped_refptr<base::SingleThreadTaskRunner> cookie_store_task_runner_;
   std::unique_ptr<net::CookieStore> cookie_store_;
 
   // Tracks if we're in the middle of a call to SetMojoCookieManager(). See the
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index c53df92..c4d00088 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -1001,7 +1001,6 @@
         Flag.baseFeature(
                 BlinkFeatures.CONCURRENT_VIEW_TRANSITIONS_SPA,
                 "Allows concurrent transitions in local frames rendered in the same process"),
-        Flag.baseFeature("WebViewCookieManagerUseThreadPool"),
         Flag.baseFeature("WebViewVizUseThreadPool"),
         // Add new commandline switches and features above. The final entry should have a
         // trailing comma for cleaner diffs.
diff --git a/ash/accelerators/keyboard_code_util.cc b/ash/accelerators/keyboard_code_util.cc
index d984c83..1e8d2107 100644
--- a/ash/accelerators/keyboard_code_util.cc
+++ b/ash/accelerators/keyboard_code_util.cc
@@ -84,7 +84,7 @@
   if (key_label)
     return key_label.value();
 
-  return ash::KeycodeToKeyString(key_code, remap_positional_key);
+  return ash::GetKeyDisplay(key_code, remap_positional_key);
 }
 
 const gfx::VectorIcon* GetVectorIconForKeyboardCode(ui::KeyboardCode key_code) {
diff --git a/ash/accelerators/shortcut_input_handler.cc b/ash/accelerators/shortcut_input_handler.cc
index 8182649a7..cd89dee2d 100644
--- a/ash/accelerators/shortcut_input_handler.cc
+++ b/ash/accelerators/shortcut_input_handler.cc
@@ -23,7 +23,7 @@
 
 constexpr int kKeyboardModifierFlags = ui::EF_CONTROL_DOWN |
                                        ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN |
-                                       ui::EF_ALT_DOWN;
+                                       ui::EF_ALT_DOWN | ui::EF_FUNCTION_DOWN;
 
 ui::KeyboardCode RetrieveKeyCode(const ui::KeyEvent& event) {
   // Remap positional keys in the current layout to the corresponding US layout
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index c869e65..e66712a7e 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -7937,44 +7937,29 @@
       <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_NUDGE_TITLE" translateable="false" desc="Title of the pop up nudge to remind using fn key.">
         Use your fn Key
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus top row key.">
-        Press fn + the action key
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_TOP_ROW_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus top row key.">
+        To access F keys, hold Fn key and press corresponding top row key
       </message>
       <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_CAPS_LOCK_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus top row key.">
-        Press fn + right alt for CAPS LOCK
+        Looking for Caps Lock? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_UP_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus up arrow key.">
-        Press fn + up for page up
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_UP_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus up arrow key.">
+        Looking for Page Up? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_DOWN_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus down arrow key.">
-        Press fn + down for page down
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_DOWN_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus down arrow key.">
+        Looking for Page Down? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_LEFT_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus left arrow key.">
-        Press fn + left for home
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_HOME_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus left arrow key.">
+        Looking for Home? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_RIGHT_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus right arrow key.">
-        Press fn + right for end
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_END_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus right arrow key.">
+        Looking for End? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_BACKSPACE_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus backspace key.">
-        Press fn + backspace for delete
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_DELETE_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus backspace key.">
+        Looking for Delete? Try
       </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_SHIFT_BACKSPACE_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus shift backspace key.">
-        Press Fn + shift + backspace in stead of search + shift + backspace key
-      </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_UP_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses alt plus up arrow key.">
-        Press fn + up for page up
-      </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_DOWN_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses alt plus down arrow key.">
-        Press fn + down for page down
-      </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_LEFT_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses alt plus left arrow key.">
-        Press fn + left for home
-      </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_RIGHT_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses alt plus right arrow key.">
-        Press fn + right for end
-      </message>
-      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_BACKSPACE_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses alt plus backspace key.">
-        Press fn + backspace for delete
+      <message name="IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_INSERT_NUDGE_DESCRIPTION" translateable="false" desc="Description of the pop up nudge to remind using fn key when user presses search plus shift backspace key.">
+        Looking for Insert? Try
       </message>
       <!-- Modifier key names -->
       <message name="IDS_ASH_SETTINGS_SHORTCUT_MODIFIER_LAUNCHER" desc="Name of the [Launcher] key. This key has a different name and keyboard glyph/icon depending on the device. This is the string for when it is called Launcher and has a circle/launcher icon.">
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 3035e1c..9bccb77d 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1138,11 +1138,6 @@
              "kFileNotificationRevamp",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Enables experimental UI features in Files app.
-BASE_FEATURE(kFilesAppExperimental,
-             "FilesAppExperimental",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Enables the files transfer conflict dialog in Files app.
 BASE_FEATURE(kFilesConflictDialog,
              "FilesConflictDialog",
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 80fa4a14..0b282187 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -354,7 +354,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kFederatedLauncherQueryAnalyticsVersion2Task);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFileNotificationRevamp);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesAppExperimental);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesConflictDialog);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesLocalImageSearch);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesMaterializedViews);
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 54a0c87..cb012dc 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -176,6 +176,9 @@
 inline constexpr char kEduCoexistenceToSAcceptedVersion[] =
     "family_link_user.edu_coexistence_tos_accepted_version";
 
+// A string pref that stores the PIN used to unlock parental app controls.
+inline constexpr char kOnDeviceAppControlsPin[] = "on_device_app_controls.pin";
+
 // A boolean pref indicating if a PIN has been set up for on-device apps
 // parental controls.
 inline constexpr char kOnDeviceAppControlsSetupCompleted[] =
diff --git a/ash/login/ui/lock_screen_media_controls_view_unittest.cc b/ash/login/ui/lock_screen_media_controls_view_unittest.cc
index bf70731..983d972c 100644
--- a/ash/login/ui/lock_screen_media_controls_view_unittest.cc
+++ b/ash/login/ui/lock_screen_media_controls_view_unittest.cc
@@ -23,7 +23,6 @@
 #include "services/media_session/public/cpp/test/test_media_controller.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/layer_observer.h"
@@ -662,11 +661,8 @@
   EXPECT_EQ(1, media_controller()->seek_forward_count());
 }
 
-TEST_F(LockScreenMediaControlsViewTest, UpdateAppIcon) {
-  // TODO (crbug/1520620): Remove the skip code once test is fixed.
-  if (::features::IsChromeRefresh2023()) {
-    GTEST_SKIP();
-  }
+// TODO (crbug/1520620): Test fails post-ChromeRefresh2023. Fix and reenable.
+TEST_F(LockScreenMediaControlsViewTest, DISABLED_UpdateAppIcon) {
   SimulateMediaSessionChanged(
       media_session::mojom::MediaPlaybackState::kPlaying);
 
diff --git a/ash/system/input_device_settings/input_device_settings_notification_controller.cc b/ash/system/input_device_settings/input_device_settings_notification_controller.cc
index 7f46292..83ccc7d 100644
--- a/ash/system/input_device_settings/input_device_settings_notification_controller.cc
+++ b/ash/system/input_device_settings/input_device_settings_notification_controller.cc
@@ -307,50 +307,59 @@
   }
 }
 
-std::u16string GetSixPackShortcutUpdatedString(
-    ui::KeyboardCode key_code,
-    SixPackShortcutModifier blocked_modifier) {
-  CHECK(blocked_modifier != SixPackShortcutModifier::kNone);
-  std::u16string input_key_string;
+std::u16string GetSixPackShortcutUpdatedString(ui::KeyboardCode key_code) {
   switch (key_code) {
     case ui::VKEY_PRIOR:
-      return blocked_modifier == SixPackShortcutModifier::kSearch
-                 ? l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_UP_NUDGE_DESCRIPTION)
-                 : l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_UP_NUDGE_DESCRIPTION);
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_UP_NUDGE_DESCRIPTION);
     case ui::VKEY_NEXT:
-      return blocked_modifier == SixPackShortcutModifier::kSearch
-                 ? l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_DOWN_NUDGE_DESCRIPTION)
-                 : l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_DOWN_NUDGE_DESCRIPTION);
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_DOWN_NUDGE_DESCRIPTION);
     case ui::VKEY_HOME:
-      return blocked_modifier == SixPackShortcutModifier::kSearch
-                 ? l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_LEFT_NUDGE_DESCRIPTION)
-                 : l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_LEFT_NUDGE_DESCRIPTION);
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_HOME_NUDGE_DESCRIPTION);
     case ui::VKEY_END:
-      return blocked_modifier == SixPackShortcutModifier::kSearch
-                 ? l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_RIGHT_NUDGE_DESCRIPTION)
-                 : l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_RIGHT_NUDGE_DESCRIPTION);
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_END_NUDGE_DESCRIPTION);
     case ui::VKEY_DELETE:
-      return blocked_modifier == SixPackShortcutModifier::kSearch
-                 ? l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_BACKSPACE_NUDGE_DESCRIPTION)
-                 : l10n_util::GetStringUTF16(
-                       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_BACKSPACE_NUDGE_DESCRIPTION);
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_DELETE_NUDGE_DESCRIPTION);
     case ui::VKEY_INSERT:
       return l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_SHIFT_BACKSPACE_NUDGE_DESCRIPTION);
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_INSERT_NUDGE_DESCRIPTION);
     default:
       NOTREACHED_NORETURN();
   }
 }
 
+void InsertSixPackShortcutKeyboardCodes(
+    ui::KeyboardCode key_code,
+    std::vector<ui::KeyboardCode>& keyboard_codes) {
+  switch (key_code) {
+    case ui::VKEY_PRIOR:
+      keyboard_codes.push_back(ui::VKEY_UP);
+      break;
+    case ui::VKEY_NEXT:
+      keyboard_codes.push_back(ui::VKEY_DOWN);
+      break;
+    case ui::VKEY_HOME:
+      keyboard_codes.push_back(ui::VKEY_LEFT);
+      break;
+    case ui::VKEY_END:
+      keyboard_codes.push_back(ui::VKEY_RIGHT);
+      break;
+    case ui::VKEY_DELETE:
+      keyboard_codes.push_back(ui::VKEY_BACK);
+      break;
+    case ui::VKEY_INSERT:
+      keyboard_codes.push_back(ui::VKEY_SHIFT);
+      keyboard_codes.push_back(ui::VKEY_BACK);
+      break;
+    default:
+      NOTREACHED();
+  }
+}
+
 std::u16string GetSixPackShortcut(ui::KeyboardCode key_code,
                                   SixPackShortcutModifier modifier) {
   CHECK(modifier != SixPackShortcutModifier::kNone);
@@ -1065,7 +1074,7 @@
   AnchoredNudgeData nudge_data(
       kTopRowKeyNoMatchNudgeId, NudgeCatalogName::kSearchTopRowKeyPressed,
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_TOP_ROW_NUDGE_DESCRIPTION));
   nudge_data.title_text = l10n_util::GetStringUTF16(
       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_NUDGE_TITLE);
 
@@ -1131,11 +1140,14 @@
   prefs->SetInteger(shown_count_pref_name, shown_count + 1);
   prefs->SetTime(last_shown_time_pref_name, now);
 
-  AnchoredNudgeData nudge_data(
-      kSixPackKeyNoMatchNudgeId, NudgeCatalogName::kSixPackRemappingPressed,
-      GetSixPackShortcutUpdatedString(key_code, old_matched_modifier));
+  AnchoredNudgeData nudge_data(kSixPackKeyNoMatchNudgeId,
+                               NudgeCatalogName::kSixPackRemappingPressed,
+                               GetSixPackShortcutUpdatedString(key_code));
   nudge_data.title_text = l10n_util::GetStringUTF16(
       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_NUDGE_TITLE);
+  std::vector<ui::KeyboardCode> keyboard_codes = {ui::VKEY_FUNCTION};
+  InsertSixPackShortcutKeyboardCodes(key_code, keyboard_codes);
+  nudge_data.keyboard_codes = std::move(keyboard_codes);
 
   AnchoredNudgeManager::Get()->Show(nudge_data);
 }
@@ -1170,6 +1182,7 @@
           IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_CAPS_LOCK_NUDGE_DESCRIPTION));
   nudge_data.title_text = l10n_util::GetStringUTF16(
       IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_NUDGE_TITLE);
+  nudge_data.keyboard_codes = {ui::VKEY_FUNCTION, ui::VKEY_RIGHT_ALT};
 
   AnchoredNudgeManager::Get()->Show(nudge_data);
 }
diff --git a/ash/system/input_device_settings/input_device_settings_notification_controller_unittest.cc b/ash/system/input_device_settings/input_device_settings_notification_controller_unittest.cc
index 06ac2be..9ce7b566 100644
--- a/ash/system/input_device_settings/input_device_settings_notification_controller_unittest.cc
+++ b/ash/system/input_device_settings/input_device_settings_notification_controller_unittest.cc
@@ -636,7 +636,7 @@
   EXPECT_EQ(
       nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_SHIFT_BACKSPACE_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_INSERT_NUDGE_DESCRIPTION));
   CancelNudge(kSixPackKeyNoMatchNudgeId);
   EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
 
@@ -651,7 +651,7 @@
   EXPECT_EQ(
       nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_BACKSPACE_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_DELETE_NUDGE_DESCRIPTION));
   CancelNudge(kSixPackKeyNoMatchNudgeId);
   EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
 
@@ -662,7 +662,7 @@
   EXPECT_EQ(
       nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_LEFT_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_HOME_NUDGE_DESCRIPTION));
   CancelNudge(kSixPackKeyNoMatchNudgeId);
   EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
 
@@ -673,7 +673,7 @@
   EXPECT_EQ(
       nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_SEARCH_PLUS_RIGHT_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_END_NUDGE_DESCRIPTION));
   CancelNudge(kSixPackKeyNoMatchNudgeId);
   EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
 
@@ -684,7 +684,7 @@
   EXPECT_EQ(
       nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
       l10n_util::GetStringUTF16(
-          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_ALT_PLUS_UP_NUDGE_DESCRIPTION));
+          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_UP_NUDGE_DESCRIPTION));
   CancelNudge(kSixPackKeyNoMatchNudgeId);
   EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
 
diff --git a/ash/webui/common/mojom/sea_pen.mojom b/ash/webui/common/mojom/sea_pen.mojom
index e1cb6c9d..27f6871 100644
--- a/ash/webui/common/mojom/sea_pen.mojom
+++ b/ash/webui/common/mojom/sea_pen.mojom
@@ -32,7 +32,7 @@
   string template_title;
 };
 
-// The information needed to query template wallpapers. The possible `id` and
+// The information needed to send a template query. The possible `id` and
 // `options` values are defined in cs/ChromeOsWallpaperQueryProcessor.kt. They
 // are not controlled by the renderer. The browser is responsible for validating
 // the values.
@@ -88,10 +88,10 @@
   RecentSeaPenImageInfo? image_info;
 };
 
-// Maximum allowable search text length, in bytes.
+// Maximum allowable query text length, in bytes.
 // When validating length in Javascript, be careful of the difference between
 // UTF-16 (Javascript) length and UTF-8 length.
-const uint32 kMaximumSearchWallpaperTextBytes = 3000;
+const uint32 kMaximumGetSeaPenThumbnailsTextBytes = 3000;
 
 // Should match the associated enum in components/manta/manta_status.h
 enum MantaStatusCode {
@@ -110,43 +110,45 @@
   kMax = kPerUserQuotaExceeded,
 };
 
-// Provides APIs to perform wallpaper searches. Uses APIs in
-// `manta.proto` to send search requests to a server.
+// Provides APIs to get thumbnails and full size images. Uses APIs in
+// `manta.proto` to send requests to a google owned server.
 // Implemented in the browser process, and called by the ChromeOS
 // Personalization App (chrome://personalization) in a renderer process.
 interface SeaPenProvider {
 
-    // Given a user text input, return a set of matching images.
-    // `images` will be null in case of an unrecoverable error, such as network
-    // failure.
-    // `images` will be empty array when the request succeeded, but no matching
-    // images were returned.
-    // `text` must be smaller than `kMaximumSearchWallpaperTextBytes` bytes.
-    SearchWallpaper(SeaPenQuery query) =>
-        (array<SeaPenThumbnail>? images, MantaStatusCode statusCode);
+    // Given a user text input, return a set of matching thumbnails.
+    // `thumbnails` will be null in case of an unrecoverable error, such as
+    // network failure.
+    // `thumbnails` will be empty array when the request succeeded, but no
+    // matching thumbnails were returned.
+    // If `query` is of type string, it must be smaller than
+    // `kMaximumGetSeaPenThumbnailsTextBytes` bytes.
+    GetSeaPenThumbnails(SeaPenQuery query) =>
+        (array<SeaPenThumbnail>? thumbnails, MantaStatusCode statusCode);
 
-    // Select a thumbnail and set as the system wallpaper for the current user.
-    // `id` must be a valid id from a previous call to `SearchWallpaper`.
+    // Select a thumbnail for the current user.
+    // `id` must be a valid id from a previous call to `GetSeaPenThumbnails`.
     SelectSeaPenThumbnail(uint32 id) => (bool success);
 
-    // Gets the available previously selected SeaPen wallpapers. `ids` will be
+    // Gets the available previously selected SeaPen images. `ids` will be
     // empty if no thumbnails have been previously selected, or in case of disk
     // or other failure.
-    GetRecentSeaPenImages() => (array<uint32> ids);
+    GetRecentSeaPenImageIds() => (array<uint32> ids);
 
     // Sets the given SeaPen image id as the user's background. Must be a valid
-    // id from a previous call to `GetRecentSeaPenImages`.
+    // id from a previous call to `GetRecentSeaPenImageIds`.
     SelectRecentSeaPenImage(uint32 id) => (bool success);
 
     // Fetches the thumbnail data for the given recent Sea Pen image `id`.
-    // `id` must be a valid id from a previous call to `GetRecentSeaPenImages`.
+    // `id` must be a valid id from a previous call to
+    // `GetRecentSeaPenImageIds`.
     // `thumbnail_data` is null if the `id` is invalid or the image fails to
     // decode.
     GetRecentSeaPenImageThumbnail(uint32 id) =>
         (RecentSeaPenThumbnailData? thumbnail_data);
 
     // Deletes the selected SeaPen image from SeaPen directory. `id` must be a
-    // valid id from a previous call to `GetRecentSeaPenImages`.
+    // valid id from a previous call to `GetRecentSeaPenImageIds`.
     DeleteRecentSeaPenImage(uint32 id) => (bool success);
 
     // Pops up a feedback dialog when user clicks feedback buttons.
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_actions.ts b/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
index 0f39045..9b18c005 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
@@ -76,7 +76,7 @@
 export interface SetSeaPenThumbnailsAction extends Action {
   name: SeaPenActionName.SET_SEA_PEN_THUMBNAILS;
   query: SeaPenQuery;
-  images: SeaPenThumbnail[]|null;
+  thumbnails: SeaPenThumbnail[]|null;
 }
 
 /**
@@ -84,8 +84,8 @@
  */
 export function setSeaPenThumbnailsAction(
     query: SeaPenQuery,
-    images: SeaPenThumbnail[]|null): SetSeaPenThumbnailsAction {
-  return {name: SeaPenActionName.SET_SEA_PEN_THUMBNAILS, query, images};
+    thumbnails: SeaPenThumbnail[]|null): SetSeaPenThumbnailsAction {
+  return {name: SeaPenActionName.SET_SEA_PEN_THUMBNAILS, query, thumbnails};
 }
 
 export interface BeginLoadRecentSeaPenImagesAction extends Action {
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_controller.ts b/ash/webui/common/resources/sea_pen/sea_pen_controller.ts
index 95b49ad..099f235 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_controller.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_controller.ts
@@ -14,8 +14,8 @@
     id: SeaPenImageId, provider: SeaPenProviderInterface,
     store: SeaPenStoreInterface): Promise<void> {
   const originalCurrentSelected = store.data.currentSelected;
-  // Returns if the selected image is the current wallpaper.
   if (id === originalCurrentSelected) {
+    // Return if the just selected image is already the current image.
     return;
   }
   // Batch these changes together to reduce polymer churn as multiple state
@@ -30,7 +30,7 @@
   store.beginBatchUpdate();
   store.dispatch(seaPenAction.endSelectRecentSeaPenImageAction(id, success));
   if (!success) {
-    console.warn('Error setting wallpaper');
+    console.warn('Error setting image');
   }
   if (store.data.loading.setImage === 0) {
     // Mark the image as applied or revert back to the old one.
@@ -44,14 +44,14 @@
   }
 }
 
-export async function searchSeaPenThumbnails(
+export async function getSeaPenThumbnails(
     query: SeaPenQuery, provider: SeaPenProviderInterface,
     store: SeaPenStoreInterface): Promise<void> {
   store.dispatch(seaPenAction.beginSearchSeaPenThumbnailsAction(query));
   store.dispatch(seaPenAction.setCurrentSeaPenQueryAction(query));
-  const {images, statusCode} =
-      await withMinimumDelay(provider.searchWallpaper(query));
-  if (!isNonEmptyArray(images) || statusCode !== MantaStatusCode.kOk) {
+  const {thumbnails, statusCode} =
+      await withMinimumDelay(provider.getSeaPenThumbnails(query));
+  if (!isNonEmptyArray(thumbnails) || statusCode !== MantaStatusCode.kOk) {
     console.warn('Error generating thumbnails. Status code: ', statusCode);
   }
 
@@ -69,11 +69,11 @@
       (templateIdParam === 'Query' && !!query.textQuery)) {
     store.dispatch(
         seaPenAction.setThumbnailResponseStatusCodeAction(statusCode));
-    store.dispatch(seaPenAction.setSeaPenThumbnailsAction(query, images));
+    store.dispatch(seaPenAction.setSeaPenThumbnailsAction(query, thumbnails));
   }
 }
 
-export async function selectSeaPenWallpaper(
+export async function selectSeaPenThumbnail(
     thumbnail: SeaPenThumbnail, provider: SeaPenProviderInterface,
     store: SeaPenStoreInterface): Promise<void> {
   const originalCurrentSelected = store.data.currentSelected;
@@ -104,7 +104,7 @@
         success ? thumbnail.id : originalCurrentSelected));
   }
   store.endBatchUpdate();
-  // Re-fetches the recent Sea Pen image if setting sea pen wallpaper
+  // Re-fetches the recent SeaPen image if setting SeaPen thumbnail
   // successfully, which means the file has been downloaded successfully.
   if (success) {
     logSeaPenImageSet(/*source=*/ 'Create');
@@ -135,12 +135,12 @@
   }
 }
 
-export async function getRecentSeaPenImages(
+export async function getRecentSeaPenImageIds(
     provider: SeaPenProviderInterface,
     store: SeaPenStoreInterface): Promise<void> {
   store.dispatch(seaPenAction.beginLoadRecentSeaPenImagesAction());
 
-  const {ids} = await provider.getRecentSeaPenImages();
+  const {ids} = await provider.getRecentSeaPenImageIds();
   if (ids == null) {
     console.warn('Failed to fetch recent sea pen images');
   }
@@ -157,7 +157,7 @@
     store: SeaPenStoreInterface): Promise<void> {
   // Do not restart loading local image list if a load is already in progress.
   if (!store.data.loading.recentImages) {
-    await getRecentSeaPenImages(provider, store);
+    await getRecentSeaPenImageIds(provider, store);
   }
   await getMissingRecentSeaPenImageData(provider, store);
 }
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts b/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
index 67cea39..aee9fc9b 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_images_element.ts
@@ -24,7 +24,7 @@
 import {Query, SeaPenImageId} from './constants.js';
 import {isLacrosEnabled} from './load_time_booleans.js';
 import {MantaStatusCode, SeaPenThumbnail} from './sea_pen.mojom-webui.js';
-import {clearSeaPenThumbnails, openFeedbackDialog, selectSeaPenWallpaper} from './sea_pen_controller.js';
+import {clearSeaPenThumbnails, openFeedbackDialog, selectSeaPenThumbnail} from './sea_pen_controller.js';
 import {SeaPenTemplateId} from './sea_pen_generated.mojom-webui.js';
 import {getTemplate} from './sea_pen_images_element.html.js';
 import {getSeaPenProvider} from './sea_pen_interface_provider.js';
@@ -283,7 +283,7 @@
       logSeaPenThumbnailClicked(this.templateId as SeaPenTemplateId);
     }
 
-    selectSeaPenWallpaper(
+    selectSeaPenThumbnail(
         event.model.item, getSeaPenProvider(), this.getStore());
   }
 
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.ts b/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.ts
index 3065fe7..26fdfd0 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_input_query_element.ts
@@ -22,8 +22,8 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
 import {isSeaPenTextInputEnabled} from './load_time_booleans.js';
-import {MAXIMUM_SEARCH_WALLPAPER_TEXT_BYTES, SeaPenQuery, SeaPenThumbnail} from './sea_pen.mojom-webui.js';
-import {searchSeaPenThumbnails} from './sea_pen_controller.js';
+import {MAXIMUM_GET_SEA_PEN_THUMBNAILS_TEXT_BYTES, SeaPenQuery, SeaPenThumbnail} from './sea_pen.mojom-webui.js';
+import {getSeaPenThumbnails} from './sea_pen_controller.js';
 import {getTemplate} from './sea_pen_input_query_element.html.js';
 import {getSeaPenProvider} from './sea_pen_interface_provider.js';
 import {WithSeaPenStore} from './sea_pen_store.js';
@@ -67,7 +67,7 @@
 
       maxTextLength_: {
         type: Number,
-        value: Math.floor(MAXIMUM_SEARCH_WALLPAPER_TEXT_BYTES / 3),
+        value: Math.floor(MAXIMUM_GET_SEA_PEN_THUMBNAILS_TEXT_BYTES / 3),
       },
     };
   }
@@ -95,7 +95,7 @@
     const query: SeaPenQuery = {
       textQuery: this.textValue_,
     };
-    searchSeaPenThumbnails(query, getSeaPenProvider(), this.getStore());
+    getSeaPenThumbnails(query, getSeaPenProvider(), this.getStore());
     // Stop the event propagation, otherwise, the event will be passed to parent
     // element, this.onClick_ will be triggered improperly.
     event.preventDefault();
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.ts b/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.ts
index b5fcfef..e5a6510 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_recent_wallpapers_element.ts
@@ -24,7 +24,7 @@
 import {SeaPenImageId} from './constants.js';
 import {isSeaPenUINextEnabled} from './load_time_booleans.js';
 import {RecentSeaPenThumbnailData, SeaPenThumbnail} from './sea_pen.mojom-webui.js';
-import {deleteRecentSeaPenImage, fetchRecentSeaPenData, searchSeaPenThumbnails, selectRecentSeaPenImage} from './sea_pen_controller.js';
+import {deleteRecentSeaPenImage, fetchRecentSeaPenData, getSeaPenThumbnails, selectRecentSeaPenImage} from './sea_pen_controller.js';
 import {getSeaPenProvider} from './sea_pen_interface_provider.js';
 import {logRecentImageActionMenuItemClick, RecentImageActionMenuItem} from './sea_pen_metrics_logger.js';
 import {getTemplate} from './sea_pen_recent_wallpapers_element.html.js';
@@ -333,7 +333,7 @@
         seaPenQuery.textQuery ? 'Query' : seaPenQuery.templateQuery?.id;
     // Route to the results page and search thumbnails for the Sea Pen query.
     SeaPenRouterElement.instance().selectSeaPenTemplate(templateId);
-    searchSeaPenThumbnails(seaPenQuery, getSeaPenProvider(), this.getStore());
+    getSeaPenThumbnails(seaPenQuery, getSeaPenProvider(), this.getStore());
   }
 
   private async onClickDeleteWallpaper_(event: Event&{
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts b/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
index a9b08ea..d659de2 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
@@ -205,7 +205,7 @@
   switch (action.name) {
     case SeaPenActionName.SET_SEA_PEN_THUMBNAILS:
       assert(!!action.query, 'input text is empty.');
-      return action.images;
+      return action.thumbnails;
     case SeaPenActionName.CLEAR_SEA_PEN_THUMBNAILS:
       return null;
     default:
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.ts b/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.ts
index d5aa2f7..a2b140d 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_template_query_element.ts
@@ -22,7 +22,7 @@
 
 import {getSeaPenTemplates, SeaPenOption, SeaPenTemplate} from './constants.js';
 import {SeaPenQuery, SeaPenThumbnail, SeaPenUserVisibleQuery} from './sea_pen.mojom-webui.js';
-import {searchSeaPenThumbnails} from './sea_pen_controller.js';
+import {getSeaPenThumbnails} from './sea_pen_controller.js';
 import {SeaPenTemplateChip, SeaPenTemplateId, SeaPenTemplateOption} from './sea_pen_generated.mojom-webui.js';
 import {getSeaPenProvider} from './sea_pen_interface_provider.js';
 import {logGenerateSeaPenWallpaper} from './sea_pen_metrics_logger.js';
@@ -364,7 +364,7 @@
 
   private onClickSearchButton_(event: Event) {
     this.clearSelectedChipState_();
-    searchSeaPenThumbnails(
+    getSeaPenThumbnails(
         this.getTemplateRequest_(), getSeaPenProvider(), this.getStore());
     logGenerateSeaPenWallpaper(this.getSeaPenTemplateId_());
 
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
index fa44ec6..70ba833b 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
@@ -69,6 +69,14 @@
                                       pendingPrerewrittenKeyEvent.*)]]"
           has-launcher-button="[[hasLauncherButton]]">
       </shortcut-input-key>
+      <template is="dom-if" if="[[hasFunctionKey]]">
+        <shortcut-input-key id="functionKey"
+            key="function"
+            key-state="[[getFunctionState(pendingKeyEvent.*,
+                                        pendingPrerewrittenKeyEvent.*)]]"
+            has-launcher-button="[[hasLauncherButton]]">
+        </shortcut-input-key>
+      </template>
       <div id="keySeparator"> + </div>
       <shortcut-input-key id="pendingKey"
           key="[[getKey(pendingKeyEvent.*, pendingPrerewrittenKeyEvent.*)]]"
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
index 241a826..f5de57b 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
@@ -82,10 +82,16 @@
       shouldIgnoreKeyRelease: {
         type: Boolean,
       },
+
+      hasFunctionKey: {
+        type: Boolean,
+      },
+
     };
   }
 
   hasLauncherButton: boolean = true;
+  hasFunctionKey: boolean = false;
   shortcutInputProvider: ShortcutInputProviderInterface|null = null;
   pendingKeyEvent: KeyEvent|null = null;
   pendingPrerewrittenKeyEvent: KeyEvent|null = null;
@@ -348,6 +354,13 @@
   /**
    * Returns the specified CSS state of the modifier key element.
    */
+  protected getFunctionState(): string {
+    return this.getModifierState(Modifier.FN_KEY);
+  }
+
+  /**
+   * Returns the specified CSS state of the modifier key element.
+   */
   private getModifierState(modifier: Modifier): KeyInputState {
     const keyEvent = this.getPendingKeyEvent();
     if (keyEvent && keyEvent?.modifiers & modifier) {
@@ -367,6 +380,8 @@
         return 'alt';
       case Modifier.COMMAND:
         return 'meta';
+      case Modifier.FN_KEY:
+        return 'fn';
     }
     return assertNotReached();
   }
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_utils.ts b/ash/webui/common/resources/shortcut_input_ui/shortcut_utils.ts
index 2c9dde4..855c856 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_utils.ts
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_utils.ts
@@ -26,6 +26,7 @@
   CONTROL = 1 << 2,
   ALT = 1 << 3,
   COMMAND = 1 << 4,
+  FN_KEY = 1 << 5,
 }
 
 export const Modifiers: Modifier[] = [
@@ -33,6 +34,7 @@
   Modifier.CONTROL,
   Modifier.ALT,
   Modifier.COMMAND,
+  Modifier.FN_KEY,
 ];
 
 export enum AllowedModifierKeyCodes {
@@ -41,6 +43,7 @@
   ALT = 18,
   META_LEFT = 91,
   META_RIGHT = 92,
+  FN_KEY = 255,
 }
 
 export const ModifierKeyCodes: AllowedModifierKeyCodes[] = [
@@ -49,10 +52,11 @@
   AllowedModifierKeyCodes.CTRL,
   AllowedModifierKeyCodes.META_LEFT,
   AllowedModifierKeyCodes.META_RIGHT,
+  AllowedModifierKeyCodes.FN_KEY,
 ];
 
 export const getSortedModifiers = (modifierStrings: string[]): string[] => {
-  const sortOrder = ['meta', 'ctrl', 'alt', 'shift'];
+  const sortOrder = ['meta', 'ctrl', 'alt', 'shift', 'fn'];
   if (modifierStrings.length <= 1) {
     return modifierStrings;
   }
diff --git a/ash/webui/personalization_app/resources/js/personalization_app.ts b/ash/webui/personalization_app/resources/js/personalization_app.ts
index c9ca1eb..96c48ed 100644
--- a/ash/webui/personalization_app/resources/js/personalization_app.ts
+++ b/ash/webui/personalization_app/resources/js/personalization_app.ts
@@ -119,7 +119,7 @@
 export {DEFAULT_COLOR_SCHEME} from './theme/utils.js';
 export {LocalImagesElement} from './wallpaper/local_images_element.js';
 export * from 'chrome://resources/ash/common/sea_pen/sea_pen_actions.js';
-export {getRecentSeaPenImages, selectRecentSeaPenImage, searchSeaPenThumbnails, selectSeaPenWallpaper} from 'chrome://resources/ash/common/sea_pen/sea_pen_controller.js';
+export {getRecentSeaPenImageIds, selectRecentSeaPenImage, getSeaPenThumbnails, selectSeaPenThumbnail} from 'chrome://resources/ash/common/sea_pen/sea_pen_controller.js';
 export {SeaPenFeedbackElement} from 'chrome://resources/ash/common/sea_pen/sea_pen_feedback_element.js';
 export {SeaPenImageLoadingElement} from 'chrome://resources/ash/common/sea_pen/sea_pen_image_loading_element.js';
 export {SeaPenImagesElement} from 'chrome://resources/ash/common/sea_pen/sea_pen_images_element.js';
diff --git a/ash/webui/personalization_app/test/personalization_app_mojom_banned_mocha_test_base.cc b/ash/webui/personalization_app/test/personalization_app_mojom_banned_mocha_test_base.cc
index 8425395..7bf2321 100644
--- a/ash/webui/personalization_app/test/personalization_app_mojom_banned_mocha_test_base.cc
+++ b/ash/webui/personalization_app/test/personalization_app_mojom_banned_mocha_test_base.cc
@@ -116,8 +116,9 @@
   bool IsEligibleForSeaPen() override { return true; }
   // ::ash::personalization_app::mojom::SeaPenProvider:
   MOCK_METHOD(void,
-              SearchWallpaper,
-              (const mojom::SeaPenQueryPtr, SearchWallpaperCallback callback),
+              GetSeaPenThumbnails,
+              (const mojom::SeaPenQueryPtr,
+               GetSeaPenThumbnailsCallback callback),
               (override));
   MOCK_METHOD(void,
               SelectSeaPenThumbnail,
@@ -128,8 +129,8 @@
               (uint32_t id, SelectRecentSeaPenImageCallback),
               (override));
   MOCK_METHOD(void,
-              GetRecentSeaPenImages,
-              (GetRecentSeaPenImagesCallback),
+              GetRecentSeaPenImageIds,
+              (GetRecentSeaPenImageIdsCallback),
               (override));
   MOCK_METHOD(void,
               GetRecentSeaPenImageThumbnail,
diff --git a/ash/webui/sanitize_ui/BUILD.gn b/ash/webui/sanitize_ui/BUILD.gn
new file mode 100644
index 0000000..e4df07d
--- /dev/null
+++ b/ash/webui/sanitize_ui/BUILD.gn
@@ -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.
+
+import("//build/config/chromeos/ui_mode.gni")
+import("//testing/test.gni")
+
+assert(is_chromeos_ash, "Sanitize App is ash-chrome only")
+
+static_library("sanitize_ui") {
+  sources = [
+    "sanitize_ui.cc",
+    "sanitize_ui.h",
+    "url_constants.cc",
+    "url_constants.h",
+  ]
+
+  public_deps = [ ":url_constants" ]
+
+  deps = [
+    "//ash/webui/common:chrome_os_webui_config",
+    "//ash/webui/common:trusted_types_util",
+    "//ash/webui/sanitize_ui/resources:resources",
+    "//chromeos/strings:strings_grit",
+    "//content/public/browser:browser",
+    "//ui/base",
+    "//ui/resources",
+    "//ui/web_dialogs",
+    "//ui/webui",
+  ]
+}
+
+# Url constants pulled out to enable depending on production url constants in
+# browser tests.
+source_set("url_constants") {
+  sources = [
+    "url_constants.cc",
+    "url_constants.h",
+  ]
+}
diff --git a/ash/webui/sanitize_ui/DEPS b/ash/webui/sanitize_ui/DEPS
new file mode 100644
index 0000000..52d69fec
--- /dev/null
+++ b/ash/webui/sanitize_ui/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ui/webui",
+]
diff --git a/ash/webui/sanitize_ui/resources/BUILD.gn b/ash/webui/sanitize_ui/resources/BUILD.gn
new file mode 100644
index 0000000..62e052a
--- /dev/null
+++ b/ash/webui/sanitize_ui/resources/BUILD.gn
@@ -0,0 +1,24 @@
+# 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.
+
+import("//build/config/chromeos/ui_mode.gni")
+import("//ui/webui/resources/tools/build_webui.gni")
+
+assert(is_chromeos_ash, "Non-ChromeOS builds cannot depend on //ash")
+
+build_webui("build") {
+  static_files = [ "index.html" ]
+
+  web_component_files = [ "sanitize_done.ts" ]
+
+  ts_composite = true
+  ts_deps = [
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/js:build_ts",
+  ]
+
+  webui_context_type = "trusted"
+  grd_prefix = "ash_sanitize_app"
+  grit_output_dir = "$root_gen_dir/ash/webui"
+}
diff --git a/ash/webui/sanitize_ui/resources/index.html b/ash/webui/sanitize_ui/resources/index.html
new file mode 100644
index 0000000..04b7d83
--- /dev/null
+++ b/ash/webui/sanitize_ui/resources/index.html
@@ -0,0 +1,15 @@
+<!-- Copyright 2024 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<!DOCTYPE html>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
+  <head>
+    <title></title>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <sanitize-done></sanitize-done>
+    <script type="module" src="sanitize_done.js"></script>
+  </body>
+</html>
+
diff --git a/ash/webui/sanitize_ui/resources/sanitize_done.html b/ash/webui/sanitize_ui/resources/sanitize_done.html
new file mode 100644
index 0000000..743263b
--- /dev/null
+++ b/ash/webui/sanitize_ui/resources/sanitize_done.html
@@ -0,0 +1 @@
+<div id="header"></div>
diff --git a/ash/webui/sanitize_ui/resources/sanitize_done.ts b/ash/webui/sanitize_ui/resources/sanitize_done.ts
new file mode 100644
index 0000000..6e99321
--- /dev/null
+++ b/ash/webui/sanitize_ui/resources/sanitize_done.ts
@@ -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.
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './sanitize_done.html.js';
+
+export interface SanitizeDoneElement {
+  $: {
+    header: HTMLDivElement,
+  };
+}
+
+/**
+ * @fileoverview
+ * 'sanitize-done' is a dialog shown after reverting to safe settings
+ * (aka sanitize).
+ */
+export class SanitizeDoneElement extends PolymerElement {
+  static get is() {
+    return 'sanitize-done' as const;
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  override ready() {
+    super.ready();
+    this.$.header.textContent = 'Sanitize Done';
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    [SanitizeDoneElement.is]: SanitizeDoneElement;
+  }
+}
+customElements.define(SanitizeDoneElement.is, SanitizeDoneElement);
diff --git a/ash/webui/sanitize_ui/sanitize_ui.cc b/ash/webui/sanitize_ui/sanitize_ui.cc
new file mode 100644
index 0000000..ce36754
--- /dev/null
+++ b/ash/webui/sanitize_ui/sanitize_ui.cc
@@ -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.
+
+#include "ash/webui/sanitize_ui/sanitize_ui.h"
+
+#include "ash/webui/common/trusted_types_util.h"
+#include "ash/webui/grit/ash_sanitize_app_resources.h"
+#include "ash/webui/grit/ash_sanitize_app_resources_map.h"
+#include "ash/webui/sanitize_ui/url_constants.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/resources/grit/webui_resources.h"
+
+namespace ash {
+
+SanitizeDialogUI::SanitizeDialogUI(content::WebUI* web_ui)
+    : ui::MojoWebDialogUI(web_ui) {
+  content::WebUIDataSource* html_source =
+      content::WebUIDataSource::CreateAndAdd(
+          web_ui->GetWebContents()->GetBrowserContext(),
+          kChromeUISanitizeAppHost);
+  html_source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ScriptSrc,
+      "script-src chrome://resources chrome://webui-test 'self';");
+  ash::EnableTrustedTypesCSP(html_source);
+  html_source->EnableReplaceI18nInJS();
+
+  const auto resources =
+      base::make_span(kAshSanitizeAppResources, kAshSanitizeAppResourcesSize);
+  html_source->AddResourcePaths(resources);
+  html_source->AddResourcePath("", IDR_ASH_SANITIZE_APP_INDEX_HTML);
+  html_source->AddResourcePath("test_loader.html", IDR_WEBUI_TEST_LOADER_HTML);
+  html_source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
+  html_source->AddResourcePath("test_loader_util.js",
+                               IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
+}
+
+SanitizeDialogUI::~SanitizeDialogUI() {}
+
+WEB_UI_CONTROLLER_TYPE_IMPL(SanitizeDialogUI)
+
+}  // namespace ash
diff --git a/ash/webui/sanitize_ui/sanitize_ui.h b/ash/webui/sanitize_ui/sanitize_ui.h
new file mode 100644
index 0000000..dec1f0e
--- /dev/null
+++ b/ash/webui/sanitize_ui/sanitize_ui.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 ASH_WEBUI_SANITIZE_UI_SANITIZE_UI_H_
+#define ASH_WEBUI_SANITIZE_UI_SANITIZE_UI_H_
+
+#include "ash/webui/common/chrome_os_webui_config.h"
+#include "ash/webui/sanitize_ui/url_constants.h"
+#include "ui/web_dialogs/web_dialog_ui.h"
+
+namespace ash {
+class SanitizeDialogUI;
+
+// The WebDialogUIConfig for chrome://sanitize
+class SanitizeDialogUIConfig : public ChromeOSWebUIConfig<SanitizeDialogUI> {
+ public:
+  explicit SanitizeDialogUIConfig(
+      CreateWebUIControllerFunc create_controller_func)
+      : ChromeOSWebUIConfig(content::kChromeUIScheme,
+                            ash::kChromeUISanitizeAppHost,
+                            create_controller_func) {}
+};
+
+// The WebDialogUI for chrome://sanitize
+class SanitizeDialogUI : public ui::MojoWebDialogUI {
+ public:
+  explicit SanitizeDialogUI(content::WebUI* web_ui);
+  ~SanitizeDialogUI() override;
+
+  SanitizeDialogUI(const SanitizeDialogUI&) = delete;
+  SanitizeDialogUI& operator=(const SanitizeDialogUI&) = delete;
+
+ private:
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_SANITIZE_UI_SANITIZE_UI_H_
diff --git a/ash/webui/sanitize_ui/url_constants.cc b/ash/webui/sanitize_ui/url_constants.cc
new file mode 100644
index 0000000..660df2e
--- /dev/null
+++ b/ash/webui/sanitize_ui/url_constants.cc
@@ -0,0 +1,12 @@
+// 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/sanitize_ui/url_constants.h"
+
+namespace ash {
+
+const char kChromeUISanitizeAppHost[] = "sanitize";
+const char kChromeUISanitizeAppUrl[] = "chrome://sanitize";
+
+}  // namespace ash
diff --git a/ash/webui/sanitize_ui/url_constants.h b/ash/webui/sanitize_ui/url_constants.h
new file mode 100644
index 0000000..b6edc5861
--- /dev/null
+++ b/ash/webui/sanitize_ui/url_constants.h
@@ -0,0 +1,10 @@
+// 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_WEBUI_SANITIZE_UI_URL_CONSTANTS_H_
+#define ASH_WEBUI_SANITIZE_UI_URL_CONSTANTS_H_
+namespace ash {
+extern const char kChromeUISanitizeAppHost[];
+extern const char kChromeUISanitizeAppUrl[];
+}  // namespace ash
+#endif  // ASH_WEBUI_SANITIZE_UI_URL_CONSTANTS_H_
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
index 9d17258..c6ce8984 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
@@ -70,6 +70,7 @@
         should-ignore-key-release="[[hasError]]"
         update-on-key-press
         display-prerewritten-key-events
+        has-function-key="[[hasFunctionKey]]"
         ignore-blur>
     </shortcut-input>
   </template>
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
index 4e0c697f..e7447d1 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
@@ -6,13 +6,14 @@
 import 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input_key.js';
 import 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input.js';
 
+import {getInstance as getAnnouncerInstance} from 'chrome://resources/ash/common/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
+import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {KeyEvent} from 'chrome://resources/ash/common/shortcut_input_ui/input_device_settings.mojom-webui.js';
 import {ShortcutInputElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input.js';
 import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
-import {getInstance as getAnnouncerInstance} from 'chrome://resources/ash/common/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
-import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {mojoString16ToString} from 'chrome://resources/js/mojo_type_util.js';
 import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
 import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
@@ -150,6 +151,11 @@
 
       /** Whether to show a launcher icon or search icon for meta key. */
       hasLauncherButton: Boolean,
+
+      hasFunctionKey: {
+        type: Boolean,
+        value: loadTimeData.getBoolean('hasFunctionKey'),
+      },
     };
   }
 
@@ -170,6 +176,7 @@
   pendingKeyEvent: KeyEvent|null = null;
   shortcutInput: ShortcutInputElement|null;
   defaultAccelerators: Accelerator[];
+  hasFunctionKey: boolean;
   protected isCapturing: boolean;
   protected lastAccelerator: Accelerator;
   protected lastResult: AcceleratorConfigResult;
diff --git a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
index 8575dda..b020c0bb 100644
--- a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
+++ b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc
@@ -247,6 +247,9 @@
       ash::features::IsJellyEnabledForShortcutCustomization());
   html_source->AddBoolean("isInputDeviceSettingsSplitEnabled",
                           features::IsInputDeviceSettingsSplitEnabled());
+  html_source->AddBoolean(
+      "hasFunctionKey",
+      Shell::Get()->keyboard_capability()->HasFunctionKeyOnAnyKeyboard());
 }
 
 }  // namespace
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index 7aae1dbb..ab0ad0ee 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -303,6 +303,8 @@
 
   hover_container_->layer()->SetOpacity(0.0f);
   icon_container_view_->layer()->SetOpacity(1.0f);
+
+  AddAccelerator(ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN));
 }
 
 SavedDeskItemView::~SavedDeskItemView() {
@@ -564,6 +566,18 @@
   return Button::GetKeyClickActionForEvent(event);
 }
 
+bool SavedDeskItemView::AcceleratorPressed(const ui::Accelerator& accelerator) {
+  if (accelerator.IsCtrlDown() && accelerator.key_code() == ui::VKEY_W) {
+    OnDeleteButtonPressed();
+    return true;
+  }
+  return views::Button::AcceleratorPressed(accelerator);
+}
+
+bool SavedDeskItemView::CanHandleAccelerators() const {
+  return HasFocus() && views::Button::CanHandleAccelerators();
+}
+
 void SavedDeskItemView::UpdateSavedDeskName() {
   saved_desk_->set_template_name(name_view_->GetText());
   OnSavedDeskNameChanged(saved_desk_->template_name());
diff --git a/ash/wm/desks/templates/saved_desk_item_view.h b/ash/wm/desks/templates/saved_desk_item_view.h
index 3454e05c..32e922e 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.h
+++ b/ash/wm/desks/templates/saved_desk_item_view.h
@@ -120,6 +120,8 @@
   void OnFocus() override;
   void OnBlur() override;
   KeyClickAction GetKeyClickActionForEvent(const ui::KeyEvent& event) override;
+  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
+  bool CanHandleAccelerators() const override;
 
   // views::TextfieldController:
   void ContentsChanged(views::Textfield* sender,
diff --git a/ash/wm/desks/templates/saved_desk_save_desk_button.cc b/ash/wm/desks/templates/saved_desk_save_desk_button.cc
index 7fdb2be6..24273b5 100644
--- a/ash/wm/desks/templates/saved_desk_save_desk_button.cc
+++ b/ash/wm/desks/templates/saved_desk_save_desk_button.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/desks/templates/saved_desk_save_desk_button.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/style/style_util.h"
 #include "ash/wm/desks/templates/saved_desk_constants.h"
 #include "ash/wm/overview/overview_utils.h"
@@ -29,12 +30,14 @@
       button_type_(button_type) {
   auto* focus_ring = views::FocusRing::Get(this);
   focus_ring->SetOutsetFocusRingDisabled(true);
-  focus_ring->SetHasFocusPredicate(
-      base::BindRepeating([](const views::View* view) {
-        const auto* v = views::AsViewClass<SavedDeskSaveDeskButton>(view);
-        CHECK(v);
-        return v->is_focused();
-      }));
+  if (!features::IsOverviewNewFocusEnabled()) {
+    focus_ring->SetHasFocusPredicate(
+        base::BindRepeating([](const views::View* view) {
+          const auto* v = views::AsViewClass<SavedDeskSaveDeskButton>(view);
+          CHECK(v);
+          return v->is_focused();
+        }));
+  }
 
   SetBorder(std::make_unique<views::HighlightBorder>(
       kSaveDeskCornerRadius,
diff --git a/ash/wm/desks/templates/saved_desk_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
index e23cc11..b6b83d4 100644
--- a/ash/wm/desks/templates/saved_desk_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -1969,11 +1969,14 @@
 }
 
 TEST_F(SavedDeskTest, OverviewTabbing) {
+  const base::Time saved_desk_creation_time =
+      base::Time::FromSecondsSinceUnixEpoch(10);
+
   auto test_window = CreateAppWindow();
-  AddEntry(base::Uuid::GenerateRandomV4(), "template1", base::Time::Now(),
-           DeskTemplateType::kTemplate);
-  AddEntry(base::Uuid::GenerateRandomV4(), "template2", base::Time::Now(),
-           DeskTemplateType::kTemplate);
+  AddEntry(base::Uuid::GenerateRandomV4(), "template1",
+           saved_desk_creation_time, DeskTemplateType::kTemplate);
+  AddEntry(base::Uuid::GenerateRandomV4(), "template2",
+           saved_desk_creation_time, DeskTemplateType::kTemplate);
 
   OpenOverviewAndShowSavedDeskGrid();
   SavedDeskItemView* first_item = GetItemViewFromSavedDeskGrid(0);
@@ -1993,6 +1996,14 @@
   // Testing that we traverse to the `name_view` of the second item.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_EQ(second_item->name_view(), GetFocusedView());
+
+  // Traversing name views should not update the template if there was no
+  // editing.
+  std::vector<raw_ptr<const DeskTemplate, VectorExperimental>> entries =
+      GetAllEntries();
+  ASSERT_EQ(2u, entries.size());
+  EXPECT_EQ(saved_desk_creation_time, entries[0]->GetLastUpdatedTime());
+  EXPECT_EQ(saved_desk_creation_time, entries[1]->GetLastUpdatedTime());
 }
 
 // Tests that if the templates button is invisible, it is not part of the
diff --git a/ash/wm/overview/overview_focus_cycler.cc b/ash/wm/overview/overview_focus_cycler.cc
index ca407485..3a06cfd 100644
--- a/ash/wm/overview/overview_focus_cycler.cc
+++ b/ash/wm/overview/overview_focus_cycler.cc
@@ -111,7 +111,8 @@
     }
 
     // Skip this widget if it has no focusable views. (i.e. Saved desks library
-    // with all saved desks deleted.)
+    // with all saved desks deleted or saved desk button container with all
+    // buttons disabled.)
     if (!widget->GetFocusManager()->GetNextFocusableView(
             /*starting_view=*/nullptr, widget, /*reverse=*/false,
             /*dont_loop=*/false)) {
@@ -125,6 +126,7 @@
   OverviewGrid* primary_grid =
       overview_session_->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
   maybe_add_widget(primary_grid->pine_widget());
+  maybe_add_widget(primary_grid->save_desk_button_container_widget());
   maybe_add_widget(primary_grid->feedback_widget());
   maybe_add_widget(primary_grid->birch_bar_widget());
   maybe_add_widget(primary_grid->saved_desk_library_widget());
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 838fe79..f7560ae 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -325,7 +325,7 @@
       Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
   params.activatable =
       (spoken_feedback_enabled || features::IsOverviewNewFocusEnabled())
-          ? views::Widget::InitParams::Activatable::kDefault
+          ? views::Widget::InitParams::Activatable::kYes
           : views::Widget::InitParams::Activatable::kNo;
   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
diff --git a/ash/wm/overview/overview_grid_unittest.cc b/ash/wm/overview/overview_grid_unittest.cc
index ae188b2..5bdb9ee 100644
--- a/ash/wm/overview/overview_grid_unittest.cc
+++ b/ash/wm/overview/overview_grid_unittest.cc
@@ -8,6 +8,7 @@
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/overview/overview_item.h"
+#include "ash/wm/overview/overview_test_base.h"
 #include "ash/wm/overview/overview_test_util.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -33,7 +34,7 @@
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kFasterSplitScreenSetup,
                               features::kOsSettingsRevampWayfinding},
-        /*disabled_features=*/{});
+        /*disabled_features=*/{features::kForestFeature});
   }
 
   OverviewGridTest(const OverviewGridTest&) = delete;
@@ -320,4 +321,289 @@
   EXPECT_FALSE(item3->should_animate_when_entering());
 }
 
+class OverviewGridForestTest : public OverviewTestBase {
+ public:
+  OverviewGridForestTest() {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kFasterSplitScreenSetup,
+                              features::kOsSettingsRevampWayfinding,
+                              features::kForestFeature},
+        /*disabled_features=*/{});
+  }
+  OverviewGridForestTest(const OverviewGridForestTest&) = delete;
+  OverviewGridForestTest& operator=(const OverviewGridForestTest&) = delete;
+  ~OverviewGridForestTest() override = default;
+
+  // Calculates `OverviewItemBase::should_animate_when_exiting_`. The reason we
+  // do it like this is because this is normally called during shutdown, and
+  // then the grid and items objects are destroyed. Note that this function
+  // assumes one root window.
+  void CalculateShouldAnimateWhenExiting(
+      OverviewItemBase* selected_item = nullptr) {
+    ASSERT_EQ(1u, Shell::GetAllRootWindows().size());
+
+    OverviewGrid* grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
+    ASSERT_TRUE(grid);
+    grid->CalculateWindowListAnimationStates(selected_item,
+                                             OverviewTransition::kExit,
+                                             /*target_bounds=*/{});
+  }
+
+  // Checks expected against actual enter and exit animation values. Note that
+  // this function assumes one root window.
+  void VerifyAnimationStates(
+      const std::vector<bool>& expected_enter_animations,
+      const std::vector<bool>& expected_exit_animations) {
+    ASSERT_EQ(1u, Shell::GetAllRootWindows().size());
+
+    OverviewGrid* grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
+    ASSERT_TRUE(grid);
+
+    const std::vector<std::unique_ptr<OverviewItemBase>>& overview_items =
+        grid->window_list();
+    if (!expected_enter_animations.empty()) {
+      ASSERT_EQ(overview_items.size(), expected_enter_animations.size());
+      for (size_t i = 0; i < overview_items.size(); ++i) {
+        EXPECT_EQ(expected_enter_animations[i],
+                  overview_items[i]->should_animate_when_entering());
+      }
+    }
+    if (!expected_exit_animations.empty()) {
+      ASSERT_EQ(overview_items.size(), expected_exit_animations.size());
+      for (size_t i = 0; i < overview_items.size(); ++i) {
+        EXPECT_EQ(expected_exit_animations[i],
+                  overview_items[i]->should_animate_when_exiting());
+      }
+    }
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that with only one window, we always animate.
+TEST_F(OverviewGridForestTest, AnimateWithSingleWindow) {
+  auto window = CreateAppWindow(gfx::Rect(100, 100));
+  ToggleOverview();
+  VerifyAnimationStates({true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true});
+}
+
+// Tests that are animations if the destination bounds are shown.
+TEST_F(OverviewGridForestTest, SourceHiddenDestinationShown) {
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+  auto window2 = CreateAppWindow(gfx::Rect(200, 200));
+
+  ToggleOverview();
+  VerifyAnimationStates({true, true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true});
+}
+
+// Tests that are animations if the source bounds are shown.
+TEST_F(OverviewGridForestTest, SourceShownDestinationHidden) {
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+  WindowState::Get(window1.get())->Maximize();
+
+  auto window2 = CreateAppWindow(gfx::Rect(400, 400));
+
+  ToggleOverview();
+  VerifyAnimationStates({true, true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true});
+}
+
+// Tests that a window that is in the union of two other windows, but is still
+// shown will be animated.
+TEST_F(OverviewGridForestTest, SourceShownButInTheUnionOfTwoOtherWindows) {
+  // Create three windows, the union of the first two windows will be
+  // gfx::Rect(0, 0, 200, 200). Window 3 will be in that union, but should still
+  // animate since its not fully occluded.
+  auto window3 = CreateAppWindow(gfx::Rect(50, 200));
+  auto window2 = CreateAppWindow(gfx::Rect(50, 50, 150, 150));
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+
+  ToggleOverview();
+  VerifyAnimationStates({true, true, true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true, true});
+}
+
+// Tests that an always on top window will still be animated even if its source
+// and destination bounds are covered.
+TEST_F(OverviewGridForestTest, AlwaysOnTopWindow) {
+  UpdateDisplay("800x600");
+
+  // Create two windows, even if `window1` is maximized, `window2` will still
+  // animate since it is always on top.
+  auto window2 = CreateAppWindow(gfx::Rect(100, 100));
+  window2->SetProperty(aura::client::kZOrderingKey,
+                       ui::ZOrderLevel::kFloatingWindow);
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+  WindowState::Get(window1.get())->Maximize();
+
+  ToggleOverview();
+  VerifyAnimationStates({true, true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true});
+}
+
+// Tests that windows that are minimized are animated as expected.
+TEST_F(OverviewGridForestTest, MinimizedWindows) {
+  UpdateDisplay("800x600");
+
+  auto window3 = CreateAppWindow(gfx::Rect(800, 600));
+  WindowState::Get(window3.get())->Minimize();
+  auto window2 = CreateAppWindow(gfx::Rect(800, 600));
+  WindowState::Get(window2.get())->Minimize();
+  auto window1 = CreateAppWindow(gfx::Rect(10, 10, 780, 580));
+
+  // The minimized windows do not animate since their source is hidden, and
+  // their destination is blocked by the near maximized window.
+  ToggleOverview();
+  VerifyAnimationStates({true, false, false}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, false, false});
+}
+
+TEST_F(OverviewGridForestTest, SelectedWindow) {
+  // Create 3 windows with the third window being maximized. All windows are
+  // visible on entering, so they should all be animated. On exit we select the
+  // third window which is maximized, so the other two windows should not
+  // animate.
+  auto window3 = CreateAppWindow(gfx::Rect(400, 400));
+  WindowState::Get(window3.get())->Maximize();
+  auto window2 = CreateAppWindow(gfx::Rect(400, 400));
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+
+  ToggleOverview();
+  VerifyAnimationStates({true, true, true}, {});
+
+  OverviewItemBase* selected_item = GetOverviewItemForWindow(window3.get());
+  CalculateShouldAnimateWhenExiting(selected_item);
+  VerifyAnimationStates({}, {false, false, true});
+}
+
+TEST_F(OverviewGridForestTest, WindowWithBackdrop) {
+  // Create one non resizable window and one normal window and verify that the
+  // backdrop shows over the non resizable window, and that normal window
+  // becomes maximized upon entering tablet mode.
+  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
+  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
+  window1->SetProperty(aura::client::kResizeBehaviorKey,
+                       aura::client::kResizeBehaviorNone);
+  wm::ActivateWindow(window1.get());
+
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  BackdropController* backdrop_controller =
+      GetWorkspaceControllerForContext(window1.get())
+          ->layout_manager()
+          ->backdrop_controller();
+  EXPECT_EQ(window1.get(), backdrop_controller->GetTopmostWindowWithBackdrop());
+  EXPECT_TRUE(backdrop_controller->backdrop_window());
+  EXPECT_TRUE(WindowState::Get(window2.get())->IsMaximized());
+
+  // Tests that the second window despite being larger than the first window
+  // does not animate as it is hidden behind the backdrop. On exit, it still
+  // animates as the backdrop is not visible yet.
+  ToggleOverview();
+  VerifyAnimationStates({true, false}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true});
+}
+
+TEST_F(OverviewGridForestTest, SourcePartiallyOffscreenWindow) {
+  UpdateDisplay("500x400");
+
+  // Create `window2` to be partially offscreen.
+  auto window2 = CreateAppWindow(gfx::Rect(450, 100, 100, 100));
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+
+  // Tests that it still animates because the onscreen portion is not occluded
+  // by `window1`.
+  ToggleOverview();
+  VerifyAnimationStates({true, true}, {});
+
+  CalculateShouldAnimateWhenExiting();
+  VerifyAnimationStates({}, {true, true});
+  ToggleOverview();
+
+  // Maximize `window1`. `window2` should no longer animate since the parts of
+  // it that are onscreen are fully occluded.
+  WindowState::Get(window1.get())->Maximize();
+  ToggleOverview();
+  VerifyAnimationStates({true, false}, {});
+}
+
+// Tests that windows whose destination is partially or fully offscreen never
+// animate.
+TEST_F(OverviewGridForestTest, PartialAndFullOffscreenWindow) {
+  UpdateDisplay("800x600");
+
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+
+  // With this display size, the 9th and 10th windows will be partially
+  // offscreen and the 11th and 12th windows will be fully offscreen once we
+  // enter overview. Since the earlier created windows are lower on the MRU
+  // order, this equals the 3rd and 4th windows and the 1st and 2nd window
+  // respectively.
+  std::vector<std::unique_ptr<aura::Window>> windows;
+  for (int i = 0; i < 12; ++i) {
+    windows.push_back(CreateAppWindow(gfx::Rect(100, 100)));
+  }
+
+  // Enter overview and assert that we have one partially offscreen overview
+  // item and one fully offscreen overview item.
+  ToggleOverview();
+  OverviewItemBase* partially_offscreen_item =
+      GetOverviewItemForWindow(windows[2].get());
+  OverviewItemBase* fully_offscreen_item =
+      GetOverviewItemForWindow(windows[0].get());
+  ASSERT_FALSE(gfx::RectF(800.f, 600.f)
+                   .Contains(partially_offscreen_item->target_bounds()));
+  ASSERT_TRUE(gfx::RectF(800.f, 600.f)
+                  .Intersects(partially_offscreen_item->target_bounds()));
+  ASSERT_FALSE(
+      gfx::RectF(800.f, 600.f).Contains(fully_offscreen_item->target_bounds()));
+  ASSERT_FALSE(gfx::RectF(800.f, 600.f)
+                   .Intersects(fully_offscreen_item->target_bounds()));
+  EXPECT_FALSE(partially_offscreen_item->should_animate_when_entering());
+  EXPECT_FALSE(fully_offscreen_item->should_animate_when_entering());
+
+  CalculateShouldAnimateWhenExiting();
+  EXPECT_FALSE(partially_offscreen_item->should_animate_when_exiting());
+  EXPECT_FALSE(fully_offscreen_item->should_animate_when_exiting());
+}
+
+// Tests that only one window animates when entering overview from splitview
+// double snapped.
+TEST_F(OverviewGridForestTest, SnappedWindow) {
+  auto window3 = CreateAppWindow(gfx::Rect(100, 100));
+  auto window2 = CreateAppWindow(gfx::Rect(100, 100));
+  auto window1 = CreateAppWindow(gfx::Rect(100, 100));
+
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  auto* split_view_controller =
+      SplitViewController::Get(Shell::GetPrimaryRootWindow());
+  split_view_controller->SnapWindow(window1.get(), SnapPosition::kPrimary);
+
+  // Snap `window2` and check that `window3` is maximized.
+  split_view_controller->SnapWindow(window2.get(), SnapPosition::kSecondary);
+  EXPECT_TRUE(WindowState::Get(window3.get())->IsMaximized());
+
+  // Tests that `window3` is not animated even though its bounds are larger than
+  // `window2` because it is fully occluded by `window1` + `window2` and the
+  // split view divider.
+  ToggleOverview();
+  VerifyAnimationStates({true, false}, {});
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 33df687b..235ba4c8 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -1527,6 +1527,10 @@
       break;
     }
     case ui::VKEY_RETURN: {
+      if (!focus_cycler_old_) {
+        return;
+      }
+
       if (focus_cycler_old_ && !focus_cycler_old_->MaybeActivateFocusedView()) {
         return;
       }
diff --git a/build/fuchsia/test/test_server.py b/build/fuchsia/test/test_server.py
index e3af0c5..8166e08cf 100644
--- a/build/fuchsia/test/test_server.py
+++ b/build/fuchsia/test/test_server.py
@@ -10,7 +10,7 @@
 
 from typing import List, Optional, Tuple
 
-from common import DIR_SRC_ROOT, run_ffx_command
+from common import DIR_SRC_ROOT, get_ssh_address
 from compatible_utils import get_ssh_prefix
 
 sys.path.append(os.path.join(DIR_SRC_ROOT, 'build', 'util', 'lib', 'common'))
@@ -19,11 +19,14 @@
 # pylint: enable=import-error,wrong-import-position
 
 
-def port_forward(host_port_pair: str, host_port: int) -> int:
-    """Establishes a port forwarding SSH task to a localhost TCP endpoint
-    hosted at port |local_port|. Blocks until port forwarding is established.
+def ports_forward(host_port_pair: str,
+                  ports: List[Tuple[int, int]]) -> subprocess.CompletedProcess:
+    """Establishes a port forwarding SSH task to forward ports from fuchsia to
+    the local endpoints specified by tuples of port numbers. Blocks until port
+    forwarding is established.
 
-    Returns the remote port number."""
+    Returns the CompletedProcess of the SSH task."""
+    assert len(ports) > 0
 
     ssh_prefix = get_ssh_prefix(host_port_pair)
 
@@ -33,11 +36,11 @@
     forward_cmd = [
         '-O',
         'forward',  # Send SSH mux control signal.
-        '-R',
-        '0:localhost:%d' % host_port,
         '-v',  # Get forwarded port info from stderr.
         '-NT'  # Don't execute command; don't allocate terminal.
     ]
+    for port in ports:
+        forward_cmd.extend(['-R', f'{port[0]}:localhost:{port[1]}'])
     forward_proc = subprocess.run(ssh_prefix + forward_cmd,
                                   capture_output=True,
                                   check=False,
@@ -46,9 +49,17 @@
         raise Exception(
             'Got an error code when requesting port forwarding: %d' %
             forward_proc.returncode)
+    return forward_proc
 
-    output = forward_proc.stdout
-    parsed_port = int(output.splitlines()[0].strip())
+
+def port_forward(host_port_pair: str, host_port: int) -> int:
+    """Establishes a port forwarding SSH task to a localhost TCP endpoint
+    hosted at port |local_port|. Blocks until port forwarding is established.
+
+    Returns the remote port number."""
+
+    forward_proc = ports_forward(host_port_pair, [(0, host_port)])
+    parsed_port = int(forward_proc.stdout.splitlines()[0].strip())
     logging.debug('Port forwarding established (local=%d, device=%d)',
                   host_port, parsed_port)
     return parsed_port
@@ -110,9 +121,7 @@
 
     logging.debug('Starting test server.')
 
-    host_port_pair = run_ffx_command(cmd=('target', 'get-ssh-address'),
-                                     target_id=target_id,
-                                     capture_output=True).stdout.strip()
+    host_port_pair = get_ssh_address(target_id)
 
     # The TestLauncher can launch more jobs than the limit specified with
     # --test-launcher-jobs so the max number of spawned test servers is set to
diff --git a/build/fuchsia/test/test_server_unittests.py b/build/fuchsia/test/test_server_unittests.py
index f601884..209d452 100755
--- a/build/fuchsia/test/test_server_unittests.py
+++ b/build/fuchsia/test/test_server_unittests.py
@@ -75,7 +75,7 @@
         server = test_server.chrome_test_server_spawner.SpawningServer
         server.Start = mock.Mock()
         server_mock.return_value = server
-        with mock.patch('test_server.run_ffx_command'):
+        with mock.patch('test_server.get_ssh_address'):
             _, url = test_server.setup_test_server(_HOST_PORT_PAIR, 4)
         self.assertTrue(str(_HOST_PORT) in url)
 
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 84abe3a..ec32776 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -6253,6 +6253,9 @@
   <message name="IDS_SETTINGS_APP_PARENTAL_CONTROLS_FORGOT_PIN_LINK_NAME" desc="The name of the link which sends the user to an article with instructions to follow in the case of a forgotten App Parental Controls PIN.">
     Forgot PIN?
   </message>
+  <message name="IDS_SETTINGS_APP_PARENTAL_CONTROLS_PIN_MISMATCH_ERROR_TEXT" desc="The text displayed under the PIN input section that notifies the user that the re-entered PIN does not match the first PIN entered.">
+    PINs do not match
+  </message>
    <message name="IDS_OS_SETTINGS_REVAMP_APP_NOTIFICATIONS_DO_NOT_DISTURB_TOGGLE_DESCRIPTION" desc="The text description of the Do Not Disturb toggle within help icon tooltip.">
     Notifications won't pop up on the screen. You can still see notifications by clicking the Do Not Disturb icon on the bottom right of your screen.
   </message>
@@ -6379,8 +6382,8 @@
   <message name="IDS_SETTINGS_STORAGE_ITEM_AVAILABLE" desc="In Device Settings > Storage, label for the available storage size of ChromeOS internal storage.">
     Available
   </message>
-  <message name="IDS_SETTINGS_STORAGE_ITEM_MY_FILES" desc="In Device Settings > Storage, label for the size of local files in 'This Chromebook' folder.">
-    Files on this Chromebook
+  <message name="IDS_SETTINGS_STORAGE_ITEM_MY_FILES" desc="In Device Settings > Storage, label for the size of My files root.">
+    My files
   </message>
   <message name="IDS_SETTINGS_STORAGE_ITEM_BROWSING_DATA" desc="In Device Settings > Storage, label for the size of browsing data.">
     Browsing data
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_PARENTAL_CONTROLS_PIN_MISMATCH_ERROR_TEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_PARENTAL_CONTROLS_PIN_MISMATCH_ERROR_TEXT.png.sha1
new file mode 100644
index 0000000..d11b3db
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_PARENTAL_CONTROLS_PIN_MISMATCH_ERROR_TEXT.png.sha1
@@ -0,0 +1 @@
+f1cc16414f6d099dfdf67863e5469911eb679bd3
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_STORAGE_ITEM_MY_FILES.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_STORAGE_ITEM_MY_FILES.png.sha1
index dc28733..0945ced 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_STORAGE_ITEM_MY_FILES.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_STORAGE_ITEM_MY_FILES.png.sha1
@@ -1 +1 @@
-7ad8fc251b0b85d04d29a7cc5967e6383a01d508
\ No newline at end of file
+cae980b2c029aca2cb46b6f36dd6c3df40fb7e13
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 8a26cd3..fce727b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5373,6 +5373,7 @@
       "//ash/webui/projector_app",
       "//ash/webui/projector_app/mojom:annotator_mojo_bindings",
       "//ash/webui/projector_app/mojom:projector_mojo_bindings",
+      "//ash/webui/sanitize_ui",
       "//ash/webui/scanning",
       "//ash/webui/scanning/mojom",
       "//ash/webui/shimless_rma",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9d434a1..831d3b0e 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3991,6 +3991,10 @@
     {"stop-app-indexing-report", flag_descriptions::kStopAppIndexingReportName,
      flag_descriptions::kStopAppIndexingReportDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kStopAppIndexingReport)},
+    {"omnibox-rich-autocompletion-android",
+     flag_descriptions::kRichAutocompletionAndroidName,
+     flag_descriptions::kRichAutocompletionAndroidDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(omnibox::kRichAutocompletion)},
 #endif  // BUILDFLAG(IS_ANDROID)
     {"show-autofill-type-predictions",
      flag_descriptions::kShowAutofillTypePredictionsName,
@@ -5846,9 +5850,6 @@
      flag_descriptions::kFileTransferEnterpriseConnectorUIName,
      flag_descriptions::kFileTransferEnterpriseConnectorUIDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kFileTransferEnterpriseConnectorUI)},
-    {"files-app-experimental", flag_descriptions::kFilesAppExperimentalName,
-     flag_descriptions::kFilesAppExperimentalDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kFilesAppExperimental)},
     {"files-conflict-dialog", flag_descriptions::kFilesConflictDialogName,
      flag_descriptions::kFilesConflictDialogDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kFilesConflictDialog)},
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 24b54da..f089f23 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -856,6 +856,10 @@
     "child_accounts/on_device_controls/app_controls_service_factory.h",
     "child_accounts/on_device_controls/blocked_app_registry.cc",
     "child_accounts/on_device_controls/blocked_app_registry.h",
+    "child_accounts/on_device_controls/blocked_app_store.cc",
+    "child_accounts/on_device_controls/blocked_app_store.h",
+    "child_accounts/on_device_controls/blocked_app_types.cc",
+    "child_accounts/on_device_controls/blocked_app_types.h",
     "child_accounts/on_device_controls/on_device_utils.cc",
     "child_accounts/on_device_controls/on_device_utils.h",
     "child_accounts/parent_access_code/authenticator.cc",
@@ -2219,6 +2223,9 @@
     "login/users/avatar/user_image_file_selector.h",
     "login/users/avatar/user_image_loader.cc",
     "login/users/avatar/user_image_loader.h",
+    "login/users/avatar/user_image_loader_delegate.h",
+    "login/users/avatar/user_image_loader_delegate_impl.cc",
+    "login/users/avatar/user_image_loader_delegate_impl.h",
     "login/users/avatar/user_image_manager_impl.cc",
     "login/users/avatar/user_image_manager_impl.h",
     "login/users/avatar/user_image_manager_registry.cc",
@@ -5616,6 +5623,8 @@
     "child_accounts/on_device_controls/app_controls_test_base.cc",
     "child_accounts/on_device_controls/app_controls_test_base.h",
     "child_accounts/on_device_controls/blocked_app_registry_unittest.cc",
+    "child_accounts/on_device_controls/blocked_app_store_unittest.cc",
+    "child_accounts/on_device_controls/blocked_app_types_unittest.cc",
     "child_accounts/on_device_controls/on_device_utils_unittest.cc",
     "child_accounts/parent_access_code/authenticator_unittest.cc",
     "child_accounts/time_limit_notifier_unittest.cc",
@@ -5936,6 +5945,7 @@
     "login/ui/oobe_dialog_size_utils_unittest.cc",
     "login/user_online_signin_notifier_unittest.cc",
     "login/users/avatar/user_image_loader_unittest.cc",
+    "login/users/avatar/user_image_manager_impl_unittest.cc",
     "login/users/default_user_image/default_user_images_unittest.cc",
     "login/users/multi_user_sign_in_policy_controller_unittest.cc",
     "login/users/user_manager_unittest.cc",
@@ -6572,6 +6582,7 @@
     "//chromeos/ash/components/trash_service/public/cpp",
     "//chromeos/ash/components/trash_service/public/mojom",
     "//chromeos/ash/components/wifi_p2p",
+    "//chromeos/ash/resources",
     "//chromeos/ash/services/assistant/public/cpp",
     "//chromeos/ash/services/cros_healthd/public/cpp:test_support",
     "//chromeos/ash/services/cros_healthd/public/mojom",
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index eb74fae..428c56a 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -1859,6 +1859,10 @@
   base::UmaHistogramBoolean(
       "Accessibility.CrosSpokenFeedback.BrailleDisplayConnected",
       IsBrailleDisplayConnected());
+  if (::features::IsAccessibilityFaceGazeEnabled()) {
+    base::UmaHistogramBoolean("Accessibility.CrosFaceGaze",
+                              IsFaceGazeEnabled());
+  }
 }
 
 void AccessibilityManager::PlayVolumeAdjustSound() {
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.cc b/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.cc
index 342c713..42a7616 100644
--- a/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.cc
+++ b/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.h"
 
+#include <string>
+
 #include "ash/constants/ash_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 
@@ -11,6 +13,7 @@
 
 // static
 void AppControlsService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterStringPref(prefs::kOnDeviceAppControlsPin, std::string());
   registry->RegisterBooleanPref(prefs::kOnDeviceAppControlsSetupCompleted,
                                 false);
 }
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service_factory.cc b/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service_factory.cc
index bc9d255..4518f6ac 100644
--- a/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service_factory.cc
+++ b/chrome/browser/ash/child_accounts/on_device_controls/app_controls_service_factory.cc
@@ -9,6 +9,7 @@
 #include "ash/constants/ash_features.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/ash/child_accounts/on_device_controls/app_controls_service.h"
+#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.h"
 #include "chrome/browser/ash/child_accounts/on_device_controls/on_device_utils.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
@@ -67,6 +68,7 @@
 void AppControlsServiceFactory::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   AppControlsService::RegisterProfilePrefs(registry);
+  BlockedAppStore::RegisterProfilePrefs(registry);
 }
 
 }  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc
index 98a4042c..2149c26 100644
--- a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc
@@ -9,7 +9,6 @@
 #include "base/logging.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
@@ -24,26 +23,17 @@
 
 }  // namespace
 
-BlockedAppRegistry::AppDetails::AppDetails()
-    : AppDetails(base::TimeTicks::Now()) {}
-
-BlockedAppRegistry::AppDetails::AppDetails(base::TimeTicks block_timestamp)
-    : block_timestamp(block_timestamp) {}
-
-BlockedAppRegistry::AppDetails::~AppDetails() = default;
-
-// static
-void BlockedAppRegistry::RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterListPref(prefs::kOnDeviceAppControlsBlockedApps);
-}
-
 BlockedAppRegistry::BlockedAppRegistry(apps::AppServiceProxy* app_service,
                                        PrefService* pref_service)
-    : app_service_(app_service), pref_service_(pref_service) {
+    : store_(pref_service), app_service_(app_service) {
   CHECK(app_service_);
-  CHECK(pref_service_);
 
+  registry_ = store_.GetFromPref();
   app_registry_cache_observer_.Observe(&GetAppCache());
+
+  VLOG(1) << "app-controls: calling block apps to initialize the state in app "
+             "service";
+  app_service_->BlockApps(GetBlockedApps());
 }
 
 BlockedAppRegistry::~BlockedAppRegistry() = default;
@@ -55,11 +45,13 @@
     LOG(WARNING) << app_id << " already in blocked app registry";
     return;
   }
-  registry_[app_id] = AppDetails();
+  registry_[app_id] = BlockedAppDetails();
 
+  VLOG(1) << "app-controls: calling block app: " << app_id;
   app_service_->BlockApps({app_id});
 
-  // TODO(b/338247185): Update pref state.
+  // TODO(b/338247185): Only update value that changed.
+  store_.SaveToPref(registry_);
 }
 
 void BlockedAppRegistry::RemoveApp(const std::string& app_id) {
@@ -71,9 +63,11 @@
   }
   registry_.erase(app_id);
 
+  VLOG(1) << "app-controls: calling unblock app: " << app_id;
   app_service_->UnblockApps({app_id});
 
-  // TODO(b/338247185): Update pref state.
+  // TODO(b/338247185): Only update value that changed.
+  store_.SaveToPref(registry_);
 }
 
 std::set<std::string> BlockedAppRegistry::GetBlockedApps() {
@@ -89,7 +83,7 @@
     return LocalAppState::kAvailable;
   }
 
-  if (registry_.at(app_id).uninstall_timestamp.has_value()) {
+  if (!registry_.at(app_id).IsInstalled()) {
     return LocalAppState::kBlockedUninstalled;
   }
 
@@ -120,6 +114,8 @@
 }
 void BlockedAppRegistry::OnAppRegistryCacheWillBeDestroyed(
     apps::AppRegistryCache* cache) {
+  VLOG(1) << "app-controls: persisting apps before app service cache destroyed";
+  store_.SaveToPref(registry_);
   app_registry_cache_observer_.Reset();
 }
 
@@ -129,31 +125,32 @@
 
 void BlockedAppRegistry::OnAppReady(const std::string& app_id) {
   VLOG(1) << "app-controls: app ready " << app_id;
-
   if (GetAppState(app_id) == LocalAppState::kAvailable) {
     return;
   }
 
   if (GetAppState(app_id) == LocalAppState::kBlockedUninstalled) {
     // Clear the uninstall timestamp, but keep the initial blocked timestamp.
-    registry_[app_id].uninstall_timestamp.reset();
+    registry_[app_id].MarkInstalled();
+
+    // TODO(b/338247185): Only update value that changed.
+    store_.SaveToPref(registry_);
   }
 
+  VLOG(1) << "app-controls: calling block app: " << app_id;
   app_service_->BlockApps({app_id});
-
-  // TODO(b/338247185): Update pref state.
 }
 
 void BlockedAppRegistry::OnAppUninstalled(const std::string& app_id) {
   VLOG(1) << "app-controls: app uninstalled " << app_id;
-
   if (GetAppState(app_id) == LocalAppState::kAvailable) {
     return;
   }
 
-  registry_[app_id].uninstall_timestamp = base::TimeTicks::Now();
+  registry_[app_id].SetUninstallTimestamp(base::Time::Now());
 
-  // TODO(b/338247185): Update pref state.
+  // TODO(b/338247185): Only update value that changed.
+  store_.SaveToPref(registry_);
 }
 
 }  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.h b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.h
index 46ed457..4886e20 100644
--- a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.h
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.h
@@ -6,39 +6,25 @@
 #define CHROME_BROWSER_ASH_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_REGISTRY_H_
 
 #include <map>
-#include <optional>
 #include <set>
 #include <string>
 
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
-#include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
+#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.h"
+#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 
-class PrefRegistrySimple;
 class PrefService;
 
 namespace ash::on_device_controls {
 
-// State of the app
-enum class LocalAppState {
-  // App is not blocked by on device controls.
-  kAvailable = 0,
-  // App installed and blocked by on device controls.
-  kBlocked = 1,
-  // App uninstalled and blocked by on device controls.
-  // Used to block the app upon reinstallation.
-  kBlockedUninstalled = 2,
-};
-
 // Keeps track of blocked apps and persists blocked apps on the disk.
 // TODO(b/338246850): Handle app uninstall/reinstall.
 // TODO(b/338247185): Persist blocked apps in a pref.
 class BlockedAppRegistry : public apps::AppRegistryCache::Observer {
  public:
-  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
-
   BlockedAppRegistry(apps::AppServiceProxy* app_service,
                      PrefService* pref_service);
   BlockedAppRegistry(const BlockedAppRegistry&) = delete;
@@ -60,19 +46,6 @@
   LocalAppState GetAppState(const std::string& app_id) const;
 
  private:
-  struct AppDetails {
-    AppDetails();
-    explicit AppDetails(base::TimeTicks block_timestamp);
-    ~AppDetails();
-
-    // The timestamp when the app was uninstalled..
-    // If not populated app is currently installed.
-    std::optional<base::TimeTicks> uninstall_timestamp;
-
-    // The timestamp when the app was blocked.
-    base::TimeTicks block_timestamp;
-  };
-
   // apps::AppRegistryCache::Observer:
   void OnAppUpdate(const apps::AppUpdate& update) override;
   void OnAppRegistryCacheWillBeDestroyed(
@@ -89,12 +62,13 @@
 
   // The in-memory registry of the locked apps.
   // Maps blocked app id to blocked ap metadata.
-  std::map<std::string, AppDetails> registry_;
+  BlockedAppMap registry_;
+
+  // Manages persisting and restoring blocked apps.
+  BlockedAppStore store_;
 
   const raw_ptr<apps::AppServiceProxy> app_service_;
 
-  const raw_ptr<PrefService> pref_service_;
-
   base::ScopedObservation<apps::AppRegistryCache,
                           apps::AppRegistryCache::Observer>
       app_registry_cache_observer_{this};
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.cc
new file mode 100644
index 0000000..6f482d02
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.cc
@@ -0,0 +1,102 @@
+// 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/child_accounts/on_device_controls/blocked_app_store.h"
+
+#include "ash/constants/ash_pref_names.h"
+#include "base/json/values_util.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+
+namespace ash::on_device_controls {
+
+namespace {
+
+constexpr char kAppIdKey[] = "app_id";
+constexpr char kBlockTimestampKey[] = "block_timestamp";
+constexpr char kUninstallTimestampKey[] = "uninstall_timestamp";
+
+}  // namespace
+
+// static
+void BlockedAppStore::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterListPref(prefs::kOnDeviceAppControlsBlockedApps);
+}
+
+BlockedAppStore::BlockedAppStore(PrefService* pref_service)
+    : pref_service_(pref_service) {
+  CHECK(pref_service_);
+}
+
+BlockedAppStore::~BlockedAppStore() = default;
+
+BlockedAppMap BlockedAppStore::GetFromPref() const {
+  VLOG(1) << "app-controls: reading blocked apps from pref ";
+
+  const base::Value::List& list =
+      pref_service_->GetList(prefs::kOnDeviceAppControlsBlockedApps);
+
+  BlockedAppMap apps_from_pref;
+  for (const auto& item : list) {
+    const base::Value::Dict* dict = item.GetIfDict();
+    if (!dict) {
+      LOG(WARNING) << "app-controls: invalid block app dictionary";
+      continue;
+    }
+
+    const std::string* app_id = dict->FindString(kAppIdKey);
+    if (!app_id) {
+      LOG(WARNING) << "app-controls: invalid app id string";
+      continue;
+    }
+
+    std::optional<base::Time> block_timestamp =
+        base::ValueToTime(dict->Find(kBlockTimestampKey));
+    if (!block_timestamp) {
+      LOG(WARNING) << "app-controls: invalid app blocked timestamp";
+      continue;
+    }
+
+    std::optional<base::Time> uninstall_timestamp =
+        base::ValueToTime(dict->Find(kUninstallTimestampKey));
+
+    apps_from_pref[*app_id] =
+        uninstall_timestamp
+            ? BlockedAppDetails(*block_timestamp, *uninstall_timestamp)
+            : BlockedAppDetails(*block_timestamp);
+  }
+  return apps_from_pref;
+}
+
+void BlockedAppStore::SaveToPref(const BlockedAppMap& apps) {
+  VLOG(1) << "app-controls: saving blocked apps to pref ";
+
+  ScopedListPrefUpdate update(pref_service_,
+                              prefs::kOnDeviceAppControlsBlockedApps);
+
+  base::Value::List& list = update.Get();
+  list.clear();
+
+  for (const auto& app : apps) {
+    const std::string& app_id = app.first;
+    const BlockedAppDetails& app_details = app.second;
+
+    base::Value::Dict dict;
+    dict.Set(kAppIdKey, app_id);
+    dict.Set(kBlockTimestampKey,
+             base::TimeToValue(app_details.block_timestamp()));
+    if (!app_details.IsInstalled()) {
+      dict.Set(kUninstallTimestampKey,
+               base::TimeToValue(app_details.uninstall_timestamp().value()));
+    }
+    list.Append(std::move(dict));
+  }
+  pref_service_->CommitPendingWrite();
+}
+
+}  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.h b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.h
new file mode 100644
index 0000000..7c4a710
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store.h
@@ -0,0 +1,38 @@
+// 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_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_STORE_H_
+#define CHROME_BROWSER_ASH_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_STORE_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace ash::on_device_controls {
+
+// Persists blocked apps in the user pref and provides API to load and update
+// persisted data.
+class BlockedAppStore {
+ public:
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+  explicit BlockedAppStore(PrefService* pref_service);
+  BlockedAppStore(const BlockedAppStore&) = delete;
+  BlockedAppStore& operator=(const BlockedAppStore&) = delete;
+  ~BlockedAppStore();
+
+  // Returns the list of blocked apps stored in pref.
+  BlockedAppMap GetFromPref() const;
+  // Saves the list of blocked `apps` in pref.
+  void SaveToPref(const BlockedAppMap& apps);
+
+ private:
+  const raw_ptr<PrefService> pref_service_;
+};
+
+}  // namespace ash::on_device_controls
+
+#endif  // CHROME_BROWSER_ASH_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_STORE_H_
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store_unittest.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store_unittest.cc
new file mode 100644
index 0000000..08b0539
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_store_unittest.cc
@@ -0,0 +1,130 @@
+// 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/child_accounts/on_device_controls/blocked_app_store.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::on_device_controls {
+
+// Tests blocked app registry.
+class BlockedAppStoreTest : public testing::Test {
+ public:
+  BlockedAppStoreTest() = default;
+  BlockedAppStoreTest(const BlockedAppStoreTest&) = delete;
+  BlockedAppStoreTest& operator=(const BlockedAppStoreTest&) = delete;
+
+  ~BlockedAppStoreTest() override = default;
+
+ protected:
+  BlockedAppStore* store() { return store_.get(); }
+
+  // testing::Test:
+  void SetUp() override;
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+
+  std::unique_ptr<BlockedAppStore> store_;
+};
+
+void BlockedAppStoreTest::SetUp() {
+  testing::Test::SetUp();
+
+  store_ = std::make_unique<BlockedAppStore>(profile_.GetPrefs());
+}
+
+// Tests reading from the empty preference.
+TEST_F(BlockedAppStoreTest, TestGetFromEmptyPref) {
+  BlockedAppMap apps = store()->GetFromPref();
+
+  EXPECT_EQ(0UL, apps.size());
+}
+
+// Tests writing and reading one blocked app.
+TEST_F(BlockedAppStoreTest, TestBlockedApp) {
+  BlockedAppMap apps_in;
+
+  const std::string app_id = "abc";
+  const base::Time timestamp = base::Time::Now();
+  apps_in[app_id] = BlockedAppDetails(timestamp);
+
+  store()->SaveToPref(apps_in);
+
+  const BlockedAppMap apps_out = store()->GetFromPref();
+  EXPECT_EQ(1UL, apps_out.size());
+  ASSERT_TRUE(base::Contains(apps_out, app_id));
+  EXPECT_EQ(timestamp, apps_out.at(app_id).block_timestamp());
+  EXPECT_EQ(std::nullopt, apps_out.at(app_id).uninstall_timestamp());
+}
+
+// Tests writing and reading one uninstalled app.
+TEST_F(BlockedAppStoreTest, TestUninstalledApp) {
+  BlockedAppMap apps_in;
+
+  const std::string app_id = "abc";
+  const base::Time block_timestamp = base::Time::Now();
+  const base::Time uninstall_timestamp = base::Time::Now() + base::Minutes(5);
+  apps_in[app_id] = BlockedAppDetails(block_timestamp, uninstall_timestamp);
+
+  store()->SaveToPref(apps_in);
+
+  const BlockedAppMap apps_out = store()->GetFromPref();
+  EXPECT_EQ(1UL, apps_out.size());
+  ASSERT_TRUE(base::Contains(apps_out, app_id));
+  EXPECT_EQ(block_timestamp, apps_out.at(app_id).block_timestamp());
+  EXPECT_EQ(uninstall_timestamp, *apps_out.at(app_id).uninstall_timestamp());
+}
+
+// Tests writing and reading multiple apps.
+TEST_F(BlockedAppStoreTest, TestMultipleApps) {
+  const std::string base_app_id = "abc";
+  const base::Time initial_time = base::Time::Now();
+  const base::TimeDelta uninstall_delta = base::Hours(5);
+  const base::TimeDelta block_delta = base::Minutes(13);
+  const size_t apps_count = 10;
+
+  BlockedAppMap apps_in;
+  base::Time timestamp = initial_time;
+  // Adds apps with the different block and uninstall timestamps.
+  for (size_t i = 0; i < apps_count; ++i) {
+    const std::string app_id =
+        base::StrCat({base_app_id, base::NumberToString(i)});
+    apps_in[app_id] = BlockedAppDetails(timestamp, timestamp + uninstall_delta);
+    timestamp += block_delta;
+  }
+
+  store()->SaveToPref(apps_in);
+
+  const BlockedAppMap apps_out = store()->GetFromPref();
+  EXPECT_EQ(apps_count, apps_out.size());
+
+  for (size_t i = 0; i < apps_count; ++i) {
+    const std::string app_id =
+        base::StrCat({base_app_id, base::NumberToString(i)});
+    ASSERT_TRUE(base::Contains(apps_out, app_id));
+
+    const BlockedAppDetails& details = apps_out.at(app_id);
+
+    const base::Time expected_block_time = initial_time + i * block_delta;
+    EXPECT_EQ(expected_block_time, details.block_timestamp());
+
+    const base::Time expected_uninstall_time =
+        expected_block_time + uninstall_delta;
+    EXPECT_EQ(expected_uninstall_time, *details.uninstall_timestamp());
+  }
+}
+
+}  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.cc
new file mode 100644
index 0000000..1fb6988e
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.cc
@@ -0,0 +1,53 @@
+// 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/child_accounts/on_device_controls/blocked_app_types.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace ash::on_device_controls {
+
+BlockedAppDetails::BlockedAppDetails() : BlockedAppDetails(base::Time::Now()) {}
+
+BlockedAppDetails::BlockedAppDetails(base::Time block_timestamp)
+    : block_timestamp_(block_timestamp) {}
+
+BlockedAppDetails::BlockedAppDetails(base::Time block_timestamp,
+                                     base::Time uninstall_timestamp)
+    : block_timestamp_(block_timestamp),
+      uninstall_timestamp_(uninstall_timestamp) {
+  if (uninstall_timestamp_ && block_timestamp_ > uninstall_timestamp_) {
+    LOG(WARNING) << "app-controls: block timestamp after uninstall timestamp";
+  }
+}
+
+BlockedAppDetails::~BlockedAppDetails() = default;
+
+bool BlockedAppDetails::IsInstalled() const {
+  return !uninstall_timestamp_.has_value();
+}
+
+void BlockedAppDetails::MarkInstalled() {
+  if (IsInstalled()) {
+    LOG(WARNING) << "app-controls: installed app marked installed again";
+  }
+  uninstall_timestamp_.reset();
+}
+
+void BlockedAppDetails::SetUninstallTimestamp(base::Time timestamp) {
+  if (!IsInstalled()) {
+    LOG(WARNING) << "app-controls: uninstalled timestamp updated";
+  }
+  uninstall_timestamp_ = timestamp;
+}
+
+void BlockedAppDetails::SetBlockTimestamp(base::Time timestamp) {
+  if (uninstall_timestamp_ && block_timestamp_ > uninstall_timestamp_) {
+    LOG(WARNING) << "app-controls: block timestamp after uninstall timestamp";
+  }
+  block_timestamp_ = timestamp;
+}
+
+}  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h
new file mode 100644
index 0000000..6336842
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types.h
@@ -0,0 +1,68 @@
+// 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_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_TYPES_H_
+#define CHROME_BROWSER_ASH_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_TYPES_H_
+
+#include <map>
+#include <optional>
+#include <string>
+
+#include "base/time/time.h"
+
+namespace ash::on_device_controls {
+
+// State of the app in the context of on device app controls.
+enum class LocalAppState {
+  // App is not blocked by on device controls.
+  kAvailable = 0,
+  // App installed and blocked by on device controls.
+  kBlocked = 1,
+  // App uninstalled and blocked by on device controls.
+  // Used to block the app upon reinstallation.
+  kBlockedUninstalled = 2,
+};
+
+// The details of the app blocked on device.
+// Note: Those details are preserved in-between the sessions.
+class BlockedAppDetails {
+ public:
+  BlockedAppDetails();
+  explicit BlockedAppDetails(base::Time block_timestamp);
+  BlockedAppDetails(base::Time block_timestamp, base::Time uninstall_timestamp);
+
+  ~BlockedAppDetails();
+
+  base::Time block_timestamp() const { return block_timestamp_; }
+  std::optional<base::Time> uninstall_timestamp() const {
+    return uninstall_timestamp_;
+  }
+
+  // Returns whether the blocked app is currently installed.
+  bool IsInstalled() const;
+  // Marks app as installed by resetting the uninstall timestamp.
+  void MarkInstalled();
+
+  // Sets un-installation timestamp.
+  // Marks app as uninstalled if that timestamp was not previously set.
+  void SetUninstallTimestamp(base::Time timestamp);
+  // Sets block timestamp.
+  void SetBlockTimestamp(base::Time timestamp);
+
+ private:
+  // The timestamp when the app was blocked.
+  base::Time block_timestamp_;
+
+  // The timestamp when the app was uninstalled..
+  // If not populated app is currently installed.
+  std::optional<base::Time> uninstall_timestamp_;
+};
+
+// The map containing the record of the apps blocked on device.
+// Keyed by the App Service app id.
+typedef std::map<std::string, BlockedAppDetails> BlockedAppMap;
+
+}  // namespace ash::on_device_controls
+
+#endif  // CHROME_BROWSER_ASH_CHILD_ACCOUNTS_ON_DEVICE_CONTROLS_BLOCKED_APP_TYPES_H_
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types_unittest.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types_unittest.cc
new file mode 100644
index 0000000..501ab08
--- /dev/null
+++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_types_unittest.cc
@@ -0,0 +1,64 @@
+// 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/child_accounts/on_device_controls/blocked_app_types.h"
+
+#include <optional>
+
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::on_device_controls {
+
+using BlockedAppTypesTest = testing::Test;
+
+TEST_F(BlockedAppTypesTest, AppDetailsWithBlockTimestamp) {
+  const base::Time timestamp = base::Time::Now() + base::Hours(12);
+  BlockedAppDetails details = BlockedAppDetails(timestamp);
+
+  EXPECT_EQ(timestamp, details.block_timestamp());
+  EXPECT_EQ(std::nullopt, details.uninstall_timestamp());
+  EXPECT_TRUE(details.IsInstalled());
+}
+
+TEST_F(BlockedAppTypesTest, UninstalledAppDetails) {
+  const base::Time block_time = base::Time::Now() + base::Hours(12);
+  const base::Time uninstall_time = base::Time::Now() + base::Days(30);
+
+  BlockedAppDetails details = BlockedAppDetails(block_time, uninstall_time);
+
+  EXPECT_EQ(block_time, details.block_timestamp());
+  EXPECT_FALSE(details.IsInstalled());
+  EXPECT_EQ(uninstall_time, *details.uninstall_timestamp());
+}
+
+TEST_F(BlockedAppTypesTest, MarkUninstalled) {
+  const base::Time timestamp;
+  BlockedAppDetails details = BlockedAppDetails(timestamp, timestamp);
+  EXPECT_FALSE(details.IsInstalled());
+
+  details.MarkInstalled();
+  EXPECT_TRUE(details.IsInstalled());
+}
+
+TEST_F(BlockedAppTypesTest, SetBlockTimestamp) {
+  const base::Time timestamp = base::Time::Now();
+  BlockedAppDetails details = BlockedAppDetails(timestamp);
+  EXPECT_EQ(timestamp, details.block_timestamp());
+
+  base::Time new_block_time = base::Time::Now() + base::Minutes(30);
+  details.SetBlockTimestamp(new_block_time);
+  EXPECT_EQ(new_block_time, details.block_timestamp());
+}
+
+TEST_F(BlockedAppTypesTest, SetUninstallTimestamp) {
+  BlockedAppDetails details = BlockedAppDetails();
+  EXPECT_EQ(std::nullopt, details.uninstall_timestamp());
+
+  base::Time uninstall_time = base::Time::Now() + base::Minutes(30);
+  details.SetUninstallTimestamp(uninstall_time);
+  EXPECT_EQ(uninstall_time, details.uninstall_timestamp());
+}
+
+}  // namespace ash::on_device_controls
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index c7b601a..93b9b39 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -1072,7 +1072,6 @@
 
   PRINT_IF_NOT_DEFAULT(arc)
   PRINT_IF_NOT_DEFAULT(browser)
-  PRINT_IF_NOT_DEFAULT(files_experimental)
   PRINT_IF_NOT_DEFAULT(generic_documents_provider)
   PRINT_IF_NOT_DEFAULT(mount_volumes)
   PRINT_IF_NOT_DEFAULT(native_smb)
@@ -2366,12 +2365,6 @@
   // Make sure to run the ARC storage UI toast tests.
   enabled_features.push_back(arc::kUsbStorageUIFeature);
 
-  if (options.files_experimental) {
-    enabled_features.push_back(ash::features::kFilesAppExperimental);
-  } else {
-    disabled_features.push_back(ash::features::kFilesAppExperimental);
-  }
-
   if (options.enable_conflict_dialog) {
     enabled_features.push_back(ash::features::kFilesConflictDialog);
   } else {
@@ -2886,14 +2879,6 @@
     return;
   }
 
-  if (name == "isFilesAppExperimental") {
-    // Return whether the flag Files Experimental is enabled.
-    *output = base::FeatureList::IsEnabled(ash::features::kFilesAppExperimental)
-                  ? "true"
-                  : "false";
-    return;
-  }
-
   if (name == "isInGuestMode") {
     // Obtain if the test runs in guest or incognito mode.
     LOG(INFO) << GetTestCaseName() << " is in " << options.guest_mode
@@ -3844,11 +3829,6 @@
     return;
   }
 
-  if (name == "isFilesExperimentalEnabled") {
-    *output = options.files_experimental ? "true" : "false";
-    return;
-  }
-
   if (name == "switchLanguage") {
     const std::string* language = value.FindString("language");
     ASSERT_TRUE(language);
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
index ed310f91..768bcb67 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
@@ -141,9 +141,6 @@
     // Whether Drive should act as if offline.
     bool offline = false;
 
-    // Whether test needs the files-app-experimental feature.
-    bool files_experimental = false;
-
     // Whether test should enable the conflict dialog.
     bool enable_conflict_dialog = false;
 
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
index bd6fdd86..8bfaae8 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
@@ -68,11 +68,6 @@
   return *this;
 }
 
-TestCase& TestCase::FilesExperimental() {
-  options.files_experimental = true;
-  return *this;
-}
-
 TestCase& TestCase::EnableConflictDialog() {
   options.enable_conflict_dialog = true;
   return *this;
@@ -254,10 +249,6 @@
     full_name += "_Offline";
   }
 
-  if (options.files_experimental) {
-    full_name += "_FilesExperimental";
-  }
-
   if (options.enable_conflict_dialog) {
     full_name += "_ConflictDialog";
   }
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
index 4aef0de..c50aacc 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
@@ -48,8 +48,6 @@
 
   TestCase& Offline();
 
-  TestCase& FilesExperimental();
-
   TestCase& EnableConflictDialog();
 
   TestCase& DisableNativeSmb();
diff --git a/chrome/browser/ash/growth/show_notification_action_performer.cc b/chrome/browser/ash/growth/show_notification_action_performer.cc
index 5bc65bad..bf25cc8 100644
--- a/chrome/browser/ash/growth/show_notification_action_performer.cc
+++ b/chrome/browser/ash/growth/show_notification_action_performer.cc
@@ -20,6 +20,9 @@
 #include "chromeos/ash/components/growth/campaigns_manager.h"
 #include "chromeos/ash/components/growth/campaigns_model.h"
 #include "chromeos/ash/components/growth/growth_metrics.h"
+#include "chromeos/ash/grit/ash_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
 #include "ui/gfx/vector_icon_utils.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -30,16 +33,18 @@
 
 constexpr char kTitlePath[] = "title";
 constexpr char kMessagePath[] = "message";
-constexpr char kIconPath[] = "icon";
+constexpr char kIconPath[] = "sourceIcon";
 constexpr char kButtonsPath[] = "buttons";
 constexpr char kLabelPath[] = "label";
 constexpr char kActionPath[] = "action";
+constexpr char kImagePath[] = "image";
 constexpr char kNotificationIdTemplate[] = "growth_campaign_%d";
 
 struct ShowNotificationParams {
   std::string title;
   std::string message;
   raw_ptr<const gfx::VectorIcon> icon = nullptr;
+  raw_ptr<const gfx::Image> image = nullptr;
 
   std::vector<message_center::ButtonInfo> buttons_info;
 };
@@ -68,7 +73,7 @@
     return nullptr;
   }
 
-  const auto* icon = growth::Image(icon_value).GetVectorIcon();
+  const auto* icon = growth::VectorIcon(icon_value).GetVectorIcon();
   if (!icon) {
     growth::RecordCampaignsManagerError(
         growth::CampaignsManagerError::kNotificationPayloadInvalidIcon);
@@ -76,6 +81,13 @@
   }
   show_notification_params->icon = icon;
 
+  const auto* image_dict = params->FindDict(kImagePath);
+  if (image_dict) {
+    // TODO: b/341368196 - consider skip showing the notification if the image
+    // type is not recognized. The payload is invalid in this case.
+    show_notification_params->image = growth::Image(image_dict).GetImage();
+  }
+
   // Set buttons info.
   const auto* buttons = params->FindList(kButtonsPath);
   if (buttons) {
@@ -124,6 +136,9 @@
 
   message_center::RichNotificationData optional_fields;
   optional_fields.buttons = std::move(show_notification_params->buttons_info);
+  if (show_notification_params->image) {
+    optional_fields.image = *show_notification_params->image;
+  }
 
   auto id = base::StringPrintf(kNotificationIdTemplate, campaign_id);
   std::unique_ptr<message_center::Notification> notification =
diff --git a/chrome/browser/ash/growth/show_notification_action_performer_unittest.cc b/chrome/browser/ash/growth/show_notification_action_performer_unittest.cc
index 6529c5e1..a770722 100644
--- a/chrome/browser/ash/growth/show_notification_action_performer_unittest.cc
+++ b/chrome/browser/ash/growth/show_notification_action_performer_unittest.cc
@@ -13,10 +13,13 @@
 #include "base/scoped_observation.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/branding_buildflags.h"
 #include "chrome/browser/ash/growth/mock_ui_performer_observer.h"
+#include "chromeos/ash/grit/ash_resources.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
 #include "ui/message_center/message_center.h"
 #include "url/gurl.h"
 
@@ -26,8 +29,11 @@
     {
       "title": "%s",
       "message": "%s",
-      "icon": {
-        "builtInIcon": 0
+      "sourceIcon": {
+        "builtInVectorIcon": 0
+      },
+      "image": {
+        "builtInImage": 2
       }
     }
 )";
@@ -127,6 +133,17 @@
   EXPECT_EQ(notification->title(), base::UTF8ToUTF16(std::string(kTestTitle)));
   EXPECT_EQ(notification->message(),
             base::UTF8ToUTF16(std::string(kTestMessage)));
+  EXPECT_EQ(chromeos::kRedeemIcon.name,
+            notification->vector_small_image().name);
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  const auto& expected_image =
+      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+          IDR_GROWTH_FRAMEWORK_REBUY_PNG);
+  EXPECT_EQ(expected_image, notification->rich_notification_data().image);
+#else
+  EXPECT_EQ(gfx::Image(), notification->rich_notification_data().image);
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 }
 
 TEST_F(ShowNotificationActionPerformerTest, TestInvalidParams) {
diff --git a/chrome/browser/ash/growth/show_nudge_action_performer.cc b/chrome/browser/ash/growth/show_nudge_action_performer.cc
index 6d07ec5..67576175 100644
--- a/chrome/browser/ash/growth/show_nudge_action_performer.cc
+++ b/chrome/browser/ash/growth/show_nudge_action_performer.cc
@@ -134,7 +134,7 @@
     return;
   }
 
-  auto image_model = growth::Image(image_value).GetImage();
+  auto image_model = growth::ImageModel(image_value).GetImageModel();
   if (!image_model) {
     // No image model matched the image payload.
     growth::RecordCampaignsManagerError(
diff --git a/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.cc b/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.cc
new file mode 100644
index 0000000..12fc0e3
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.cc
@@ -0,0 +1,38 @@
+// 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/login/users/avatar/mock_user_image_loader_delegate.h"
+
+#include <memory>
+
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+#include "components/user_manager/user_image/user_image.h"
+
+namespace ash::test {
+
+namespace {
+
+std::unique_ptr<user_manager::UserImage> MakeUserImage() {
+  return std::make_unique<user_manager::UserImage>();
+}
+
+}  // namespace
+
+MockUserImageLoaderDelegate::MockUserImageLoaderDelegate() {
+  ON_CALL(*this, FromGURLAnimated)
+      .WillByDefault([](const GURL& default_image_url,
+                        user_image_loader::LoadedCallback loaded_cb) {
+        base::SequencedTaskRunner::GetCurrentDefault()
+            ->PostTaskAndReplyWithResult(FROM_HERE,
+                                         base::BindOnce(&MakeUserImage),
+                                         std::move(loaded_cb));
+      });
+}
+
+MockUserImageLoaderDelegate::~MockUserImageLoaderDelegate() = default;
+
+}  // namespace ash::test
diff --git a/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.h b/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.h
new file mode 100644
index 0000000..6d26aadd
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.h
@@ -0,0 +1,34 @@
+// 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_LOGIN_USERS_AVATAR_MOCK_USER_IMAGE_LOADER_DELEGATE_H_
+#define CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_MOCK_USER_IMAGE_LOADER_DELEGATE_H_
+
+#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace ash::test {
+
+class MockUserImageLoaderDelegate : public UserImageLoaderDelegate {
+ public:
+  MockUserImageLoaderDelegate();
+
+  MockUserImageLoaderDelegate(const MockUserImageLoaderDelegate&) = delete;
+  MockUserImageLoaderDelegate& operator=(const MockUserImageLoaderDelegate&) =
+      delete;
+
+  ~MockUserImageLoaderDelegate() override;
+
+  MOCK_METHOD(void,
+              FromGURLAnimated,
+              (const GURL& default_image_url,
+               user_image_loader::LoadedCallback loaded_cb),
+              (override));
+};
+
+}  // namespace ash::test
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_MOCK_USER_IMAGE_LOADER_DELEGATE_H_
diff --git a/chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h
new file mode 100644
index 0000000..e88cd5d5
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate.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_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_H_
+#define CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_H_
+
+#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+#include "url/gurl.h"
+
+namespace ash {
+
+// An interface for downloading user avatar images from network. Can be mocked
+// in tests.
+class UserImageLoaderDelegate {
+ public:
+  virtual ~UserImageLoaderDelegate() = default;
+
+  virtual void FromGURLAnimated(
+      const GURL& default_image_url,
+      user_image_loader::LoadedCallback loaded_cb) = 0;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_H_
diff --git a/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.cc b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.cc
new file mode 100644
index 0000000..f4ce10f3
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.cc
@@ -0,0 +1,22 @@
+// 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/login/users/avatar/user_image_loader_delegate_impl.h"
+
+#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+
+namespace ash {
+
+UserImageLoaderDelegateImpl::UserImageLoaderDelegateImpl() = default;
+
+UserImageLoaderDelegateImpl::~UserImageLoaderDelegateImpl() = default;
+
+void UserImageLoaderDelegateImpl::FromGURLAnimated(
+    const GURL& default_image_url,
+    user_image_loader::LoadedCallback loaded_cb) {
+  user_image_loader::StartWithGURLAnimated(default_image_url,
+                                           std::move(loaded_cb));
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.h b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.h
new file mode 100644
index 0000000..499dee3
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.h
@@ -0,0 +1,30 @@
+// 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_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_IMPL_H_
+#define CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_IMPL_H_
+
+#include "chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h"
+namespace ash {
+
+// Concrete implementation. Makes real network requests to download avatar
+// images.
+class UserImageLoaderDelegateImpl : public UserImageLoaderDelegate {
+ public:
+  UserImageLoaderDelegateImpl();
+
+  UserImageLoaderDelegateImpl(const UserImageLoaderDelegateImpl&) = delete;
+  UserImageLoaderDelegateImpl& operator=(const UserImageLoaderDelegateImpl&) =
+      delete;
+
+  ~UserImageLoaderDelegateImpl() override;
+
+  // UserImageLoaderDelegate:
+  void FromGURLAnimated(const GURL& default_image_url,
+                        user_image_loader::LoadedCallback loaded_cb) override;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_USER_IMAGE_LOADER_DELEGATE_IMPL_H_
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
index ba76af44..1e70aa7b 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
@@ -97,33 +97,6 @@
   return policy_manager->core()->store();
 }
 
-class UserImageChangeWaiter : public user_manager::UserManager::Observer {
- public:
-  UserImageChangeWaiter() {}
-
-  UserImageChangeWaiter(const UserImageChangeWaiter&) = delete;
-  UserImageChangeWaiter& operator=(const UserImageChangeWaiter&) = delete;
-
-  ~UserImageChangeWaiter() override {}
-
-  void Wait() {
-    user_manager::UserManager::Get()->AddObserver(this);
-    run_loop_ = std::make_unique<base::RunLoop>();
-    run_loop_->Run();
-    user_manager::UserManager::Get()->RemoveObserver(this);
-  }
-
-  // user_manager::UserManager::Observer:
-  void OnUserImageChanged(const user_manager::User& user) override {
-    if (run_loop_) {
-      run_loop_->Quit();
-    }
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-};
-
 }  // namespace
 
 class UserImageManagerTestBase : public LoginManagerTest,
@@ -352,7 +325,7 @@
   ASSERT_TRUE(user);
   // Wait for image load.
   if (user->image_index() == user_manager::User::USER_IMAGE_INVALID) {
-    UserImageChangeWaiter().Wait();
+    test::UserImageChangeWaiter().Wait();
   }
   // Check image dimensions. Images can't be compared since JPEG is lossy.
   const gfx::ImageSkia& saved_image = default_user_image::GetStubDefaultImage();
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_impl.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_impl.cc
index 47c143e..6847c0b 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_impl.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_impl.cc
@@ -26,7 +26,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/helper.h"
-#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_prefs.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_sync_observer.h"
 #include "chrome/browser/ash/login/users/default_user_image/default_user_images.h"
@@ -54,13 +54,13 @@
 namespace {
 
 // Delay between user login and attempt to update user's profile data.
-const int kProfileDataDownloadDelaySec = 10;
+constexpr int kProfileDataDownloadDelaySec = 10;
 
 // Interval between retries to update user's profile data.
-const int kProfileDataDownloadRetryIntervalSec = 300;
+constexpr int kProfileDataDownloadRetryIntervalSec = 300;
 
 // Delay between subsequent profile refresh attempts (24 hrs).
-const int kProfileRefreshIntervalSec = 24 * 3600;
+constexpr int kProfileRefreshIntervalSec = 24 * 3600;
 
 static bool g_ignore_profile_data_download_delay_ = false;
 
@@ -234,6 +234,10 @@
 
   const AccountId& account_id() const { return parent_->account_id_; }
 
+  UserImageLoaderDelegate* user_image_loader_delegate() {
+    return parent_->user_image_loader_delegate_;
+  }
+
   raw_ptr<UserImageManagerImpl, DanglingUntriaged> parent_;
 
   // Whether one of the Load*() or Set*() methods has been run already.
@@ -288,7 +292,7 @@
       }
       // Fetch the default image from cloud before caching it.
       image_url_ = default_user_image::GetDefaultImageUrl(image_index_);
-      user_image_loader::StartWithGURLAnimated(
+      user_image_loader_delegate()->FromGURLAnimated(
           image_url_, base::BindOnce(&Job::OnLoadImageDone,
                                      weak_factory_.GetWeakPtr(), true));
     }
@@ -335,7 +339,7 @@
     return;
   }
 
-  user_image_loader::StartWithGURLAnimated(
+  user_image_loader_delegate()->FromGURLAnimated(
       image_url_,
       base::BindOnce(&Job::OnLoadImageDone, weak_factory_.GetWeakPtr(), true));
 }
@@ -538,9 +542,11 @@
 
 UserImageManagerImpl::UserImageManagerImpl(
     const AccountId& account_id,
-    user_manager::UserManager* user_manager)
+    user_manager::UserManager* user_manager,
+    UserImageLoaderDelegate* user_image_loader_delegate)
     : account_id_(account_id),
       user_manager_(user_manager),
+      user_image_loader_delegate_(user_image_loader_delegate),
       downloading_profile_image_(false),
       has_managed_image_(false) {
   background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
@@ -620,7 +626,8 @@
     // correspond to (a) special constants and (b) indexes of an array
     // containing resource IDs.
     base::UmaHistogramExactLinear(
-        "UserImage.LoggedIn3", ImageIndexToHistogramIndex(user->image_index()),
+        kUserImageLoggedInHistogramName,
+        ImageIndexToHistogramIndex(user->image_index()),
         default_user_image::kHistogramImagesCount + 1);
   }
 
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_impl.h b/chrome/browser/ash/login/users/avatar/user_image_manager_impl.h
index 71c0f69..aac25c54 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_impl.h
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_impl.h
@@ -32,6 +32,7 @@
 
 namespace ash {
 
+class UserImageLoaderDelegate;
 class UserImageSyncObserver;
 
 // Provides a mechanism for updating user images. There is an instance of this
@@ -42,6 +43,10 @@
   inline static constexpr char kUserImageChangedHistogramName[] =
       "UserImage.Changed2";
 
+  // The name of the histogram that records the user's chosen image at login.
+  inline static constexpr char kUserImageLoggedInHistogramName[] =
+      "UserImage.LoggedIn3";
+
   // Converts `image_index` to UMA histogram value.
   static int ImageIndexToHistogramIndex(int image_index);
 
@@ -52,7 +57,8 @@
   static void RegisterPrefs(PrefRegistrySimple* registry);
 
   UserImageManagerImpl(const AccountId& account_id,
-                       user_manager::UserManager* user_manager);
+                       user_manager::UserManager* user_manager,
+                       UserImageLoaderDelegate* user_image_loader_delegate);
 
   UserImageManagerImpl(const UserImageManagerImpl&) = delete;
   UserImageManagerImpl& operator=(const UserImageManagerImpl&) = delete;
@@ -139,6 +145,7 @@
 
  private:
   friend class UserImageManagerTestBase;
+  friend class UserImageManagerImplTest;
 
   // ID of user which images are managed by current instance of
   // UserImageManager.
@@ -225,6 +232,10 @@
   // The user manager.
   raw_ptr<user_manager::UserManager> user_manager_;
 
+  // A delegate to retrieve user images from disk and network. Allows injecting
+  // a mock for testing.
+  raw_ptr<UserImageLoaderDelegate> user_image_loader_delegate_;
+
   // Whether the `profile_downloader_` is downloading the profile image for the
   // currently logged-in user (and not just the full name). Only valid when a
   // download is currently in progress.
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_impl_unittest.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_impl_unittest.cc
new file mode 100644
index 0000000..a2594da
--- /dev/null
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_impl_unittest.cc
@@ -0,0 +1,182 @@
+// 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/login/users/avatar/user_image_manager_impl.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/ash/login/users/avatar/mock_user_image_loader_delegate.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_loader.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_manager_registry.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_manager_test_util.h"
+#include "chrome/browser/ash/login/users/default_user_image/default_user_images.h"
+#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/test/base/scoped_testing_local_state.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/account_id/account_id.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "components/user_manager/user.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace ash {
+namespace {
+
+constexpr std::string_view kFakeRegularUserEmail = "test@example.com";
+
+}  // namespace
+
+class UserImageManagerImplTest : public testing::Test {
+ public:
+  UserImageManagerImplTest() = default;
+  UserImageManagerImplTest(const UserImageManagerImplTest&) = delete;
+  UserImageManagerImplTest& operator=(const UserImageManagerImplTest&) = delete;
+  ~UserImageManagerImplTest() override = default;
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    ASSERT_TRUE(profile_manager_.SetUp());
+
+    auto mock_user_image_loader_delegate = std::make_unique<
+        testing::StrictMock<test::MockUserImageLoaderDelegate>>();
+    mock_user_image_loader_delegate_ = mock_user_image_loader_delegate.get();
+
+    user_image_manager_registry_ = std::make_unique<UserImageManagerRegistry>(
+        fake_chrome_user_manager(), std::move(mock_user_image_loader_delegate));
+  }
+
+  void TearDown() override {
+    mock_user_image_loader_delegate_ = nullptr;
+    testing::Test::TearDown();
+  }
+
+  user_manager::User* AddUser(const AccountId& account_id) {
+    user_manager::User* user = fake_chrome_user_manager()->AddUser(account_id);
+    TestingProfile* profile =
+        profile_manager_.CreateTestingProfile(account_id.GetUserEmail());
+    fake_chrome_user_manager()->OnUserProfileCreated(account_id,
+                                                     profile->GetPrefs());
+    return user;
+  }
+
+  FakeChromeUserManager* fake_chrome_user_manager() {
+    return fake_chrome_user_manager_.Get();
+  }
+
+  UserImageManagerRegistry* user_image_manager_registry() {
+    return user_image_manager_registry_.get();
+  }
+
+  UserImageManagerImpl* user_image_manager_impl(const AccountId& account_id) {
+    return user_image_manager_registry()->GetManager(account_id);
+  }
+
+  testing::StrictMock<test::MockUserImageLoaderDelegate>*
+  mock_user_image_loader_delegate() {
+    return mock_user_image_loader_delegate_.get();
+  }
+
+  bool is_random_image_set(const AccountId& account_id) {
+    return user_image_manager_impl(account_id)->is_random_image_set_;
+  }
+
+  bool is_downloading_profile_image(const AccountId& account_id) {
+    return user_image_manager_impl(account_id)->downloading_profile_image_;
+  }
+
+  bool NeedProfileImage(const AccountId& account_id) {
+    return user_image_manager_impl(account_id)->NeedProfileImage();
+  }
+
+ private:
+  ScopedTestingLocalState local_state_{TestingBrowserProcess::GetGlobal()};
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal(),
+                                         &local_state_};
+  user_manager::TypedScopedUserManager<FakeChromeUserManager>
+      fake_chrome_user_manager_{std::make_unique<FakeChromeUserManager>()};
+  raw_ptr<testing::StrictMock<test::MockUserImageLoaderDelegate>>
+      mock_user_image_loader_delegate_;
+  std::unique_ptr<UserImageManagerRegistry> user_image_manager_registry_;
+};
+
+// TODO(b/339503132) should set to google profile image.
+TEST_F(UserImageManagerImplTest, SetsRandomDefaultInitialImageForNewUsers) {
+  const AccountId account_id = AccountId::FromUserEmailGaiaId(
+      std::string(kFakeRegularUserEmail), std::string(kFakeRegularUserEmail));
+  user_manager::User* user = AddUser(account_id);
+
+  GURL requested_url;
+
+  EXPECT_CALL(*mock_user_image_loader_delegate(), FromGURLAnimated)
+      .WillOnce(testing::DoAll(
+          testing::SaveArg<0>(&requested_url),
+          testing::Invoke([](const GURL& default_image_url,
+                             user_image_loader::LoadedCallback loaded_cb) {
+            base::SequencedTaskRunner::GetCurrentDefault()
+                ->PostTaskAndReplyWithResult(
+                    FROM_HERE, base::BindLambdaForTesting([]() {
+                      return std::make_unique<user_manager::UserImage>();
+                    }),
+                    std::move(loaded_cb));
+          })));
+
+  fake_chrome_user_manager()->SetIsCurrentUserNew(true);
+  fake_chrome_user_manager()->UserLoggedIn(
+      account_id, /*user_id_hash=*/
+      user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+      /*browser_restart=*/false,
+      /*is_child=*/false);
+
+  test::UserImageChangeWaiter user_image_change_waiter;
+  user_image_change_waiter.Wait();
+
+  EXPECT_TRUE(default_user_image::IsInCurrentImageSet(user->image_index()));
+  EXPECT_EQ(requested_url,
+            default_user_image::GetDefaultImageUrl(user->image_index()));
+
+  EXPECT_FALSE(is_downloading_profile_image(account_id));
+  EXPECT_FALSE(NeedProfileImage(account_id));
+  EXPECT_TRUE(
+      user_image_manager_impl(account_id)->DownloadedProfileImage().isNull());
+  EXPECT_TRUE(is_random_image_set(account_id));
+}
+
+TEST_F(UserImageManagerImplTest, RecordsUserImageLoggedInHistogram) {
+  constexpr int kDefaultImageIndex = 85;
+  ASSERT_TRUE(default_user_image::IsInCurrentImageSet(kDefaultImageIndex));
+
+  base::HistogramTester histogram_tester;
+
+  const AccountId account_id = AccountId::FromUserEmailGaiaId(
+      std::string(kFakeRegularUserEmail), std::string(kFakeRegularUserEmail));
+  AddUser(account_id);
+
+  EXPECT_CALL(*mock_user_image_loader_delegate(), FromGURLAnimated);
+
+  user_image_manager_impl(account_id)
+      ->SaveUserDefaultImageIndex(kDefaultImageIndex);
+
+  fake_chrome_user_manager()->SetIsCurrentUserNew(false);
+  fake_chrome_user_manager()->UserLoggedIn(
+      account_id, /*user_id_hash=*/
+      user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+      /*browser_restart=*/false,
+      /*is_child=*/false);
+
+  histogram_tester.ExpectUniqueSample(
+      UserImageManagerImpl::kUserImageLoggedInHistogramName,
+      UserImageManagerImpl::ImageIndexToHistogramIndex(kDefaultImageIndex), 1);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_registry.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_registry.cc
index 4c30d92..1b74d92 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_registry.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_registry.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/ash/login/users/avatar/user_image_manager_registry.h"
 
-#include "base/notreached.h"
+#include <memory>
+
+#include "chrome/browser/ash/login/users/avatar/user_image_loader_delegate.h"
+#include "chrome/browser/ash/login/users/avatar/user_image_loader_delegate_impl.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_manager_impl.h"
 #include "components/account_id/account_id.h"
 #include "components/user_manager/user.h"
@@ -22,7 +25,15 @@
 
 UserImageManagerRegistry::UserImageManagerRegistry(
     user_manager::UserManager* user_manager)
-    : user_manager_(user_manager) {
+    : UserImageManagerRegistry(
+          user_manager,
+          std::make_unique<UserImageLoaderDelegateImpl>()) {}
+
+UserImageManagerRegistry::UserImageManagerRegistry(
+    user_manager::UserManager* user_manager,
+    std::unique_ptr<UserImageLoaderDelegate> user_image_loader_delegate)
+    : user_image_loader_delegate_(std::move(user_image_loader_delegate)),
+      user_manager_(user_manager) {
   CHECK(!g_instance);
   g_instance = this;
   observation_.Observe(user_manager);
@@ -38,7 +49,8 @@
   auto it = map_.find(account_id);
   if (it == map_.end()) {
     it = map_.emplace(account_id, std::make_unique<UserImageManagerImpl>(
-                                      account_id, user_manager_.get()))
+                                      account_id, user_manager_.get(),
+                                      user_image_loader_delegate_.get()))
              .first;
   }
   return it->second.get();
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_registry.h b/chrome/browser/ash/login/users/avatar/user_image_manager_registry.h
index e59a1c2..080b186 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_registry.h
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_registry.h
@@ -21,6 +21,7 @@
 namespace ash {
 
 class UserImageManagerImpl;
+class UserImageLoaderDelegate;
 
 // UserImageManger is per user. This manages the mapping from each user
 // identified by AccountId to UserImageManager.
@@ -32,8 +33,15 @@
 
   // Given user_manager's lifetime needs to outlive this instance.
   explicit UserImageManagerRegistry(user_manager::UserManager* user_manager);
+
+  // Constructor to inject a test version of `UserImageLoaderDelegate`.
+  UserImageManagerRegistry(
+      user_manager::UserManager* user_manager,
+      std::unique_ptr<UserImageLoaderDelegate> user_image_loader_delegate);
+
   UserImageManagerRegistry(const UserImageManagerRegistry&) = delete;
   UserImageManagerRegistry operator=(UserImageManagerRegistry&) = delete;
+
   ~UserImageManagerRegistry() override;
 
   // Returns the manager for the given avator.
@@ -51,7 +59,12 @@
   void OnUserProfileCreated(const user_manager::User& user) override;
 
  private:
+  // Owned. Expected to outlive `map_` as it is shared by every
+  // `UserImageManagerImpl`.
+  const std::unique_ptr<UserImageLoaderDelegate> user_image_loader_delegate_;
+
   const raw_ptr<user_manager::UserManager> user_manager_;
+
   std::map<AccountId, std::unique_ptr<UserImageManagerImpl>> map_;
 
   base::ScopedObservation<user_manager::UserManager,
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.cc
index 187f5a7..8741b4f 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.cc
@@ -7,16 +7,12 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <string>
-#include <utility>
 
 #include "base/files/file_util.h"
-#include "base/memory/ref_counted.h"
 #include "base/threading/thread_restrictions.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/image/image_skia_rep.h"
 
-namespace ash {
-namespace test {
+namespace ash::test {
 
 const char kUserAvatarImage1RelativePath[] = "chromeos/avatars/avatar1.jpg";
 const char kUserAvatarImage2RelativePath[] = "chromeos/avatars/avatar2.jpg";
@@ -50,6 +46,10 @@
   return true;
 }
 
+// *****************************************************************************
+// ImageLoader
+// *****************************************************************************
+
 ImageLoader::ImageLoader(const base::FilePath& path) : path_(path) {}
 
 ImageLoader::~ImageLoader() {}
@@ -79,5 +79,25 @@
   run_loop_.Quit();
 }
 
-}  // namespace test
-}  // namespace ash
+// *****************************************************************************
+// UserImageChangeWaiter
+// *****************************************************************************
+
+UserImageChangeWaiter::UserImageChangeWaiter() = default;
+
+UserImageChangeWaiter::~UserImageChangeWaiter() = default;
+
+void UserImageChangeWaiter::Wait() {
+  user_manager::UserManager::Get()->AddObserver(this);
+  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_->Run();
+  user_manager::UserManager::Get()->RemoveObserver(this);
+}
+
+void UserImageChangeWaiter::OnUserImageChanged(const user_manager::User& user) {
+  if (run_loop_) {
+    run_loop_->Quit();
+  }
+}
+
+}  // namespace ash::test
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.h b/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.h
index 5152514..ba736d42 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.h
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_test_util.h
@@ -8,14 +8,14 @@
 #include "base/files/file_path.h"
 #include "base/run_loop.h"
 #include "chrome/browser/image_decoder/image_decoder.h"
+#include "components/user_manager/user_manager.h"
 #include "ui/gfx/image/image_skia.h"
 
 namespace base {
 class FilePath;
 }
 
-namespace ash {
-namespace test {
+namespace ash::test {
 
 extern const char kUserAvatarImage1RelativePath[];
 extern const char kUserAvatarImage2RelativePath[];
@@ -47,7 +47,24 @@
   gfx::ImageSkia decoded_image_;
 };
 
-}  // namespace test
-}  // namespace ash
+class UserImageChangeWaiter : public user_manager::UserManager::Observer {
+ public:
+  UserImageChangeWaiter();
+
+  UserImageChangeWaiter(const UserImageChangeWaiter&) = delete;
+  UserImageChangeWaiter& operator=(const UserImageChangeWaiter&) = delete;
+
+  ~UserImageChangeWaiter() override;
+
+  void Wait();
+
+  // user_manager::UserManager::Observer:
+  void OnUserImageChanged(const user_manager::User& user) override;
+
+ private:
+  std::unique_ptr<base::RunLoop> run_loop_;
+};
+
+}  // namespace ash::test
 
 #endif  // CHROME_BROWSER_ASH_LOGIN_USERS_AVATAR_USER_IMAGE_MANAGER_TEST_UTIL_H_
diff --git a/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc b/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc
index ef7cf6b..49361d28 100644
--- a/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc
+++ b/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc
@@ -198,7 +198,8 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SmartDimLacrosIntegrationTest, SmartDim) {
+// TODO(b/341659658): Fails to initialize Lacros window.
+IN_PROC_BROWSER_TEST_F(SmartDimLacrosIntegrationTest, DISABLED_SmartDim) {
   // Lacros fails to start up correctly on VM tryservers like
   // chromeos-amd64-generic (Lacros restarts in a loop). b/303359438
   if (base::CPU().is_running_in_vm()) {
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
index dbfb617..5efa12e 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
@@ -70,14 +70,15 @@
   return ::ash::personalization_app::IsEligibleForSeaPen(profile_);
 }
 
-void PersonalizationAppSeaPenProviderBase::SearchWallpaper(
+void PersonalizationAppSeaPenProviderBase::GetSeaPenThumbnails(
     const mojom::SeaPenQueryPtr query,
-    SearchWallpaperCallback callback) {
+    GetSeaPenThumbnailsCallback callback) {
   // Search for wallpaper.
-  if (query->is_text_query() && query->get_text_query().length() >
-                                    mojom::kMaximumSearchWallpaperTextBytes) {
+  if (query->is_text_query() &&
+      query->get_text_query().length() >
+          mojom::kMaximumGetSeaPenThumbnailsTextBytes) {
     sea_pen_receiver_.ReportBadMessage(
-        "SearchWallpaper exceeded maximum text length");
+        "GetSeaPenThumbnails exceeded maximum text length");
     return;
   }
   last_query_ = query.Clone();
@@ -104,8 +105,8 @@
   if (feature_name_ == manta::proto::FeatureName::CHROMEOS_WALLPAPER) {
     auto* sea_pen_fetcher = GetOrCreateSeaPenFetcher();
     CHECK(sea_pen_fetcher);
-    // |last_query_| is set when calling SearchWallpaper() to fetch thumbnails.
-    // It should not be null when a thumbnail is selected.
+    // |last_query_| is set when calling GetSeaPenThumbnails() to fetch
+    // thumbnails. It should not be null when a thumbnail is selected.
     CHECK(last_query_);
     sea_pen_fetcher->FetchWallpaper(
         feature_name_, it->second, last_query_,
@@ -141,10 +142,10 @@
           weak_ptr_factory_.GetWeakPtr()));
 }
 
-void PersonalizationAppSeaPenProviderBase::GetRecentSeaPenImages(
-    GetRecentSeaPenImagesCallback callback) {
-  GetRecentSeaPenImagesInternal(base::BindOnce(
-      &PersonalizationAppSeaPenProviderBase::OnGetRecentSeaPenImages,
+void PersonalizationAppSeaPenProviderBase::GetRecentSeaPenImageIds(
+    GetRecentSeaPenImageIdsCallback callback) {
+  GetRecentSeaPenImageIdsInternal(base::BindOnce(
+      &PersonalizationAppSeaPenProviderBase::OnGetRecentSeaPenImageIds,
       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
@@ -174,7 +175,7 @@
 }
 
 void PersonalizationAppSeaPenProviderBase::OnFetchThumbnailsDone(
-    SearchWallpaperCallback callback,
+    GetSeaPenThumbnailsCallback callback,
     std::optional<std::vector<SeaPenImage>> images,
     manta::MantaStatusCode status_code) {
   if (!images) {
@@ -211,8 +212,8 @@
   std::move(pending_select_recent_sea_pen_image_callback_).Run(success);
 }
 
-void PersonalizationAppSeaPenProviderBase::OnGetRecentSeaPenImages(
-    GetRecentSeaPenImagesCallback callback,
+void PersonalizationAppSeaPenProviderBase::OnGetRecentSeaPenImageIds(
+    GetRecentSeaPenImageIdsCallback callback,
     const std::vector<uint32_t>& ids) {
   recent_sea_pen_image_ids_ = std::set<uint32_t>(ids.begin(), ids.end());
   std::move(callback).Run(ids);
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
index 591af243..0e99849 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
@@ -58,8 +58,8 @@
   bool IsEligibleForSeaPen() override;
 
   // ::ash::personalization_app::mojom::SeaPenProvider:
-  void SearchWallpaper(mojom::SeaPenQueryPtr query,
-                       SearchWallpaperCallback callback) override;
+  void GetSeaPenThumbnails(mojom::SeaPenQueryPtr query,
+                           GetSeaPenThumbnailsCallback callback) override;
 
   void SelectSeaPenThumbnail(uint32_t id,
                              SelectSeaPenThumbnailCallback callback) override;
@@ -68,7 +68,8 @@
       uint32_t id,
       SelectRecentSeaPenImageCallback callback) override;
 
-  void GetRecentSeaPenImages(GetRecentSeaPenImagesCallback callback) override;
+  void GetRecentSeaPenImageIds(
+      GetRecentSeaPenImageIdsCallback callback) override;
 
   void GetRecentSeaPenImageThumbnail(
       uint32_t id,
@@ -88,8 +89,8 @@
       uint32_t id,
       SelectRecentSeaPenImageCallback callback) = 0;
 
-  virtual void GetRecentSeaPenImagesInternal(
-      GetRecentSeaPenImagesCallback callback) = 0;
+  virtual void GetRecentSeaPenImageIdsInternal(
+      GetRecentSeaPenImageIdsCallback callback) = 0;
 
   virtual void GetRecentSeaPenImageThumbnailInternal(
       uint32_t id,
@@ -119,8 +120,7 @@
   mojo::Receiver<mojom::SeaPenProvider> sea_pen_receiver_{this};
 
  private:
-
-  void OnFetchThumbnailsDone(SearchWallpaperCallback callback,
+  void OnFetchThumbnailsDone(GetSeaPenThumbnailsCallback callback,
                              std::optional<std::vector<SeaPenImage>> images,
                              manta::MantaStatusCode status_code);
 
@@ -129,8 +129,8 @@
 
   void OnRecentSeaPenImageSelected(bool success);
 
-  void OnGetRecentSeaPenImages(GetRecentSeaPenImagesCallback callback,
-                               const std::vector<uint32_t>& ids);
+  void OnGetRecentSeaPenImageIds(GetRecentSeaPenImageIdsCallback callback,
+                                 const std::vector<uint32_t>& ids);
 
   void OnGetRecentSeaPenImageThumbnail(
       uint32_t id,
@@ -148,7 +148,7 @@
   std::map<uint32_t, const SeaPenImage> sea_pen_images_;
 
   // The last query made to the sea pen provider. This can be null when
-  // SearchWallpaper() is never called.
+  // GetSeaPenThumbnails() is never called.
   mojom::SeaPenQueryPtr last_query_;
 
   // Perform a network request to search/upscale available wallpapers.
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
index 8235463..888c0b5cb 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
@@ -93,8 +93,8 @@
                                            std::move(callback));
 }
 
-void PersonalizationAppSeaPenProviderImpl::GetRecentSeaPenImagesInternal(
-    GetRecentSeaPenImagesCallback callback) {
+void PersonalizationAppSeaPenProviderImpl::GetRecentSeaPenImageIdsInternal(
+    GetRecentSeaPenImageIdsCallback callback) {
   auto* sea_pen_wallpaper_manager = SeaPenWallpaperManager::GetInstance();
   DCHECK(sea_pen_wallpaper_manager);
   sea_pen_wallpaper_manager->GetImageIds(GetAccountId(profile_),
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
index d07f428..3d541147 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
@@ -49,8 +49,8 @@
       uint32_t id,
       SelectRecentSeaPenImageCallback callback) override;
 
-  void GetRecentSeaPenImagesInternal(
-      GetRecentSeaPenImagesCallback callback) override;
+  void GetRecentSeaPenImageIdsInternal(
+      GetRecentSeaPenImageIdsCallback callback) override;
 
   void GetRecentSeaPenImageThumbnailInternal(
       uint32_t id,
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl_unittest.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl_unittest.cc
index 4a94ad0..7d9db8a 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl_unittest.cc
@@ -327,7 +327,7 @@
   mojom::SeaPenQueryPtr search_query =
       mojom::SeaPenQuery::NewTextQuery("search_query");
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       std::move(search_query), search_wallpaper_future.GetCallback());
 
   EXPECT_THAT(
@@ -358,7 +358,7 @@
           mojom::SeaPenUserVisibleQuery::New("test template query",
                                              "test template title")));
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       std::move(search_query), search_wallpaper_future.GetCallback());
 
   EXPECT_THAT(
@@ -376,11 +376,11 @@
   // "\uFFFF" is picked because `.size()` differs by a factor of three
   // between UTF-8 (C++ std::string) and UTF-16 (javascript string).
   std::string long_unicode_string =
-      RepeatToSize("\uFFFF", mojom::kMaximumSearchWallpaperTextBytes);
-  ASSERT_EQ(mojom::kMaximumSearchWallpaperTextBytes,
+      RepeatToSize("\uFFFF", mojom::kMaximumGetSeaPenThumbnailsTextBytes);
+  ASSERT_EQ(mojom::kMaximumGetSeaPenThumbnailsTextBytes,
             long_unicode_string.size());
   // In javascript UTF-16, `long_unicode_string.length` is 1/3.
-  ASSERT_EQ(mojom::kMaximumSearchWallpaperTextBytes / 3,
+  ASSERT_EQ(mojom::kMaximumGetSeaPenThumbnailsTextBytes / 3,
             base::UTF8ToUTF16(long_unicode_string).size());
 
   base::test::TestFuture<
@@ -391,31 +391,31 @@
   mojom::SeaPenQueryPtr long_query =
       mojom::SeaPenQuery::NewTextQuery(long_unicode_string);
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       std::move(long_query), search_wallpaper_future.GetCallback());
 
   EXPECT_EQ(4u, search_wallpaper_future.Get<0>().value().size())
-      << "SearchWallpaper succeeds if text is exactly max length";
+      << "GetSeaPenThumbnails succeeds if text is exactly max length";
 }
 
 TEST_F(PersonalizationAppSeaPenProviderImplTest, QueryLengthExceeded) {
   SetUpProfileForTesting(kFakeTestEmail, GetTestAccountId());
   std::string max_length_unicode_string =
-      RepeatToSize("\uFFFF", mojom::kMaximumSearchWallpaperTextBytes);
+      RepeatToSize("\uFFFF", mojom::kMaximumGetSeaPenThumbnailsTextBytes);
   mojom::SeaPenQueryPtr bad_long_query =
       mojom::SeaPenQuery::NewTextQuery(max_length_unicode_string + 'a');
   mojo::test::BadMessageObserver bad_message_observer;
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       std::move(bad_long_query),
       base::BindLambdaForTesting(
           [](std::optional<std::vector<
                  ash::personalization_app::mojom::SeaPenThumbnailPtr>>,
              manta::MantaStatusCode) { NOTREACHED_IN_MIGRATION(); }));
 
-  EXPECT_EQ("SearchWallpaper exceeded maximum text length",
+  EXPECT_EQ("GetSeaPenThumbnails exceeded maximum text length",
             bad_message_observer.WaitForBadMessage())
-      << "SearchWallpaper fails if text is longer than max length";
+      << "GetSeaPenThumbnails fails if text is longer than max length";
 }
 
 TEST_F(PersonalizationAppSeaPenProviderImplTest,
@@ -434,7 +434,7 @@
       manta::MantaStatusCode>
       search_wallpaper_future;
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       query->Clone(), search_wallpaper_future.GetCallback());
 
   ASSERT_EQ(963u, search_wallpaper_future.Get<0>().value().front()->id);
@@ -456,7 +456,7 @@
             test_wallpaper_controller()->wallpaper_info()->type);
 }
 
-TEST_F(PersonalizationAppSeaPenProviderImplTest, GetRecentSeaPenImages) {
+TEST_F(PersonalizationAppSeaPenProviderImplTest, GetRecentSeaPenImageIds) {
   SetUpProfileForTesting(kFakeTestEmail, GetTestAccountId());
 
   // Create two images in the Sea Pen directory for the 1st user, then get the
@@ -464,7 +464,7 @@
   CreateSeaPenFilesForTesting(GetTestAccountId(), {kSeaPenId1, kSeaPenId2});
 
   base::test::TestFuture<const std::vector<uint32_t>&> recent_images_future;
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
 
   std::vector<uint32_t> recent_images = recent_images_future.Take();
@@ -474,7 +474,7 @@
   // Log in the second user, get the list of recent images.
   SetUpProfileForTesting(kFakeTestEmail2, GetTestAccountId2());
 
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
   ASSERT_EQ(0u, recent_images_future.Take().size());
 
@@ -482,7 +482,7 @@
   // of recent images again.
   CreateSeaPenFilesForTesting(GetTestAccountId2(), {kSeaPenId1});
 
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
   recent_images = recent_images_future.Take();
   EXPECT_THAT(recent_images,
@@ -508,7 +508,7 @@
           std::vector<ash::personalization_app::mojom::SeaPenThumbnailPtr>>,
       manta::MantaStatusCode>
       search_wallpaper_future;
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       search_query.Clone(), search_wallpaper_future.GetCallback());
   // Select the first returned thumbnail.
   base::test::TestFuture<bool> select_wallpaper_future;
@@ -562,7 +562,7 @@
       manta::MantaStatusCode>
       search_wallpaper_future;
 
-  sea_pen_provider_remote()->SearchWallpaper(
+  sea_pen_provider_remote()->GetSeaPenThumbnails(
       search_query->Clone(), search_wallpaper_future.GetCallback());
 
   // Select the first returned thumbnail.
@@ -594,7 +594,7 @@
   CreateSeaPenFilesForTesting(GetTestAccountId(), {kSeaPenId1});
 
   base::test::TestFuture<const std::vector<uint32_t>&> recent_images_future;
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
 
   std::vector<uint32_t> recent_images = recent_images_future.Take();
@@ -621,7 +621,7 @@
   CreateSeaPenFilesForTesting(GetTestAccountId(), {kSeaPenId1});
 
   base::test::TestFuture<const std::vector<uint32_t>&> recent_images_future;
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
 
   std::vector<uint32_t> recent_images = recent_images_future.Take();
@@ -657,7 +657,7 @@
   }
 
   base::test::TestFuture<const std::vector<uint32_t>&> recent_images_future;
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
 
   std::vector<uint32_t> recent_images = recent_images_future.Take();
@@ -677,7 +677,7 @@
   CreateSeaPenFilesForTesting(GetTestAccountId(), {kSeaPenId1, kSeaPenId2});
 
   base::test::TestFuture<const std::vector<uint32_t>&> recent_images_future;
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
   EXPECT_THAT(recent_images_future.Take(),
               testing::UnorderedElementsAre(kSeaPenId1, kSeaPenId2));
@@ -695,7 +695,7 @@
       kSeaPenId2, delete_future.GetCallback());
   EXPECT_TRUE(delete_future.Take());
 
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
   EXPECT_THAT(recent_images_future.Take(),
               testing::UnorderedElementsAre(kSeaPenId1));
@@ -709,7 +709,7 @@
       kSeaPenId1, delete_future.GetCallback());
   EXPECT_TRUE(delete_future.Take());
 
-  sea_pen_provider_remote()->GetRecentSeaPenImages(
+  sea_pen_provider_remote()->GetRecentSeaPenImageIds(
       recent_images_future.GetCallback());
   EXPECT_THAT(recent_images_future.Take(),
               testing::ContainerEq(std::vector<uint32_t>({})));
diff --git a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
index f668c01..1ee89713 100644
--- a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
@@ -84,8 +84,8 @@
       CameraEffectsController::SeaPenIdToRelativePath(id), std::move(callback));
 }
 
-void VcBackgroundUISeaPenProviderImpl::GetRecentSeaPenImagesInternal(
-    GetRecentSeaPenImagesCallback callback) {
+void VcBackgroundUISeaPenProviderImpl::GetRecentSeaPenImageIdsInternal(
+    GetRecentSeaPenImageIdsCallback callback) {
   GetCameraEffectsController()->GetBackgroundImageFileNames(
       base::BindOnce(&GetIdsFromFilePaths).Then(std::move(callback)));
 }
diff --git a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
index e6709da..0198349 100644
--- a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
+++ b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
@@ -47,8 +47,8 @@
       uint32_t id,
       SelectRecentSeaPenImageCallback callback) override;
 
-  void GetRecentSeaPenImagesInternal(
-      GetRecentSeaPenImagesCallback callback) override;
+  void GetRecentSeaPenImageIdsInternal(
+      GetRecentSeaPenImageIdsCallback callback) override;
 
   void GetRecentSeaPenImageThumbnailInternal(
       uint32_t id,
diff --git a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl_browsertest.cc b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl_browsertest.cc
index 6335bc2..2b03074 100644
--- a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl_browsertest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl_browsertest.cc
@@ -119,7 +119,7 @@
 IN_PROC_BROWSER_TEST_F(VcBackgroundUISeaPenProviderImplTest, AllTests) {
   // Get all background images.
   base::RunLoop run_loop;
-  sea_pen_provider_->GetRecentSeaPenImages(
+  sea_pen_provider_->GetRecentSeaPenImageIds(
       base::BindLambdaForTesting([&](const std::vector<uint32_t>& ids) {
         EXPECT_EQ(existing_image_ids_, ids);
         run_loop.Quit();
diff --git a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.cc b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.cc
index 155a7ad..1f8635ce 100644
--- a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.cc
+++ b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.cc
@@ -209,7 +209,8 @@
 
     if (query->is_text_query() &&
         query->get_text_query().size() >
-            ash::personalization_app::mojom::kMaximumSearchWallpaperTextBytes) {
+            ash::personalization_app::mojom::
+                kMaximumGetSeaPenThumbnailsTextBytes) {
       LOG(WARNING) << "Query too long. Size received: "
                    << query->get_text_query().size();
       std::move(callback).Run(std::nullopt,
@@ -254,9 +255,9 @@
     }
 
     if (query->is_text_query()) {
-      CHECK_LE(
-          query->get_text_query().size(),
-          ash::personalization_app::mojom::kMaximumSearchWallpaperTextBytes);
+      CHECK_LE(query->get_text_query().size(),
+               ash::personalization_app::mojom::
+                   kMaximumGetSeaPenThumbnailsTextBytes);
     }
 
     fetch_wallpaper_timer_.Stop();
diff --git a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.h b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.h
index dded8026..b0ab723 100644
--- a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.h
+++ b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.h
@@ -42,7 +42,7 @@
   virtual ~SeaPenFetcher();
 
   // Run `query` against the Manta API. `query` is required to be a valid UTF-8
-  // string no longer than `kMaximumSearchWallpaperTextBytes`.
+  // string no longer than `kMaximumGetSeaPenThumbnailsTextBytes`.
   virtual void FetchThumbnails(
       manta::proto::FeatureName feature_name,
       const ash::personalization_app::mojom::SeaPenQueryPtr& query,
diff --git a/chrome/browser/companion/core/features.cc b/chrome/browser/companion/core/features.cc
index 4a1d4d4c9..c0990f7 100644
--- a/chrome/browser/companion/core/features.cc
+++ b/chrome/browser/companion/core/features.cc
@@ -46,11 +46,6 @@
              "CompanionEnableSearchWebInNewTabContextMenuItem",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// When search companion is enabled, show new badges on the context menu items
-// that open the companion.
-BASE_FEATURE(kCompanionEnableNewBadgesInContextMenu,
-             "CompanionEnableNewBadgesInContextMenu",
-             base::FEATURE_DISABLED_BY_DEFAULT);
 // Allow sharing page content with CSC. Enabling this flag alone isn't enough to
 // share page content - the user still needs to opt in either through a promo or
 // chrome://settings. When disabled, page content will not be shared even if the
diff --git a/chrome/browser/companion/core/features.h b/chrome/browser/companion/core/features.h
index 6be10f8..b4aa8b9 100644
--- a/chrome/browser/companion/core/features.h
+++ b/chrome/browser/companion/core/features.h
@@ -22,7 +22,6 @@
 }  // namespace internal
 
 BASE_DECLARE_FEATURE(kCompanionEnableSearchWebInNewTabContextMenuItem);
-BASE_DECLARE_FEATURE(kCompanionEnableNewBadgesInContextMenu);
 BASE_DECLARE_FEATURE(kCompanionEnablePageContent);
 }  // namespace features
 
diff --git a/chrome/browser/compose/chrome_compose_client.cc b/chrome/browser/compose/chrome_compose_client.cc
index 8efdce1..b1f820e 100644
--- a/chrome/browser/compose/chrome_compose_client.cc
+++ b/chrome/browser/compose/chrome_compose_client.cc
@@ -112,7 +112,12 @@
   nudge_tracker_.StartObserving(web_contents);
 }
 
-ChromeComposeClient::~ChromeComposeClient() = default;
+ChromeComposeClient::~ChromeComposeClient() {
+  // Sessions may call back during destruction through ComposeSession::Observer.
+  // Let's ensure that happens before destroying anything else.
+  sessions_.clear();
+  debug_session_.reset();
+}
 
 void ChromeComposeClient::BindComposeDialog(
     mojo::PendingReceiver<compose::mojom::ComposeClientUntrustedPageHandler>
@@ -129,7 +134,8 @@
       url::Origin::Create(GURL(chrome::kChromeUIUntrustedComposeUrl))) {
     debug_session_ = std::make_unique<ComposeSession>(
         &GetWebContents(), GetModelExecutor(), GetModelQualityLogsUploader(),
-        GetSessionId(), GetInnerTextProvider(), autofill::FieldRendererId(-1));
+        GetSessionId(), GetInnerTextProvider(), autofill::FieldRendererId(-1),
+        this);
     debug_session_->set_collect_inner_text(false);
     debug_session_->set_fre_complete(
         pref_service_->GetBoolean(prefs::kPrefHasCompletedComposeFRE));
@@ -328,7 +334,7 @@
     auto new_session = std::make_unique<ComposeSession>(
         &GetWebContents(), GetModelExecutor(), GetModelQualityLogsUploader(),
         GetSessionId(), GetInnerTextProvider(),
-        trigger_field.global_id().renderer_id, std::move(callback));
+        trigger_field.global_id().renderer_id, this, std::move(callback));
     current_session = new_session.get();
     sessions_.insert_or_assign(active_compose_ids_.value().first,
                                std::move(new_session));
@@ -547,6 +553,7 @@
 }
 
 void ChromeComposeClient::DisableProactiveNudge() {
+  nudge_tracker_.OnUserDisabledNudge(/*single_site_only=*/false);
   proactive_nudge_enabled_.SetValue(false);
 }
 
@@ -562,6 +569,7 @@
 }
 
 void ChromeComposeClient::AddSiteToNeverPromptList(const url::Origin& origin) {
+  nudge_tracker_.OnUserDisabledNudge(/*single_site_only=*/true);
   ScopedDictPrefUpdate update(pref_service_,
                               prefs::kProactiveNudgeDisabledSitesWithTime);
   update->Set(origin.Serialize(), base::TimeToValue(base::Time::Now()));
@@ -580,6 +588,14 @@
   return allow_context_menu;
 }
 
+void ChromeComposeClient::OnSessionComplete(
+    autofill::FieldRendererId field_renderer_id,
+    compose::ComposeSessionCloseReason close_reason,
+    const compose::ComposeSessionEvents& events) {
+  nudge_tracker_.ComposeSessionCompleted(field_renderer_id, close_reason,
+                                         events);
+}
+
 void ChromeComposeClient::OnAfterFocusOnFormField(
     autofill::AutofillManager& manager,
     autofill::FormGlobalId form,
@@ -662,6 +678,8 @@
   page_ukm_tracker_ = std::make_unique<compose::PageUkmTracker>(
       page.GetMainDocument().GetPageUkmSourceId());
 
+  nudge_tracker_.Clear();
+
   compose::ComposeTextUsageLogger::GetOrCreateForCurrentDocument(
       &page.GetMainDocument());
 }
diff --git a/chrome/browser/compose/chrome_compose_client.h b/chrome/browser/compose/chrome_compose_client.h
index 4f0effc..e0fd554 100644
--- a/chrome/browser/compose/chrome_compose_client.h
+++ b/chrome/browser/compose/chrome_compose_client.h
@@ -50,6 +50,7 @@
       public autofill::AutofillManager::Observer,
       public compose::mojom::ComposeClientUntrustedPageHandler,
       public compose::ProactiveNudgeTracker::Delegate,
+      public ComposeSession::Observer,
       public InnerTextProvider {
  public:
   using EntryPoint = autofill::AutofillComposeDelegate::UiEntryPoint;
@@ -74,6 +75,11 @@
   void OpenProactiveNudgeSettings() override;
   void AddSiteToNeverPromptList(const url::Origin& origin) override;
 
+  // ComposeSession::Observer:
+  void OnSessionComplete(autofill::FieldRendererId field_renderer_id,
+                         compose::ComposeSessionCloseReason close_reason,
+                         const compose::ComposeSessionEvents& events) override;
+
   // autofill::AutofillManager::Observer:
   // Used to observe field focus changes so that the saved state notification
   // is only shown when an autofill suggestion will not be shown on another
diff --git a/chrome/browser/compose/chrome_compose_client_unittest.cc b/chrome/browser/compose/chrome_compose_client_unittest.cc
index 6a335dc..5be802d 100644
--- a/chrome/browser/compose/chrome_compose_client_unittest.cc
+++ b/chrome/browser/compose/chrome_compose_client_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
@@ -19,7 +20,9 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "base/time/time.h"
 #include "chrome/browser/compose/compose_enabling.h"
+#include "chrome/browser/segmentation_platform/segmentation_platform_service_factory.h"
 #include "chrome/common/compose/compose.mojom.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
@@ -38,6 +41,8 @@
 #include "components/optimization_guide/proto/features/compose.pb.h"
 #include "components/optimization_guide/proto/model_execution.pb.h"
 #include "components/optimization_guide/proto/model_quality_service.pb.h"
+#include "components/segmentation_platform/public/constants.h"
+#include "components/segmentation_platform/public/testing/mock_segmentation_platform_service.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "components/unified_consent/pref_names.h"
 #include "content/public/browser/navigation_entry.h"
@@ -61,11 +66,14 @@
     OptimizationGuideModelExecutionResultStreamingCallback;
 using optimization_guide::OptimizationGuideModelStreamingExecutionResult;
 using optimization_guide::StreamingResponse;
+using segmentation_platform::MockSegmentationPlatformService;
 
 namespace {
 
 const uint64_t kSessionIdHigh = 1234;
 const uint64_t kSessionIdLow = 5678;
+const segmentation_platform::TrainingRequestId kTrainingRequestId =
+    segmentation_platform::TrainingRequestId(456);
 constexpr char kTypeURL[] =
     "type.googleapis.com/optimization_guide.proto.ComposeResponse";
 
@@ -161,6 +169,15 @@
     scoped_compose_enabled_ = ComposeEnabling::ScopedEnableComposeForTesting();
     BrowserWithTestWindowTest::SetUp();
 
+    segmentation_platform::SegmentationPlatformServiceFactory::GetInstance()
+        ->SetTestingFactory(
+            GetProfile(),
+            base::BindLambdaForTesting([](content::BrowserContext* context) {
+              std::unique_ptr<KeyedService> result =
+                  std::make_unique<MockSegmentationPlatformService>();
+              return result;
+            }));
+
     scoped_feature_list_.InitWithFeatures(
         {compose::features::kEnableCompose,
          optimization_guide::features::kOptimizationGuideModelExecution},
@@ -211,10 +228,25 @@
                                                    LogAiDataRequest>(),
                               nullptr))));
             })));
+
+    ON_CALL(GetSegmentationPlatformService(),
+            GetClassificationResult(_, _, _, _))
+        .WillByDefault(testing::WithArg<3>(testing::Invoke(
+            [](segmentation_platform::ClassificationResultCallback callback) {
+              auto result = segmentation_platform::ClassificationResult(
+                  segmentation_platform::PredictionStatus::kSucceeded);
+              result.request_id = kTrainingRequestId;
+              result.ordered_labels = {
+                  segmentation_platform::kComposePrmotionLabelShow};
+              std::move(callback).Run(result);
+            })));
+
     test_timer_ = std::make_unique<base::ScopedMockElapsedTimersForTest>();
   }
 
   void TearDown() override {
+    // Clear default actions for safe teardown.
+    testing::Mock::VerifyAndClear(&GetSegmentationPlatformService());
     client_ = nullptr;
     scoped_feature_list_.Reset();
     ukm_recorder_.reset();
@@ -327,6 +359,12 @@
     field_data().set_selected_text(selection.substr(0, max_length));
   }
 
+  MockSegmentationPlatformService& GetSegmentationPlatformService() {
+    return *static_cast<MockSegmentationPlatformService*>(
+        segmentation_platform::SegmentationPlatformServiceFactory::
+            GetForProfile(GetProfile()));
+  }
+
  protected:
   optimization_guide::proto::ComposePageMetadata ComposePageMetadata() {
     optimization_guide::proto::ComposePageMetadata page_metadata;
@@ -736,6 +774,55 @@
               ukm::builders::Compose_PageEvents::kProactiveNudgeShownName, 0)));
 }
 
+TEST_F(ChromeComposeClientTest, TestProactiveNudgeEngagementIsRecorded) {
+  // Enable and trigger the proactive nudge.
+  compose::Config& config = compose::GetMutableConfigForTesting();
+  config.proactive_nudge_enabled = true;
+  config.proactive_nudge_show_probability = 1.0;
+  config.proactive_nudge_delay = base::Microseconds(1);
+  config.proactive_nudge_segmentation = true;
+
+  autofill::FormData form_data;
+  form_data.url = web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL();
+  form_data.fields = {autofill::test::CreateTestFormField(
+      "label0", "name0", "value0", autofill::FormControlType::kTextArea)};
+
+  autofill::FormFieldData selected_field_data = form_data.fields[0];
+  selected_field_data.set_origin(
+      web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());
+  const autofill::AutofillSuggestionTriggerSource trigger_source =
+      autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange;
+
+  while (!client().ShouldTriggerPopup(selected_field_data, trigger_source)) {
+    task_environment()->RunUntilIdle();
+  }
+
+  // Simulate clicking on the nudge to open compose.
+  ShowDialogAndBindMojoWithFieldData(
+      selected_field_data, base::NullCallback(),
+      autofill::AutofillComposeDelegate::UiEntryPoint::kAutofillPopup);
+
+  base::test::TestFuture<segmentation_platform::TrainingLabels> training_labels;
+  EXPECT_CALL(GetSegmentationPlatformService(),
+              CollectTrainingData(
+                  segmentation_platform::proto::SegmentId::
+                      OPTIMIZATION_TARGET_SEGMENTATION_COMPOSE_PROMOTION,
+                  kTrainingRequestId, _, _))
+      .Times(1)
+      .WillOnce(testing::WithArg<2>(testing::Invoke(
+          [&](auto labels) { training_labels.SetValue(labels); })));
+
+  client().CloseUI(compose::mojom::CloseReason::kInsertButton);
+
+  // Trigger session deletion and verify that the engagement is recorded.
+  NavigateAndCommitActiveTab(GURL("about:blank"));
+  EXPECT_EQ(training_labels.Get().output_metric,
+            std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
+                           static_cast<base::HistogramBase::Sample>(
+                               compose::ProactiveNudgeDerivedEngagement::
+                                   kAcceptedComposeSuggestion)));
+}
+
 TEST_F(ChromeComposeClientTest, TestShouldTriggerProactiveNudgeDisabledUKM) {
   autofill::FormData form_data;
   form_data.url = web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL();
diff --git a/chrome/browser/compose/compose_session.cc b/chrome/browser/compose/compose_session.cc
index 29fdd416..ea012aa7 100644
--- a/chrome/browser/compose/compose_session.cc
+++ b/chrome/browser/compose/compose_session.cc
@@ -202,6 +202,7 @@
     base::Token session_id,
     InnerTextProvider* inner_text,
     autofill::FieldRendererId node_id,
+    Observer* observer,
     ComposeCallback callback)
     : executor_(executor),
       handler_receiver_(this),
@@ -214,6 +215,7 @@
       close_reason_(compose::ComposeSessionCloseReason::kEndedImplicitly),
       final_status_(optimization_guide::proto::FinalStatus::STATUS_UNSPECIFIED),
       web_contents_(web_contents),
+      observer_(observer),
       collect_inner_text_(
           base::FeatureList::IsEnabled(compose::features::kComposeInnerText)),
       inner_text_caller_(inner_text),
@@ -261,6 +263,10 @@
   std::optional<compose::EvalLocation> eval_location =
       compose::GetEvalLocationFromEvents(session_events_);
 
+  if (observer_) {
+    observer_->OnSessionComplete(node_id_, close_reason_, session_events_);
+  }
+
   if (session_events_.fre_dialog_shown_count > 0 &&
       (!fre_complete_ || session_events_.fre_completed_in_session)) {
     compose::LogComposeFirstRunSessionCloseReason(fre_close_reason_);
diff --git a/chrome/browser/compose/compose_session.h b/chrome/browser/compose/compose_session.h
index 3aab636..34b6ab28 100644
--- a/chrome/browser/compose/compose_session.h
+++ b/chrome/browser/compose/compose_session.h
@@ -76,6 +76,13 @@
   // form field on which it was triggered.
   using ComposeCallback = base::OnceCallback<void(const std::u16string&)>;
 
+  class Observer {
+   public:
+    virtual void OnSessionComplete(
+        autofill::FieldRendererId node_id,
+        compose::ComposeSessionCloseReason close_reason,
+        const compose::ComposeSessionEvents& events) = 0;
+  };
   ComposeSession(
       content::WebContents* web_contents,
       optimization_guide::OptimizationGuideModelExecutor* executor,
@@ -83,6 +90,7 @@
       base::Token session_id,
       InnerTextProvider* inner_text,
       autofill::FieldRendererId node_id,
+      Observer* observer,
       ComposeCallback callback = base::NullCallback());
   ~ComposeSession() override;
 
@@ -335,6 +343,8 @@
   // `this`.
   raw_ptr<content::WebContents> web_contents_;
 
+  raw_ptr<Observer> observer_;
+
   // A callback to Autofill that triggers filling the field.
   ComposeCallback callback_;
 
diff --git a/chrome/browser/compose/proactive_nudge_tracker.cc b/chrome/browser/compose/proactive_nudge_tracker.cc
index 4467608..5ee3e82c 100644
--- a/chrome/browser/compose/proactive_nudge_tracker.cc
+++ b/chrome/browser/compose/proactive_nudge_tracker.cc
@@ -4,11 +4,69 @@
 
 #include "chrome/browser/compose/proactive_nudge_tracker.h"
 
+#include <memory>
+
+#include "base/functional/callback_helpers.h"
+#include "components/compose/core/browser/compose_metrics.h"
 #include "components/compose/core/browser/config.h"
 #include "components/segmentation_platform/public/constants.h"
 
 namespace compose {
 
+// Tracks user engagement as a result of the nudge.
+class ProactiveNudgeTracker::EngagementTracker {
+ public:
+  EngagementTracker(
+      autofill::FieldRendererId field_renderer_id,
+      segmentation_platform::TrainingRequestId training_request_id,
+      ProactiveNudgeTracker* nudge_tracker)
+      : field_renderer_id_(field_renderer_id),
+        training_request_id_(training_request_id),
+        nudge_tracker_(nudge_tracker) {}
+  EngagementTracker(const EngagementTracker&) = delete;
+  EngagementTracker& operator=(const EngagementTracker&) = delete;
+
+  ~EngagementTracker() {
+    ReportIfFirst(ProactiveNudgeDerivedEngagement::kIgnored);
+  }
+
+  void ComposeSessionCompleted(ComposeSessionCloseReason session_close_reason,
+                               const compose::ComposeSessionEvents& events) {
+    if (events.inserted_results) {
+      ReportIfFirst(
+          ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion);
+    } else if (events.compose_count > 0) {
+      ReportIfFirst(
+          ProactiveNudgeDerivedEngagement::kGeneratedComposeSuggestion);
+    } else {
+      ReportIfFirst(ProactiveNudgeDerivedEngagement::kOpenedComposeMinimalUse);
+    }
+  }
+
+  void UserDisabledNudge(bool single_site_only) {
+    if (single_site_only) {
+      ReportIfFirst(
+          ProactiveNudgeDerivedEngagement::kNudgeDisabledOnSingleSite);
+    } else {
+      ReportIfFirst(ProactiveNudgeDerivedEngagement::kNudgeDisabledOnAllSites);
+    }
+  }
+
+ private:
+  void ReportIfFirst(ProactiveNudgeDerivedEngagement engagement) {
+    if (reported_) {
+      return;
+    }
+    nudge_tracker_->CollectTrainingData(training_request_id_, engagement);
+    reported_ = true;
+  }
+
+  bool reported_ = false;
+  autofill::FieldRendererId field_renderer_id_;
+  segmentation_platform::TrainingRequestId training_request_id_;
+  raw_ptr<ProactiveNudgeTracker> nudge_tracker_;
+};
+
 ProactiveNudgeTracker::State::State() = default;
 ProactiveNudgeTracker::State::~State() = default;
 
@@ -27,7 +85,11 @@
                         InitializationPolicy::kObservePreexistingManagers);
 }
 
-ProactiveNudgeTracker::~ProactiveNudgeTracker() = default;
+ProactiveNudgeTracker::~ProactiveNudgeTracker() {
+  // Destroy all engagement trackers first to ensure CollectTrainingData() is
+  // called before other parts of this class are destroyed.
+  engagement_trackers_.clear();
+}
 
 bool ProactiveNudgeTracker::ProactiveNudgeRequestedForFormField(
     const autofill::FormFieldData& field_to_track) {
@@ -61,8 +123,6 @@
         segmentation_platform::kComposePromotionKey, options, nullptr,
         base::BindOnce(&ProactiveNudgeTracker::GotClassificationResult,
                        weak_ptr_factory_.GetWeakPtr(), state_->AsWeakPtr()));
-  } else {
-    state_->segmentation_result = true;
   }
 
   base::TimeDelta delay = compose::GetComposeConfig().proactive_nudge_delay;
@@ -74,7 +134,7 @@
                         &ProactiveNudgeTracker::ShowTimerElapsed);
   }
 
-  if (state_->segmentation_result && state_->timer_complete) {
+  if (ShouldShow(*state_)) {
     // If the timer is 0-duration and no segmentation result is required, then
     // just transition to Shown state directly before returning true.
     state_->show_state = ShowState::kShown;
@@ -83,10 +143,27 @@
   return false;
 }
 
+bool ProactiveNudgeTracker::ShouldShow(const State& state) {
+  if (!state.timer_complete) {
+    return false;
+  }
+  if (!compose::GetComposeConfig().proactive_nudge_segmentation) {
+    return true;
+  }
+  return state.segmentation_result &&
+         state.segmentation_result->ordered_labels[0] ==
+             segmentation_platform::kComposePrmotionLabelShow;
+}
+
 void ProactiveNudgeTracker::FocusChangedInPage() {
   ResetState();
 }
 
+void ProactiveNudgeTracker::Clear() {
+  engagement_trackers_.clear();
+  ResetState();
+}
+
 void ProactiveNudgeTracker::OnAfterFocusOnFormField(
     autofill::AutofillManager& manager,
     autofill::FormGlobalId form,
@@ -127,12 +204,20 @@
 
 void ProactiveNudgeTracker::MaybeShowProactiveNudge() {
   DVLOG(2) << "ProactiveNudgeTracker: MaybeShowProactiveNudge ";
-  if (state_ && state_->segmentation_result.value_or(false) &&
-      state_->timer_complete) {
-    // Transition to the SHOWN state.
-    delegate_->ShowProactiveNudge(state_->form, state_->field);
-    state_->show_state = ShowState::kCanBeShown;
+  if (!state_ || !ShouldShow(*state_)) {
+    return;
   }
+
+  // Transition to the SHOWN state.
+
+  if (state_->segmentation_result) {
+    engagement_trackers_[state_->field.renderer_id] =
+        std::make_unique<EngagementTracker>(
+            state_->field.renderer_id, state_->segmentation_result->request_id,
+            this);
+  }
+  delegate_->ShowProactiveNudge(state_->form, state_->field);
+  state_->show_state = ShowState::kCanBeShown;
 }
 
 void ProactiveNudgeTracker::GotClassificationResult(
@@ -148,14 +233,46 @@
     ResetState();
     return;
   }
-  state->segmentation_result = result.ordered_labels[0] ==
-                               segmentation_platform::kComposePrmotionLabelShow;
+  state->segmentation_result = std::move(result);
+
   MaybeShowProactiveNudge();
 }
 
+void ProactiveNudgeTracker::CollectTrainingData(
+    const segmentation_platform::TrainingRequestId training_request_id,
+    ProactiveNudgeDerivedEngagement engagement) {
+  segmentation_platform::TrainingLabels training_labels;
+  // TODO(harringtond): Add UMA for Compose.ProactiveNudge.DerivedEngagement.
+  training_labels.output_metric =
+      std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
+                     static_cast<base::HistogramBase::Sample>(engagement));
+  segmentation_service_->CollectTrainingData(
+      segmentation_platform::proto::SegmentId::
+          OPTIMIZATION_TARGET_SEGMENTATION_COMPOSE_PROMOTION,
+      training_request_id, training_labels, base::DoNothing());
+}
+
 bool ProactiveNudgeTracker::MatchesCurrentField(autofill::FormGlobalId form,
                                                 autofill::FieldGlobalId field) {
   return state_ && state_->form == form && state_->field == field;
 }
 
+void ProactiveNudgeTracker::ComposeSessionCompleted(
+    autofill::FieldRendererId field_renderer_id,
+    ComposeSessionCloseReason session_close_reason,
+    const compose::ComposeSessionEvents& events) {
+  auto iter = engagement_trackers_.find(field_renderer_id);
+  if (iter != engagement_trackers_.end()) {
+    iter->second->ComposeSessionCompleted(session_close_reason, events);
+    engagement_trackers_.erase(iter);
+  }
+}
+
+void ProactiveNudgeTracker::OnUserDisabledNudge(bool single_site_only) {
+  for (auto& iter : engagement_trackers_) {
+    iter.second->UserDisabledNudge(single_site_only);
+  }
+  engagement_trackers_.clear();
+}
+
 }  // namespace compose
diff --git a/chrome/browser/compose/proactive_nudge_tracker.h b/chrome/browser/compose/proactive_nudge_tracker.h
index 41e2b10..0d0f962 100644
--- a/chrome/browser/compose/proactive_nudge_tracker.h
+++ b/chrome/browser/compose/proactive_nudge_tracker.h
@@ -5,14 +5,18 @@
 #ifndef CHROME_BROWSER_COMPOSE_PROACTIVE_NUDGE_TRACKER_H_
 #define CHROME_BROWSER_COMPOSE_PROACTIVE_NUDGE_TRACKER_H_
 
+#include <map>
+#include <memory>
 #include <optional>
 #include <string>
 
+#include "base/functional/callback_forward.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "components/autofill/content/browser/scoped_autofill_managers_observation.h"
 #include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/unique_ids.h"
+#include "components/compose/core/browser/compose_metrics.h"
 #include "components/segmentation_platform/public/segmentation_platform_service.h"
 
 namespace compose {
@@ -57,7 +61,8 @@
     autofill::FormGlobalId form;
     autofill::FieldGlobalId field;
     std::u16string initial_text_value;
-    std::optional<bool> segmentation_result = std::nullopt;
+    std::optional<segmentation_platform::ClassificationResult>
+        segmentation_result = std::nullopt;
     base::OneShotTimer timer;
     bool timer_complete = false;
 
@@ -88,12 +93,20 @@
 
   void FocusChangedInPage();
 
+  void Clear();
+
+  void ComposeSessionCompleted(autofill::FieldRendererId field_renderer_id,
+                               ComposeSessionCloseReason session_close_reason,
+                               const compose::ComposeSessionEvents& events);
+  void OnUserDisabledNudge(bool single_site_only);
+
   // autofill::AutofillManager::Observer:
   void OnAfterFocusOnFormField(autofill::AutofillManager& manager,
                                autofill::FormGlobalId form,
                                autofill::FieldGlobalId field) override;
 
  private:
+  class EngagementTracker;
   bool SegmentationStateIsValid();
   void ResetState();
   void ShowTimerElapsed();
@@ -103,9 +116,16 @@
   void MaybeShowProactiveNudge();
   bool MatchesCurrentField(autofill::FormGlobalId form,
                            autofill::FieldGlobalId field);
+  void CollectTrainingData(
+      const segmentation_platform::TrainingRequestId training_request_id,
+      ProactiveNudgeDerivedEngagement engagement);
+  bool ShouldShow(const State& state);
 
   std::unique_ptr<State> state_;
 
+  std::map<autofill::FieldRendererId, std::unique_ptr<EngagementTracker>>
+      engagement_trackers_;
+
   raw_ptr<segmentation_platform::SegmentationPlatformService>
       segmentation_service_;
   raw_ptr<Delegate> delegate_;
diff --git a/chrome/browser/compose/proactive_nudge_tracker_unittest.cc b/chrome/browser/compose/proactive_nudge_tracker_unittest.cc
index 4166b353..38ea9a3 100644
--- a/chrome/browser/compose/proactive_nudge_tracker_unittest.cc
+++ b/chrome/browser/compose/proactive_nudge_tracker_unittest.cc
@@ -4,48 +4,69 @@
 
 #include "chrome/browser/compose/proactive_nudge_tracker.h"
 
+#include <memory>
+
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "components/autofill/core/common/autofill_test_utils.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/autofill/core/common/unique_ids.h"
+#include "components/compose/core/browser/compose_metrics.h"
 #include "components/compose/core/browser/config.h"
 #include "components/segmentation_platform/public/constants.h"
 #include "components/segmentation_platform/public/testing/mock_segmentation_platform_service.h"
+#include "components/segmentation_platform/public/trigger.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using testing::_;
 
 namespace compose {
+namespace {
+
+using base::test::TestFuture;
+using testing::_;
+const autofill::FieldRendererId kFieldRendererId(123);
+const autofill::FieldRendererId kFieldRendererId2(4);
+
+segmentation_platform::TrainingRequestId TrainingRequestId(
+    int request_number = 0) {
+  return segmentation_platform::TrainingRequestId(request_number + 456);
+}
+
+autofill::FormFieldData CreateTestFormFieldData(
+    autofill::FieldRendererId renderer_id = kFieldRendererId) {
+  autofill::FormFieldData f;
+  f.set_host_frame(autofill::test::MakeLocalFrameToken());
+  f.set_renderer_id(renderer_id);
+  f.set_value(u"FormFieldDataInitialValue");
+  return f;
+}
 
 class MockProactiveNudgeTrackerDelegate
     : public ProactiveNudgeTracker::Delegate {
  public:
   MOCK_METHOD(void,
               ShowProactiveNudge,
-              (autofill::FormGlobalId, autofill::FieldGlobalId),
-              (override));
+              (autofill::FormGlobalId, autofill::FieldGlobalId));
 };
 
-class ProactiveNudgeTrackerTest : public testing::TestWithParam<bool> {
+class ProactiveNudgeTrackerTestBase : public testing::Test {
  public:
-  ProactiveNudgeTrackerTest() = default;
+  ProactiveNudgeTrackerTestBase() = default;
 
-  ProactiveNudgeTrackerTest(const ProactiveNudgeTrackerTest&) = delete;
-  ProactiveNudgeTrackerTest& operator=(const ProactiveNudgeTrackerTest&) =
-      delete;
+  ProactiveNudgeTrackerTestBase(const ProactiveNudgeTrackerTestBase&) = delete;
+  ProactiveNudgeTrackerTestBase& operator=(
+      const ProactiveNudgeTrackerTestBase&) = delete;
 
-  ~ProactiveNudgeTrackerTest() override = default;
+  ~ProactiveNudgeTrackerTestBase() override = default;
 
-  void SetUp() override {
-    testing::TestWithParam<bool>::SetUp();
+  void SetUpNudgeTrackerTest(bool use_segmentation) {
     compose::GetMutableConfigForTesting().proactive_nudge_segmentation =
-        GetParam();
+        use_segmentation;
     nudge_tracker_ = std::make_unique<ProactiveNudgeTracker>(
         &segmentation_service_, &delegate_);
 
-    if (uses_segmentation()) {
+    if (use_segmentation) {
       SetSegmentationResult();
     } else {
       EXPECT_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
@@ -54,7 +75,6 @@
   }
 
   void TearDown() override {
-    testing::TestWithParam<bool>::TearDown();
     compose::ResetConfigForTesting();
   }
 
@@ -68,21 +88,14 @@
   }
   ProactiveNudgeTracker& nudge_tracker() { return *nudge_tracker_; }
 
-  autofill::FormFieldData CreateTestFormFieldData() {
-    autofill::FormFieldData f;
-    f.set_host_frame(autofill::test::MakeLocalFrameToken());
-    f.set_renderer_id(autofill::FieldRendererId(123));
-    f.set_value(u"FormFieldDataInitialValue");
-    return f;
-  }
-
   void SetSegmentationResult(std::string label = "Show") {
     ON_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
         .WillByDefault(testing::WithArg<3>(testing::Invoke(
-            [label](
+            [label, this](
                 segmentation_platform::ClassificationResultCallback callback) {
               auto result = segmentation_platform::ClassificationResult(
                   segmentation_platform::PredictionStatus::kSucceeded);
+              result.request_id = TrainingRequestId(training_request_number_++);
               result.ordered_labels = {label};
               std::move(callback).Run(result);
             })));
@@ -100,8 +113,6 @@
             })));
   }
 
-  bool uses_segmentation() { return GetParam(); }
-
  private:
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -112,6 +123,21 @@
   testing::NiceMock<segmentation_platform::MockSegmentationPlatformService>
       segmentation_service_;
   std::unique_ptr<ProactiveNudgeTracker> nudge_tracker_;
+  int training_request_number_ = 0;
+};
+
+class ProactiveNudgeTrackerTest : public ProactiveNudgeTrackerTestBase,
+                                  public testing::WithParamInterface<bool> {
+ public:
+  ProactiveNudgeTrackerTest() = default;
+
+  ~ProactiveNudgeTrackerTest() override = default;
+
+  void SetUp() override {
+    ProactiveNudgeTrackerTestBase::SetUpNudgeTrackerTest(uses_segmentation());
+  }
+
+  bool uses_segmentation() { return GetParam(); }
 };
 
 TEST_P(ProactiveNudgeTrackerTest, TestWait) {
@@ -128,7 +154,7 @@
   // Should not nudge if nudge is requested too soon.
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
   if (uses_segmentation()) {
     EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
     auto result = segmentation_platform::ClassificationResult(
@@ -159,7 +185,7 @@
   // Should not nudge if nudge is requested too soon.
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
   EXPECT_TRUE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 }
 
@@ -172,7 +198,7 @@
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
   nudge_tracker().FocusChangedInPage();
 
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 }
 
@@ -189,7 +215,7 @@
 
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field2));
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 }
 
@@ -200,7 +226,7 @@
       .Times(0);
 
   nudge_tracker().FocusChangedInPage();
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
 }
 
 TEST_P(ProactiveNudgeTrackerTest, TestNoNudgeDelay) {
@@ -226,7 +252,7 @@
         .Times(0);
     EXPECT_TRUE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
     // Wait just in case the timer could be pending.
-    task_environment().FastForwardBy(base::Seconds(4));
+    task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
   }
 }
 
@@ -243,7 +269,7 @@
       .Times(uses_segmentation() ? 0 : 1);
 
   EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
-  task_environment().FastForwardBy(base::Seconds(4));
+  task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
 
   if (uses_segmentation()) {
     EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
@@ -258,7 +284,194 @@
             nudge_tracker().ProactiveNudgeRequestedForFormField(field));
 }
 
-INSTANTIATE_TEST_SUITE_P(ProactiveNudgeTrackerTest,
+INSTANTIATE_TEST_SUITE_P(,
                          ProactiveNudgeTrackerTest,
-                         ::testing::Bool());
+                         ::testing::Bool(),
+                         [](const auto& info) {
+                           return info.param ? "SegmentationON"
+                                             : "SegmentationOFF";
+                         });
+
+class ProactiveNudgeTrackerDerivedEngagementTest
+    : public ProactiveNudgeTrackerTestBase {
+ public:
+  void SetUp() override {
+    ProactiveNudgeTrackerTestBase::SetUpNudgeTrackerTest(true);
+  }
+
+  // Set up a scenario where the nudge is shown for a field.
+  TestFuture<segmentation_platform::TrainingLabels>& TriggerNudgeForField(
+      int request_number,
+      const autofill::FormFieldData& field) {
+    EXPECT_CALL(delegate(),
+                ShowProactiveNudge(field.renderer_form_id(), field.global_id()))
+        .Times(1);
+
+    EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
+    task_environment().FastForwardBy(GetComposeConfig().proactive_nudge_delay);
+    EXPECT_TRUE(nudge_tracker().ProactiveNudgeRequestedForFormField(field));
+
+    TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+        training_labels_futures_.emplace_back();
+    EXPECT_CALL(segmentation_service(),
+                CollectTrainingData(
+                    segmentation_platform::proto::SegmentId::
+                        OPTIMIZATION_TARGET_SEGMENTATION_COMPOSE_PROMOTION,
+                    TrainingRequestId(request_number), _, _))
+        .Times(1)
+        .WillOnce(testing::Invoke([&](auto, auto, auto labels, auto) {
+          training_labels.SetValue(labels);
+        }));
+    return training_labels;
+  }
+
+  // Destroy the nudge tracker. This triggers CollectTrainingData() if
+  // necessary.
+  void Reset() { SetUpNudgeTrackerTest(true); }
+
+ private:
+  // Just holds memory for futures created in TriggerNudgeForField(), not for
+  // direct use.
+  std::deque<TestFuture<segmentation_platform::TrainingLabels>>
+      training_labels_futures_;
+};
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NoEngagement) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  Reset();
+
+  EXPECT_EQ(training_labels.Get().output_metric,
+            std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
+                           static_cast<base::HistogramBase::Sample>(
+                               ProactiveNudgeDerivedEngagement::kIgnored)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, MinimalUse) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  compose::ComposeSessionEvents events;
+  nudge_tracker().ComposeSessionCompleted(
+      kFieldRendererId, ComposeSessionCloseReason::kCloseButtonPressed, events);
+  Reset();
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kOpenedComposeMinimalUse)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, SuggestionGenerated) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  compose::ComposeSessionEvents events;
+  events.compose_count = 1;
+  nudge_tracker().ComposeSessionCompleted(
+      kFieldRendererId, ComposeSessionCloseReason::kCloseButtonPressed, events);
+  // This test should work with or without deleting the tracker.
+  Reset();
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kGeneratedComposeSuggestion)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, AcceptedSuggestion) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  compose::ComposeSessionEvents events;
+  events.compose_count = 1;
+  events.inserted_results = true;
+  nudge_tracker().ComposeSessionCompleted(
+      kFieldRendererId, ComposeSessionCloseReason::kAcceptedSuggestion, events);
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest,
+       IgnoresSessionForDifferentField) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  compose::ComposeSessionEvents events;
+  // This one is ignored because it has the wrong field id.
+  nudge_tracker().ComposeSessionCompleted(
+      autofill::FieldRendererId(999),
+      ComposeSessionCloseReason::kEndedImplicitly, events);
+
+  events.compose_count = 1;
+  events.inserted_results = true;
+  nudge_tracker().ComposeSessionCompleted(
+      kFieldRendererId, ComposeSessionCloseReason::kAcceptedSuggestion, events);
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, TwoSessions) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels1 =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels2 =
+      TriggerNudgeForField(1, CreateTestFormFieldData(kFieldRendererId2));
+  compose::ComposeSessionEvents events;
+  events.compose_count = 1;
+  events.inserted_results = true;
+  nudge_tracker().ComposeSessionCompleted(
+      kFieldRendererId, ComposeSessionCloseReason::kAcceptedSuggestion, events);
+  Reset();
+
+  EXPECT_EQ(
+      training_labels1.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
+  EXPECT_EQ(training_labels2.Get().output_metric,
+            std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
+                           static_cast<base::HistogramBase::Sample>(
+                               ProactiveNudgeDerivedEngagement::kIgnored)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NudgeDisabledSingleSite) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  nudge_tracker().OnUserDisabledNudge(/*single_site_only=*/true);
+  Reset();
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kNudgeDisabledOnSingleSite)));
+}
+
+TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NudgeDisabledAllSites) {
+  TestFuture<segmentation_platform::TrainingLabels>& training_labels =
+      TriggerNudgeForField(0, CreateTestFormFieldData());
+  nudge_tracker().OnUserDisabledNudge(/*single_site_only=*/false);
+  Reset();
+
+  EXPECT_EQ(
+      training_labels.Get().output_metric,
+      std::make_pair(
+          "Compose.ProactiveNudge.DerivedEngagement",
+          static_cast<base::HistogramBase::Sample>(
+              ProactiveNudgeDerivedEngagement::kNudgeDisabledOnAllSites)));
+}
+
+}  // namespace
 }  // namespace compose
diff --git a/chrome/browser/data_sharing/BUILD.gn b/chrome/browser/data_sharing/BUILD.gn
index 94b9122..e036cc21 100644
--- a/chrome/browser/data_sharing/BUILD.gn
+++ b/chrome/browser/data_sharing/BUILD.gn
@@ -121,6 +121,7 @@
       "//chrome/browser/flags:java",
       "//chrome/browser/profiles/android:java",
       "//chrome/test/android:chrome_java_integration_test_support",
+      "//components/data_sharing:test_support_java",
       "//components/data_sharing/public:public_java",
       "//content/public/test/android:content_java_test_support",
       "//third_party/androidx:androidx_test_runner_java",
diff --git a/chrome/browser/data_sharing/DEPS b/chrome/browser/data_sharing/DEPS
index 2d2a5921..50313c3 100644
--- a/chrome/browser/data_sharing/DEPS
+++ b/chrome/browser/data_sharing/DEPS
@@ -2,5 +2,5 @@
   # Minimize dependencies on internal code, but allow service construction.
   "+components/data_sharing/internal/data_sharing_service_impl.h",
   "+components/data_sharing/internal/empty_data_sharing_service.h",
-  "+components/data_sharing/test_support/mock_data_sharing_service.h",
+  "+components/data_sharing/test_support",
 ]
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
index e3f13f0..2453566a 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingServiceFactoryTest.java
@@ -13,7 +13,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.UserDataHost;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
@@ -25,9 +24,12 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.components.data_sharing.DataSharingNetworkLoader;
 import org.chromium.components.data_sharing.DataSharingService;
+import org.chromium.components.data_sharing.PeopleGroupActionFailure;
+import org.chromium.components.data_sharing.PeopleGroupActionOutcome;
+import org.chromium.components.data_sharing.TestDataSharingService;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeoutException;
 
 @RunWith(BaseJUnit4ClassRunner.class)
@@ -42,23 +44,7 @@
     @Test
     @MediumTest
     public void testSettingTestFactory() throws TimeoutException {
-        DataSharingService testService =
-                new DataSharingService() {
-                    @Override
-                    public boolean isEmptyService() {
-                        return true;
-                    }
-
-                    @Override
-                    public DataSharingNetworkLoader getNetworkLoader() {
-                        return null;
-                    }
-
-                    @Override
-                    public UserDataHost getUserDataHost() {
-                        return null;
-                    }
-                };
+        DataSharingService testService = new TestDataSharingService();
 
         DataSharingServiceFactory.setForTesting(testService);
         LibraryLoader.getInstance().ensureInitialized();
@@ -84,16 +70,83 @@
         LibraryLoader.getInstance().ensureInitialized();
         mActivityTestRule.startMainActivityOnBlankPage();
 
+        CountDownLatch countDownLatch = new CountDownLatch(6); // 6 method calls to wait for.
+
         mActivityTestRule.runOnUiThread(
                 new Runnable() {
+                    void callbackReceived() {
+                        countDownLatch.countDown();
+                    }
+
                     @Override
                     public void run() {
                         DataSharingService dataSharingService =
                                 DataSharingServiceFactory.getForProfile(
                                         ProfileManager.getLastUsedRegularProfile());
                         Assert.assertFalse(dataSharingService.isEmptyService());
+
+                        // TODO(ssid): Add tests with SDK delegate once available.
+                        dataSharingService.readAllGroups(
+                                result -> {
+                                    Assert.assertTrue(result.groupDataSet == null);
+                                    Assert.assertEquals(
+                                            result.actionFailure,
+                                            PeopleGroupActionFailure.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
+                        dataSharingService.readGroup(
+                                "bad_id",
+                                result -> {
+                                    Assert.assertTrue(result.groupData == null);
+                                    Assert.assertEquals(
+                                            result.actionFailure,
+                                            PeopleGroupActionFailure.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
+                        dataSharingService.createGroup(
+                                "bad_name",
+                                result -> {
+                                    Assert.assertTrue(result.groupData == null);
+                                    Assert.assertEquals(
+                                            result.actionFailure,
+                                            PeopleGroupActionFailure.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
+                        dataSharingService.deleteGroup(
+                                "bad_id",
+                                result -> {
+                                    Assert.assertEquals(
+                                            result.intValue(),
+                                            PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
+                        dataSharingService.inviteMember(
+                                "bad_id",
+                                "bad_email",
+                                result -> {
+                                    Assert.assertEquals(
+                                            result.intValue(),
+                                            PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
+                        dataSharingService.removeMember(
+                                "bad_id",
+                                "bad_email",
+                                result -> {
+                                    Assert.assertEquals(
+                                            result.intValue(),
+                                            PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+                                    callbackReceived();
+                                });
                     }
                 });
+
+        // Wait for all the callbacks to return.
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            Assert.assertTrue(false);
+        }
     }
 
     @Test
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index a81aa61f..4caf7082 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -790,6 +790,8 @@
       settings_api::PrefType::kBoolean;
 
   // App - On-Device Parental Controls
+  (*s_allowlist)[::ash::prefs::kOnDeviceAppControlsPin] =
+      settings_api::PrefType::kString;
   (*s_allowlist)[::ash::prefs::kOnDeviceAppControlsSetupCompleted] =
       settings_api::PrefType::kBoolean;
 
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 0dccd42a..d566e5f 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -1036,8 +1036,8 @@
       << message_;
 }
 
-// TODO: crbug.com/1450976 - Re-enable tests on Mac and Lacros.
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO: crbug.com/1450976 - Re-enable tests on Mac and CrOS.
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
 #define MAYBE_WebRequestCORSWithExtraHeaders \
   DISABLED_WebRequestCORSWithExtraHeaders
 #else
diff --git a/chrome/browser/feed/android/java/res/drawable/header_title_section_tab_background.xml b/chrome/browser/feed/android/java/res/drawable/header_title_section_tab_background.xml
index 78304210d..8254883 100644
--- a/chrome/browser/feed/android/java/res/drawable/header_title_section_tab_background.xml
+++ b/chrome/browser/feed/android/java/res/drawable/header_title_section_tab_background.xml
@@ -11,7 +11,7 @@
     <org.chromium.components.browser_ui.widget.SurfaceColorDrawable
         android:shape="rectangle"
         app:surfaceElevation="@dimen/feed_header_section_tab_bg_elevation_enabled">
-      <corners android:radius="@dimen/feed_header_background_corner_radius_polished"/>
+      <corners android:radius="@dimen/feed_header_background_corner_radius"/>
     </org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
   </item>
 </selector>
\ No newline at end of file
diff --git a/chrome/browser/feed/android/java/res/values-night/dimens.xml b/chrome/browser/feed/android/java/res/values-night/dimens.xml
index af0d318d..d6e0ce7 100644
--- a/chrome/browser/feed/android/java/res/values-night/dimens.xml
+++ b/chrome/browser/feed/android/java/res/values-night/dimens.xml
@@ -7,6 +7,5 @@
 
 <resources xmlns:tools="http://schemas.android.com/tools">
   <!-- Dynamic color dimensions. -->
-  <dimen name="feed_header_section_tab_bg_elevation_enabled">@dimen/default_elevation_1</dimen>
   <dimen name="feed_header_tab_selected_bg_elevation">@dimen/default_elevation_4</dimen>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/feed/android/java/res/values/dimens.xml b/chrome/browser/feed/android/java/res/values/dimens.xml
index 1ccbb8bc..541b00b 100644
--- a/chrome/browser/feed/android/java/res/values/dimens.xml
+++ b/chrome/browser/feed/android/java/res/values/dimens.xml
@@ -44,7 +44,7 @@
     <dimen name="feed_options_chip_margin">8dp</dimen>
     <dimen name="feed_header_dropdown_size">15dp</dimen>
     <dimen name="feed_header_tab_extra_margin_right">7dp</dimen>
-    <dimen name="feed_header_background_corner_radius">60dp</dimen>
+    <dimen name="feed_header_background_corner_radius">12dp</dimen>
     <dimen name="feed_header_background_corner_radius_polished">8dp</dimen>
     <dimen name="feed_header_background_outer_corner_radius_polished">12dp</dimen>
     <dimen name="feed_header_tab_layout_width_max">165dp</dimen>
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4236d24..0bb28550 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4561,11 +4561,6 @@
     "expiry_milestone": 130
   },
   {
-    "name": "files-app-experimental",
-    "owners": ["lucmult@chromium.org", "benreich@chromium.org", "simmonsjosh@google.com"],
-    "expiry_milestone": 127
-  },
-  {
     "name": "files-conflict-dialog",
     "owners": [ "simmonsjosh@google.com", "//ui/file_manager/OWNERS" ],
     "expiry_milestone": 135
@@ -5808,7 +5803,7 @@
   {
     "name": "mutation-events",
     "owners": [ "masonf@chromium.org" ],
-    "expiry_milestone": 127
+    "expiry_milestone": 134
   },
   {
     "name": "mute-notification-snooze-action",
@@ -6463,6 +6458,12 @@
     "owners": [ "manukh@chromium.org", "christianxu@chromium.org", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 130
   },
+
+  {
+    "name": "omnibox-rich-autocompletion-android",
+    "owners": [ "gangwu@chromium.org", "chrome-mobile-search@google.com" ],
+    "expiry_milestone": 140
+  },
   {
     "name": "omnibox-rich-autocompletion-promising",
     "owners": [ "manukh@chromium.org", "chrome-omnibox-team@google.com" ],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index a73dc48..60fa1ed 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4432,6 +4432,12 @@
     "Allow revoking site-level notification permission if Chrome has no "
     "app-level notification permission on Android.";
 
+const char kRichAutocompletionAndroidName[] =
+    "Omnibox rich inline autocompletion on Android";
+const char kRichAutocompletionAndroidDescription[] =
+    "Enables omnibox rich inline autocompletion. Expands inline autocomplete "
+    "to any type of input that users repeatedly use to get to specific URLs.";
+
 const char kSafeBrowsingNewGmsApiForBrowseUrlDatabaseCheckName[] =
     "Safe Browsing new GMS API for browse URL database check";
 const char kSafeBrowsingNewGmsApiForBrowseUrlDatabaseCheckDescription[] =
@@ -6634,12 +6640,6 @@
 const char kFileTransferEnterpriseConnectorUIDescription[] =
     "Enable the UI for the File Transfer Enterprise Connector.";
 
-const char kFilesAppExperimentalName[] =
-    "Experimental UI features for Files app";
-const char kFilesAppExperimentalDescription[] =
-    "Enable experimental UI features for Files app. Experimental features are "
-    "expected to be non functional to end users.";
-
 const char kFilesConflictDialogName[] = "Files app conflict dialog";
 const char kFilesConflictDialogDescription[] =
     "When enabled, the conflict dialog will be shown during file transfers "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1e52b03b..bf3b4b528 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2629,6 +2629,9 @@
 extern const char
     kRevokeNotificationsPermissionIfDisabledOnAppLevelDescription[];
 
+extern const char kRichAutocompletionAndroidName[];
+extern const char kRichAutocompletionAndroidDescription[];
+
 extern const char kSafeBrowsingNewGmsApiForBrowseUrlDatabaseCheckName[];
 extern const char kSafeBrowsingNewGmsApiForBrowseUrlDatabaseCheckDescription[];
 
@@ -3848,9 +3851,6 @@
 extern const char kFileTransferEnterpriseConnectorUIName[];
 extern const char kFileTransferEnterpriseConnectorUIDescription[];
 
-extern const char kFilesAppExperimentalName[];
-extern const char kFilesAppExperimentalDescription[];
-
 extern const char kFilesConflictDialogName[];
 extern const char kFilesConflictDialogDescription[];
 
diff --git a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
index 9c95e8de..fcea8a5 100644
--- a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
@@ -1298,11 +1298,12 @@
   ValidateForMemCacheLoadedImages();
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_MAC)
 #define MAYBE_NativeLazyLoadingImage DISABLED_NativeLazyLoadingImage
 #else
 #define MAYBE_NativeLazyLoadingImage NativeLazyLoadingImage
 #endif
+// TODO(crbug.com/335901379): Re-enable test
 IN_PROC_BROWSER_TEST_F(LcpBreakdownTimingsTest, MAYBE_NativeLazyLoadingImage) {
   std::string test_url =
       "/lcp_breakdown_timings_native_lazy_loading_images.html";
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
index cbdb331c..9bf5cdc 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -1175,7 +1175,6 @@
         promise.then(
                 playback -> {
                     Log.d(TAG, "Voice preview playback created.");
-                    ReadAloudMetrics.recordVoicePreviewed(voice.getVoiceId());
                     mVoicePreviewPlayback = playback;
                     playback.addListener(mVoicePreviewPlaybackListener);
                     mVoicePreviewPlayback.play();
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
index 6e9669e..377162a 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
@@ -1417,28 +1417,6 @@
     }
 
     @Test
-    public void testPreviewVoice_metric() {
-        final String histogramName = ReadAloudMetrics.VOICE_PREVIEWED;
-
-        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "abc", true);
-
-        // Play tab.
-        requestAndStartPlayback();
-
-        reset(mPlaybackHooks);
-        // Preview a voice.
-        var voice = new PlaybackVoice("en", "abc", "");
-        doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString());
-        doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any());
-        mController.previewVoice(voice);
-        verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture());
-        Playback previewPlayback = Mockito.mock(Playback.class);
-        onPlaybackSuccess(previewPlayback);
-
-        histogram.assertExpected();
-    }
-
-    @Test
     public void testRestorePlaybackState_whileLoading() {
         // Request playback but don't succeed yet.
         mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR);
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
index b840719..706ce0b 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
@@ -24,8 +24,6 @@
     public static String TAB_PLAYBACK_CREATION_FAILURE = "ReadAloud.TabPlaybackCreationFailure";
     public static String TAB_PLAYBACK_WITHOUT_READABILITY_CHECK_ERROR =
             "ReadAloud.ReadAloudPlaybackWithoutReadabilityCheckError";
-    public static String VOICE_CHANGED = "ReadAloud.VoiceChanged.";
-    public static String VOICE_PREVIEWED = "ReadAloud.VoicePreviewed.";
     public static String TIME_SPENT_LISTENING = "ReadAloud.DurationListened";
     public static String TIME_SPENT_LISTENING_LOCKED_SCREEN =
             "ReadAloud.DurationListened.LockedScreen";
@@ -186,14 +184,6 @@
         RecordUserAction.record("ReadAloud.PlaybackStarted");
     }
 
-    public static void recordVoiceChanged(String voiceID) {
-        RecordHistogram.recordBooleanHistogram(VOICE_CHANGED + voiceID, true);
-    }
-
-    public static void recordVoicePreviewed(String voiceID) {
-        RecordHistogram.recordBooleanHistogram(VOICE_PREVIEWED + voiceID, true);
-    }
-
     public static void recordHasDateModified(boolean hasDateModified) {
         RecordHistogram.recordBooleanHistogram(HAS_DATE_MODIFIED, hasDateModified);
     }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
index ded68fe..0605e81 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
@@ -51,7 +51,6 @@
         if (language == null || language.isEmpty() || voiceId == null || voiceId.isEmpty()) {
             return;
         }
-        ReadAloudMetrics.recordVoiceChanged(voiceId);
         ReadAloudPrefsJni.get().setVoice(prefs, language, voiceId);
     }
 
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
index 5ca0a6b..29d41f6 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
@@ -82,19 +82,6 @@
     }
 
     @Test
-    public void testSetVoice_metric() {
-        final String histogramName = ReadAloudMetrics.VOICE_CHANGED;
-
-        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "abc", true);
-        ReadAloudPrefs.setVoice(mPrefService, "en", "abc");
-        histogram.assertExpected();
-
-        histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "def", true);
-        ReadAloudPrefs.setVoice(mPrefService, "es", "def");
-        histogram.assertExpected();
-    }
-
-    @Test
     public void testDefaultSpeed() {
         assertEquals(1f, ReadAloudPrefs.getSpeed(mPrefService), /* delta= */ 0f);
     }
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 2fa78e59..60b8c2bf 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1773,11 +1773,9 @@
     }
 
 #if !BUILDFLAG(IS_ANDROID)
-    // TODO(b:315076421): Remove "New" badge for preview.
     if (base::FeatureList::IsEnabled(blink::features::kLinkPreview)) {
       menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKPREVIEW,
                                       IDS_CONTENT_CONTEXT_OPENLINKPREVIEW);
-      menu_model_.SetIsNewFeatureAt(menu_model_.GetItemCount() - 1, true);
       // We don't show in-production-help for ChromeOS for now because we should
       // use a different trigger.
       //
@@ -2036,10 +2034,6 @@
       menu_model_.GetIndexOfCommandId(search_for_image_idc).value();
   menu_model_.SetElementIdentifierAt(command_index, kSearchForImageItem);
 
-  if (companion::IsNewBadgeEnabledForSearchMenuItem(GetBrowser())) {
-    menu_model_.SetIsNewFeatureAt(menu_model_.GetItemCount() - 1, true);
-  }
-
   MaybePrepareForLensQuery();
 
   auto* service = TemplateURLServiceFactory::GetForProfile(GetProfile());
@@ -2112,7 +2106,6 @@
         GetSearchForVideoFrameIdc(),
         l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_SEARCHFORVIDEOFRAME,
                                    GetImageSearchProviderName(provider)));
-    menu_model_.SetIsNewFeatureAt(menu_model_.GetItemCount() - 1, true);
     MaybePrepareForLensQuery();
   }
 }
@@ -2368,9 +2361,6 @@
           l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_SEARCHWEBFOR,
                                      default_provider->short_name(),
                                      printable_selection_text));
-      if (companion::IsNewBadgeEnabledForSearchMenuItem(GetBrowser())) {
-        menu_model_.SetIsNewFeatureAt(menu_model_.GetItemCount() - 1, true);
-      }
       if (companion::IsSearchWebInCompanionSidePanelSupported(GetBrowser())) {
         // Add an "in new tab" item performing the non-side panel behavior.
         if (base::FeatureList::IsEnabled(
@@ -2437,8 +2427,8 @@
       // TODO(b/303646344): Remove new feature tag when no longer new.
       menu_model_.SetIsNewFeatureAt(
           menu_model_.GetItemCount() - 1,
-          GetBrowser()->window()->MaybeShowNewBadgeFor(
-              compose::features::kEnableCompose));
+          UserEducationService::MaybeShowNewBadge(
+              GetBrowserContext(), compose::features::kEnableCompose));
       render_separator = true;
     }
   }
@@ -2700,10 +2690,6 @@
     menu_model_.AddItem(region_search_idc,
                         l10n_util::GetStringFUTF16(
                             resource_id, GetImageSearchProviderName(provider)));
-    if (companion::IsNewBadgeEnabledForSearchMenuItem(GetBrowser())) {
-      menu_model_.SetIsNewFeatureAt(menu_model_.GetItemCount() - 1, true);
-    }
-
     menu_model_.SetElementIdentifierAt(
         menu_model_.GetIndexOfCommandId(region_search_idc).value(),
         kRegionSearchItem);
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
index e4a260d..874318d1 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
@@ -1980,110 +1980,6 @@
   ASSERT_EQ(initial_num_processes, mock_rph_factory().GetProcesses()->size());
 }
 
-// Verify that the new badge is added to region search context menu items if
-// appropriate feature is enabled.
-TEST_F(RenderViewContextMenuPrefsTest,
-       CompanionNewBadgeEnabledForRegionSearchContextMenuItem) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(
-      companion::features::kCompanionEnableNewBadgesInContextMenu);
-  SetUserSelectedDefaultSearchProvider("https://www.google.com",
-                                       /*supports_image_search=*/true);
-  content::ContextMenuParams params = CreateParams(MenuItem::PAGE);
-  TestRenderViewContextMenu menu(*web_contents()->GetPrimaryMainFrame(),
-                                 params);
-  menu.SetBrowser(GetBrowser());
-  menu.Init();
-
-  size_t index = 0;
-  ui::MenuModel* model = nullptr;
-
-  ASSERT_TRUE(menu.GetMenuModelAndItemIndex(
-      IDC_CONTENT_CONTEXT_LENS_REGION_SEARCH, &model, &index));
-  EXPECT_TRUE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_LENS_REGION_SEARCH));
-  EXPECT_TRUE(model->IsNewFeatureAt(index));
-}
-
-// Verify that the new badge is NOT added to region search context menu items if
-// appropriate feature is disabled.
-TEST_F(RenderViewContextMenuPrefsTest,
-       CompanionNewBadgeDisabledForRegionSearchContextMenuItem) {
-  base::test::ScopedFeatureList features;
-  features.InitAndDisableFeature(
-      companion::features::kCompanionEnableNewBadgesInContextMenu);
-  SetUserSelectedDefaultSearchProvider("https://www.google.com",
-                                       /*supports_image_search=*/true);
-  content::ContextMenuParams params = CreateParams(MenuItem::PAGE);
-  TestRenderViewContextMenu menu(*web_contents()->GetPrimaryMainFrame(),
-                                 params);
-  menu.SetBrowser(GetBrowser());
-  menu.Init();
-
-  size_t index = 0;
-  ui::MenuModel* model = nullptr;
-
-  ASSERT_TRUE(menu.GetMenuModelAndItemIndex(
-      IDC_CONTENT_CONTEXT_LENS_REGION_SEARCH, &model, &index));
-  EXPECT_TRUE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_LENS_REGION_SEARCH));
-  EXPECT_FALSE(model->IsNewFeatureAt(index));
-}
-
-// Verify that the new badge is added to image search context menu items if
-// appropriate feature is enabled.
-TEST_F(RenderViewContextMenuPrefsTest,
-       CompanionNewBadgeEnabledForImageSearchContextMenuItems) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(
-      companion::features::kCompanionEnableNewBadgesInContextMenu);
-  SetUserSelectedDefaultSearchProvider("https://www.google.com",
-                                       /*supports_image_search=*/true);
-  content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
-  params.has_image_contents = true;
-  TestRenderViewContextMenu menu(*web_contents()->GetPrimaryMainFrame(),
-                                 params);
-  menu.SetBrowser(GetBrowser());
-  menu.Init();
-
-  size_t index = 0;
-  ui::MenuModel* model = nullptr;
-
-  ASSERT_TRUE(menu.GetMenuModelAndItemIndex(
-      IDC_CONTENT_CONTEXT_SEARCHLENSFORIMAGE, &model, &index));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_SEARCHWEBFORIMAGE));
-  EXPECT_TRUE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_SEARCHLENSFORIMAGE));
-  EXPECT_TRUE(model->IsNewFeatureAt(index));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_TRANSLATEIMAGEWITHWEB));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_TRANSLATEIMAGEWITHLENS));
-}
-
-// Verify that the new badge is NOT added to image search context menu items if
-// appropriate feature is disabled.
-TEST_F(RenderViewContextMenuPrefsTest,
-       CompanionNewBadgeDisabledForImageSearchContextMenuItems) {
-  base::test::ScopedFeatureList features;
-  features.InitAndDisableFeature(
-      companion::features::kCompanionEnableNewBadgesInContextMenu);
-  SetUserSelectedDefaultSearchProvider("https://www.google.com",
-                                       /*supports_image_search=*/true);
-  content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
-  params.has_image_contents = true;
-  TestRenderViewContextMenu menu(*web_contents()->GetPrimaryMainFrame(),
-                                 params);
-  menu.SetBrowser(GetBrowser());
-  menu.Init();
-
-  size_t index = 0;
-  ui::MenuModel* model = nullptr;
-
-  ASSERT_TRUE(menu.GetMenuModelAndItemIndex(
-      IDC_CONTENT_CONTEXT_SEARCHLENSFORIMAGE, &model, &index));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_SEARCHWEBFORIMAGE));
-  EXPECT_TRUE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_SEARCHLENSFORIMAGE));
-  EXPECT_FALSE(model->IsNewFeatureAt(index));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_TRANSLATEIMAGEWITHWEB));
-  EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_TRANSLATEIMAGEWITHLENS));
-}
-
 // Verify that the Lens Region Search menu item is enabled for Progressive Web
 // Apps. Region Search on PWAs is currently broken and therefore disabled on
 // Mac. b/250074889
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.html b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.html
index 2cdfe58..705ec74 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.html
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.html
@@ -1,9 +1,9 @@
 <style include="cr-shared-style os-settings-icons settings-shared iron-flex">
   #networkListDiv {
-    min-height: var(--cr-section-min-height);
+    min-height: var(--cr-section-two-line-min-height);
   }
 
-  :host(:not([is-showing-vpn_])) #networkListDiv {
+  :host(:not([is-showing-vpn_], [is-showing-tether_])) #networkListDiv {
     margin-top: var(--cr-section-vertical-margin);
   }
 
@@ -44,7 +44,7 @@
   }
 
   .no-networks {
-    margin: 4px;
+    min-height: var(--cr-section-two-line-min-height);
   }
 
   network-list {
@@ -83,9 +83,8 @@
 </style>
 
 <template is="dom-if" if="[[shouldShowBluetoothDisabledTetherErrorMessage_(deviceState)]]">
-  <div id="networkListDiv" class="layout vertical flex">
+  <div id="networkListDiv" class="layout horizontal center">
     <localized-link
-      class="no-networks"
       localized-string="[[getBluetoothDisabledErrorMessageForTether_()]]">
     </localized-link>
   </div>
@@ -164,6 +163,7 @@
           <div id="your-device-hotspots-title">$i18n{internetYourDeviceHotspots}</div>
       </template>
       <network-list id="networkList" show-buttons
+          class="layout horizontal center"
           show-technology-badge="[[showTechnologyBadge_]]"
           networks="[[networkStateList_]]"
           global-policy="[[globalPolicy]]"
@@ -211,12 +211,13 @@
     </template>
 
     <!-- Text shown if no networks exist. -->
-    <localized-link
-        class="no-networks"
-        hidden="[[hideNoNetworksMessage_(networkStateList_, deviceState)]]"
-        localized-string=
-            "[[getNoNetworksInnerHtml_(deviceState, tetherDeviceState)]]">
-    </localized-link>
+    <template is="dom-if" if="[[!hideNoNetworksMessage_(networkStateList_, deviceState)]]">
+      <div class="layout horizontal center no-networks">
+        <localized-link
+          localized-string="[[getNoNetworksInnerHtml_(deviceState, tetherDeviceState)]]">
+        </localized-link>
+      </div>
+    </template>
 
     <template is="dom-if" if="[[isShowingVpn_]]">
       <!-- Third party VPNs. -->
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
index 90c4529..8c7fe70 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_subpage.ts
@@ -154,6 +154,12 @@
         reflectToAttribute: true,
       },
 
+      isShowingTether_: {
+        type: Boolean,
+        computed: 'computeIsShowingTether_(deviceState)',
+        reflectToAttribute: true,
+      },
+
       /**
        * Whether the browser/ChromeOS is managed by their organization
        * through enterprise policies.
@@ -258,6 +264,7 @@
   private hasCompletedScanSinceLastEnabled_: boolean;
   private isInstantHotspotRebrandEnabled_: boolean;
   private isManaged_: boolean;
+  private isShowingTether_: boolean;
   private isShowingVpn_: boolean;
   private networkConfig_: CrosNetworkConfigInterface;
   private networkStateList_: OncMojo.NetworkStateProperties[];
@@ -957,6 +964,13 @@
         OncMojo.getNetworkTypeString(NetworkType.kVPN), this.deviceState);
   }
 
+  private computeIsShowingTether_(): boolean {
+    return !!this.deviceState &&
+        this.matchesType_(
+            OncMojo.getNetworkTypeString(NetworkType.kTether),
+            this.deviceState);
+  }
+
   /**
    * Tells when VPN preferences section should be displayed. It is
    * displayed when the preferences are applicable to the current device.
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.html b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.html
index 70608aa..b42651c 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.html
@@ -20,7 +20,7 @@
     <div id="pinSetupDialogSubtitle">[[getSubtitle_(isConfirmStep_)]]</div>
     <div id="setupPinKeyboardDiv" class="settings-box continuation">
       <app-setup-pin-keyboard id="setupPinKeyboard"
-        is-confirm-step="{{isConfirmStep_}}">
+        is-confirm-step="{{isConfirmStep_}}" prefs="{{prefs}}">
       </app-setup-pin-keyboard>
     </div>
   </div>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.ts
index 5996ad7..00b19b33 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_dialog.ts
@@ -16,6 +16,8 @@
 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';
 
+import {Router, routes} from '../../router.js';
+
 import {getTemplate} from './app_setup_pin_dialog.html.js';
 import {AppSetupPinKeyboardElement} from './app_setup_pin_keyboard.js';
 
@@ -42,12 +44,21 @@
       /**
        * Whether the user is at the PIN confirmation step.
        */
-      isConfirmStep_: {type: Boolean, value: false},
+      isConfirmStep_: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
   private isConfirmStep_: boolean;
 
+  override ready(): void {
+    super.ready();
+
+    this.addEventListener('set-app-pin-done', this.onSetPinDone_);
+  }
+
   override connectedCallback(): void {
     super.connectedCallback();
 
@@ -66,23 +77,21 @@
   private onCancelClick_(e: Event): void {
     // Stop propagation to keep the subpage from opening.
     e.stopPropagation();
-    this.$.setupPinKeyboard.resetState();
     this.close();
   }
 
   private onPinSubmit_(e: Event): void {
-    // TODO(b/332936223): This method currently naively switches from the
-    // initial screen to the submit screen. It will be updated when the
-    // actual PIN setup flow is implemented.
-
     // Stop propagation to keep the subpage from opening.
     e.stopPropagation();
-    if (!this.isConfirmStep_) {
-      this.$.setupPinKeyboard.doSubmit();
-      this.isConfirmStep_ = true;
-    } else {
-      this.close();
-    }
+    this.$.setupPinKeyboard.doSubmit();
+  }
+
+  /**
+   * Called when the setup PIN keyboard successfully saves the PIN.
+   */
+  private onSetPinDone_(): void {
+    this.close();
+    Router.getInstance().navigateTo(routes.APP_PARENTAL_CONTROLS);
   }
 
   private getTitle_(isConfirmStep: boolean): string {
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.html b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.html
index 704c044d..ac13330 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.html
@@ -1,5 +1,5 @@
 <style include="settings-shared">
-  #problemDiv {
+  #pinMismatchErrorDiv {
     align-items: center;
     display: flex;
     flex-direction: row;
@@ -11,12 +11,15 @@
 
   /* Hide this using 'visibility: hidden' instead of 'hidden' so that the
       dialog does not resize when there are no problems to display. */
-  #problemDiv[invisible] {
+  #pinMismatchErrorDiv[invisible] {
     visibility: hidden;
   }
 </style>
-<pin-keyboard id="pinKeyboard" value="{{pinKeyboardValue_}}">
-  <div id="problemDiv">
-      <!--TODO(b/332936223): Add problem message here.-->
+<pin-keyboard id="pinKeyboard" on-submit="onPinSubmit_"
+    value="{{pinKeyboardValue_}}" disabled="[[isSetPinCallPending_]]">
+  <div id="pinMismatchErrorDiv" invisible$="[[!showPinMismatchError_]]">
+      <span aria-live="assertive">
+        $i18n{appParentalControlsPinMismatchErrorText}
+      </span>
   </div>
 </pin-keyboard>
\ No newline at end of file
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.ts
index 493fd33d..0f0b9b7 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_setup_pin_keyboard.ts
@@ -13,13 +13,14 @@
 import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
 import '../../settings_shared.css.js';
 
+import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {PinKeyboardElement} from 'chrome://resources/ash/common/quick_unlock/pin_keyboard.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './app_setup_pin_keyboard.html.js';
 
-const AppSetupPinKeyboardElementBase = I18nMixin(PolymerElement);
+const AppSetupPinKeyboardElementBase = (PrefsMixin(I18nMixin(PolymerElement)));
 
 export interface AppSetupPinKeyboardElement {
   $: {
@@ -62,12 +63,34 @@
         type: String,
         value: '',
       },
+
+      /**
+       * Whether the pin is currently being set as a pref.
+       * If true, the pin keyboard input should be disabled.
+       */
+      isSetPinCallPending_: {
+        notify: true,
+        type: Boolean,
+        value: false,
+      },
+
+      /**
+       * Whether the PIN mismatch error message should be shown after the user
+       * attempts to submit.
+       */
+      showPinMismatchError_: {
+        notify: true,
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
   isConfirmStep: boolean;
   private pinKeyboardValue_: string;
   private initialPin_: string;
+  private isSetPinCallPending_: boolean;
+  private showPinMismatchError_: boolean;
 
   override focus(): void {
     this.$.pinKeyboard.focusInput();
@@ -81,6 +104,22 @@
     this.initialPin_ = '';
     this.pinKeyboardValue_ = '';
     this.isConfirmStep = false;
+    this.showPinMismatchError_ = false;
+  }
+
+  /**
+   * Returns true if the user has re-entered the same PIN at the confirmation
+   * step.
+   */
+  private isPinConfirmed_(): boolean {
+    return this.isConfirmStep && (this.initialPin_ === this.pinKeyboardValue_);
+  }
+
+  /**
+   * Called when the user presses enter/return during PIN entry.
+   */
+  private onPinSubmit_(): void {
+    this.doSubmit();
   }
 
   /**
@@ -88,14 +127,28 @@
    * to submit the PIN.
    */
   doSubmit(): void {
-    // TODO(b/332936223): Implement actual PIN submission logic.
     if (!this.isConfirmStep) {
       this.initialPin_ = this.pinKeyboardValue_;
       this.pinKeyboardValue_ = '';
-      this.$.pinKeyboard.focusInput();
       this.isConfirmStep = true;
+      this.$.pinKeyboard.focusInput();
       return;
     }
+
+    if (!this.isPinConfirmed_()) {
+      this.showPinMismatchError_ = true;
+      // Focus the PIN keyboard and highlight the entire PIN so the user can
+      // replace it.
+      this.$.pinKeyboard.focusInput(0, this.pinKeyboardValue_.length + 1);
+      return;
+    }
+
+    this.isSetPinCallPending_ = true;
+    this.setPrefValue('on_device_app_controls.pin', this.pinKeyboardValue_);
+    this.setPrefValue('on_device_app_controls.setup_completed', true);
+    this.isSetPinCallPending_ = false;
+
+    this.dispatchEvent(new Event('set-app-pin-done', {composed: true}));
   }
 }
 
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 c04a749c..1cb4356 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
@@ -38,7 +38,7 @@
                 "[[prefs.on_device_app_controls.setup_completed.value]]"
             on-click="onClickParentalControls_">
           <iron-icon id="parentalControlsRowIcon"
-              icon="[[rowIcons_.manageApps]]">
+              icon="[[rowIcons_.parentalControls]]">
           </iron-icon>
           <div class="start settings-box-text">
             $i18n{appParentalControlsTitle}
@@ -70,19 +70,19 @@
           <template is="dom-if" if="[[showParentalControlsSetupPinDialog_]]"
               restamp>
             <app-setup-pin-dialog id="setupPin"
-                on-close="onSetupPinDialogClose_">
+                on-close="onSetupPinDialogClose_" prefs="{{prefs}}">
             </app-setup-pin-dialog>
           </template>
           <template is="dom-if" if="[[showParentalControlsVerifyPinDialog_]]"
               restamp>
             <app-verify-pin-dialog id="verifyPin"
-              on-close="onVerifyPinDialogClose_">
+                on-close="onVerifyPinDialogClose_">
             </app-verify-pin-dialog>
           </template>
           <template is="dom-if" if="[[showParentalControlsDisablePinDialog_]]"
               restamp>
             <app-verify-pin-dialog id="disableDialog"
-              on-close="onDisablePinDialogClose_">
+                on-close="onDisablePinDialogClose_">
             </app-verify-pin-dialog>
           </template>
         </div>
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 037b67f..b7b5e121 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
@@ -245,6 +245,7 @@
               androidSettings: 'os-settings:apps-android-settings',
               manageIsolatedWebApps:
                   'os-settings:apps-manage-isolated-web-apps',
+              parentalControls: 'os-settings:apps-parental-controls',
             };
           }
 
@@ -254,6 +255,7 @@
             googlePlayPreferences: '',
             androidSettings: '',
             manageIsolatedWebApps: '',
+            parentalControls: '',
           };
         },
       },
@@ -392,10 +394,6 @@
 
   private onSetupPinDialogClose_(): void {
     this.showParentalControlsSetupPinDialog_ = false;
-    // TODO(b/332936223): Only set setup pref to true and navigate to the
-    // subpage on PIN submission success.
-    this.setPrefValue('on_device_app_controls.setup_completed', true);
-    Router.getInstance().navigateTo(routes.APP_PARENTAL_CONTROLS);
   }
 
   private onClickManageIsolatedWebApps_(): void {
diff --git a/chrome/browser/resources/ash/settings/os_languages_page/input_page.html b/chrome/browser/resources/ash/settings/os_languages_page/input_page.html
index 62aaf75..cfd0753 100644
--- a/chrome/browser/resources/ash/settings/os_languages_page/input_page.html
+++ b/chrome/browser/resources/ash/settings/os_languages_page/input_page.html
@@ -145,7 +145,7 @@
 </settings-toggle-button>
 
 <div class="hr bottom-margin">
-  <template is="dom-if" if="[[!shouldShowShortcutReminder_(
+  <template is="dom-if" if="[[shouldShowShortcutReminder_(
       shortcutReminderBody_.length)]]">
     <keyboard-shortcut-banner header="$i18n{imeShortcutReminderTitle}"
         body="[[shortcutReminderBody_]]"
diff --git a/chrome/browser/resources/ash/settings/os_settings_icons.html b/chrome/browser/resources/ash/settings/os_settings_icons.html
index 8c02dd1e..9be3bd74 100644
--- a/chrome/browser/resources/ash/settings/os_settings_icons.html
+++ b/chrome/browser/resources/ash/settings/os_settings_icons.html
@@ -48,6 +48,9 @@
       <g id="apps-manage-isolated-web-apps" viewBox="0 0 20 20">
         <path fill-rule="evenodd" d="M3.646 16.396a2.02 2.02 0 0 1-1.458-.584 2.023 2.023 0 0 1-.584-1.458V5.625c0-.57.195-1.049.583-1.437a2.023 2.023 0 0 1 1.459-.584h12.708c.57 0 1.049.202 1.438.604.402.39.604.868.604 1.438v8.729a1.92 1.92 0 0 1-.604 1.438 1.953 1.953 0 0 1-1.438.583H3.646Zm0-2.042h8.208v-2.5H3.646v2.5Zm9.708 0h3v-6.5h-3v6.5Zm-9.708-4h8.208v-2.5H3.646v2.5Z"></path>
       </g>
+      <g id="apps-parental-controls" viewBox="0 -960 960 960">
+        <path fill-rule="evenodd" d="M480-48q-182.62 0-307.31-67T48-281.81Q48-350 112-406t176-86v75q-77 23-122.5 59.83T120-282.48Q120-217 227.5-168.5T480-120q145 0 252.5-48.73T840-282.44Q840-320 794.5-357T672-417v-75q111 29 175.5 85.69 64.5 56.7 64.5 124.73Q912-182 787.31-115 662.62-48 480-48ZM360-192v-408H192v-72h576v72H600v408h-72v-192h-96v192h-72Zm119.75-540q-34.75 0-59.25-24.75t-24.5-59.5q0-34.75 24.75-59.25t59.5-24.5q34.75 0 59.25 24.75t24.5 59.5q0 34.75-24.75 59.25t-59.5 24.5Z"></path>
+      </g>
 
       <!-- About page section -->
       <g id="counter-1">
diff --git a/chrome/browser/resources/commerce/product_specifications/app.html b/chrome/browser/resources/commerce/product_specifications/app.html
index e548a45..d5602c9 100644
--- a/chrome/browser/resources/commerce/product_specifications/app.html
+++ b/chrome/browser/resources/commerce/product_specifications/app.html
@@ -72,7 +72,7 @@
 </style>
 <product-specifications-header id="header" subtitle="[[setName_]]"
     on-add-to-new-group-click="addToNewGroup_"
-    on-delete-click="removeSet_"
+    on-delete-click="deleteSet_"
     on-name-change="updateSetName_"
     on-see-all-click="seeAllSets_">
 </product-specifications-header>
@@ -90,7 +90,8 @@
         id="summaryTable"
         columns="[[specsTable_.columns]]"
         rows="[[specsTable_.rows]]"
-        on-url-change="onUrlChange_">
+        on-url-change="onUrlChange_"
+        on-url-remove="onUrlRemove_">
     </product-specifications-table>
     <new-column-selector on-selected-url-change="onUrlAdd_">
     </new-column-selector>
diff --git a/chrome/browser/resources/commerce/product_specifications/app.ts b/chrome/browser/resources/commerce/product_specifications/app.ts
index 8c2faca3..b82ed3b 100644
--- a/chrome/browser/resources/commerce/product_specifications/app.ts
+++ b/chrome/browser/resources/commerce/product_specifications/app.ts
@@ -85,6 +85,7 @@
   private shoppingApi_: BrowserProxy = BrowserProxyImpl.getInstance();
   private callbackRouter_: PageCallbackRouter;
   private listenerIds_: number[] = [];
+  private id_: Uuid|null = null;
 
   constructor() {
     super();
@@ -105,6 +106,7 @@
     const params = new URLSearchParams(router.getCurrentQuery());
     const idParam = params.get('id');
     if (idParam) {
+      this.id_ = {value: idParam};
       const {set} = await this.shoppingApi_.getProductSpecificationsSetByUuid(
           {value: idParam});
       if (set) {
@@ -126,12 +128,12 @@
       return;
     }
 
-    // TODO(b/338427523): Add UI for choosing the name.
     const setName = 'Product specs';
     this.setName_ = setName;
     const {createdSet} = await this.shoppingApi_.addProductSpecificationsSet(
         setName, urls.map(url => ({url})));
     if (createdSet) {
+      this.id_ = createdSet.uuid;
       window.history.replaceState(
           undefined, '', '?id=' + createdSet.uuid.value);
     }
@@ -140,31 +142,32 @@
   }
 
   private async populateTable_(urls: string[]) {
-    const {productSpecs} =
-        await this.shoppingApi_.getProductSpecificationsForUrls(
-            urls.map(url => ({url})));
-
+    let aggregatedDatas: Record<string, AggregatedProductData> = {};
     const rows: TableRow[] = [];
-    productSpecs.productDimensionMap.forEach((value: string, key: bigint) => {
-      rows.push({
-        title: value,
-        values: productSpecs.products.map(
-            (p: ProductSpecificationsProduct) =>
-                p.productDimensionValues.get(key)!.join(',')),
+    if (urls.length) {
+      const {productSpecs} =
+          await this.shoppingApi_.getProductSpecificationsForUrls(
+              urls.map(url => ({url})));
+      productSpecs.productDimensionMap.forEach((value: string, key: bigint) => {
+        rows.push({
+          title: value,
+          values: productSpecs.products.map(
+              (p: ProductSpecificationsProduct) =>
+                  p.productDimensionValues.get(key)!.join(',')),
+        });
       });
-    });
+      const infos = await this.getInfoForUrls_(urls);
+      aggregatedDatas =
+          aggregateProductDataByClusterId(infos, productSpecs.products);
+    }
 
-    const infos = await this.getInfoForUrls_(urls);
-    const aggregatedDatas =
-        aggregateProductDataByClusterId(infos, productSpecs.products);
     this.specsTable_ = {
       columns:
           Object.values(aggregatedDatas).map((data: AggregatedProductData) => {
             return {
               selectedItem: {
                 title: data.spec ? data.spec.title : '',
-                // TODO(b/335637140): Replace with actual URL once available.
-                url: 'https://example.com',
+                url: data.info.productUrl.url,
                 imageUrl: data.info.imageUrl.url,
               },
             };
@@ -195,7 +198,9 @@
   }
 
   private deleteSet_() {
-    // TODO(b/330345730): Plumb through mojom
+    if (this.id_) {
+      this.shoppingApi_.deleteProductSpecificationsSet(this.id_);
+    }
   }
 
   private updateSetName_(_: CustomEvent<{name: string}>) {
@@ -209,30 +214,49 @@
   private onUrlAdd_(e: CustomEvent<{url: string}>) {
     const urls = this.getTableUrls_();
     urls.push(e.detail.url);
+    // TODO(b/330345730): Plumb through mojom instead of calling populateTable
+    // directly. Then, onSetUpdated should handle the table change.
     this.populateTable_(urls);
   }
 
   private onUrlChange_(e: CustomEvent<{url: string, index: number}>) {
     const urls = this.getTableUrls_();
     urls[e.detail.index] = e.detail.url;
+    // TODO(b/330345730): Plumb through mojom instead of calling populateTable
+    // directly. Then, onSetUpdated should handle the table change.
+    this.populateTable_(urls);
+  }
+
+  private onUrlRemove_(e: CustomEvent<{index: number}>) {
+    const urls = this.getTableUrls_();
+    urls.splice(e.detail.index, 1);
+    // TODO(b/330345730): Plumb through mojom instead of calling populateTable
+    // directly. Then, onSetUpdated should handle the table change.
     this.populateTable_(urls);
   }
 
   private getTableUrls_(): string[] {
-    // Until b/335637140 is resolved, these will all be the same placeholder
-    // URL and the table will not update as expected.
     return this.specsTable_.columns.map(
         (column: TableColumn) => column.selectedItem.url);
   }
 
-  private onSetUpdated_(_: ProductSpecificationsSet) {
-    // TODO(b:333378234): If the update is for the currently shown set, apply
-    //                    the updates.
+  private onSetUpdated_(set: ProductSpecificationsSet) {
+    if (set.uuid.value !== this.id_?.value) {
+      return;
+    }
+    this.setName_ = set.name;
+    this.populateTable_(set.urls.map(url => url.url));
   }
 
-  private onSetRemoved_(_: Uuid) {
-    // TODO(b:333378234): If the UUID is for the shown set, clear the UI or
-    //                    refresh to load the zero-state.
+  private onSetRemoved_(id: Uuid) {
+    if (id.value !== this.id_?.value) {
+      return;
+    }
+    this.id_ = null;
+    this.specsTable_ = {
+      columns: [],
+      rows: [],
+    };
   }
 }
 
diff --git a/chrome/browser/resources/commerce/product_specifications/images/icons.html b/chrome/browser/resources/commerce/product_specifications/images/icons.html
index fc65ca2..a293c77 100644
--- a/chrome/browser/resources/commerce/product_specifications/images/icons.html
+++ b/chrome/browser/resources/commerce/product_specifications/images/icons.html
@@ -13,6 +13,9 @@
       <g id="delete">
         <path d="m11.9 13.5 2.1-2.1 2.1 2.1 1.4-1.4-2.1-2.1 2.1-2.1-1.4-1.4L14 8.6l-2.1-2.1-1.4 1.4 2.1 2.1-2.1 2.1zM8 18a1.99 1.99 0 0 1-1.425-.575A1.99 1.99 0 0 1 6 16V4c0-.55.192-1.017.575-1.4.4-.4.875-.6 1.425-.6h12c.55 0 1.017.2 1.4.6.4.383.6.85.6 1.4v12c0 .55-.2 1.025-.6 1.425-.383.383-.85.575-1.4.575zm0-2h12V4H8zm-4 6a1.99 1.99 0 0 1-1.425-.575A1.99 1.99 0 0 1 2 20V6h2v14h14v2zM8 4v12z"></path>
       </g>
+      <g id="remove">
+        <path d="M7 13h10v-2H7zm5 9a9.873 9.873 0 0 1-3.9-.775 10.274 10.274 0 0 1-3.175-2.15c-.9-.9-1.617-1.958-2.15-3.175A9.873 9.873 0 0 1 2 12c0-1.383.258-2.683.775-3.9a10.275 10.275 0 0 1 2.15-3.175c.9-.9 1.958-1.608 3.175-2.125A9.607 9.607 0 0 1 12 2c1.383 0 2.683.267 3.9.8a9.927 9.927 0 0 1 3.175 2.125c.9.9 1.608 1.958 2.125 3.175.533 1.217.8 2.517.8 3.9a9.607 9.607 0 0 1-.8 3.9 9.927 9.927 0 0 1-2.125 3.175c-.9.9-1.958 1.617-3.175 2.15A9.873 9.873 0 0 1 12 22m0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 2.233.775 4.125 2.325 5.675C7.875 19.225 9.767 20 12 20"></path>
+      </g>
     </defs>
   </svg>
 </iron-iconset-svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/commerce/product_specifications/product_selection_menu.html b/chrome/browser/resources/commerce/product_specifications/product_selection_menu.html
index 9cad807..82f6388 100644
--- a/chrome/browser/resources/commerce/product_specifications/product_selection_menu.html
+++ b/chrome/browser/resources/commerce/product_specifications/product_selection_menu.html
@@ -14,6 +14,13 @@
     padding: 6px 16px;
   }
 
+  hr {
+    background: var(--color-product-specifications-divider);
+    border: none;
+    height: 1px;
+    margin: 0;
+  }
+
   .dropdown-item {
     padding: 0 16px;
   }
@@ -44,6 +51,17 @@
     padding: 12px;
     width: var(--product-selector-width);
   }
+
+  #remove {
+    --icon-size: 16px;
+    --iron-icon-height: var(--icon-size);
+    --iron-icon-width: var(--icon-size);
+    color: var(--cr-secondary-text-color);
+    font-weight: 400;
+    font-size: 12px;
+    gap: 8px;
+    padding: 16px;
+  }
 </style>
 
 <cr-lazy-render id="menu">
@@ -71,6 +89,14 @@
         </template>
         <div id="empty" hidden="[[sections]]">$i18n{emptyMenu}</div>
       </div>
+      <template is="dom-if" if="[[selectedUrl.length]]">
+        <hr>
+        <button id="remove" class="dropdown-item" role="menuitem"
+            on-click="onRemoveClick_">
+          <cr-icon icon="product-specifications:remove"></cr-icon>
+          $i18n{removeColumn}
+        </button>
+      </template>
     </cr-action-menu>
   </template>
 </cr-lazy-render>
diff --git a/chrome/browser/resources/commerce/product_specifications/product_selection_menu.ts b/chrome/browser/resources/commerce/product_specifications/product_selection_menu.ts
index 4a43202..b89a9a11 100644
--- a/chrome/browser/resources/commerce/product_specifications/product_selection_menu.ts
+++ b/chrome/browser/resources/commerce/product_specifications/product_selection_menu.ts
@@ -123,6 +123,14 @@
     }));
   }
 
+  private onRemoveClick_() {
+    this.close();
+    this.dispatchEvent(new CustomEvent('remove-url', {
+      bubbles: true,
+      composed: true,
+    }));
+  }
+
   private onClose_() {
     this.dispatchEvent(new CustomEvent('close-menu', {
       bubbles: true,
diff --git a/chrome/browser/resources/commerce/product_specifications/product_selector.html b/chrome/browser/resources/commerce/product_specifications/product_selector.html
index 92c17564..e04c795 100644
--- a/chrome/browser/resources/commerce/product_specifications/product_selector.html
+++ b/chrome/browser/resources/commerce/product_specifications/product_selector.html
@@ -77,6 +77,6 @@
 </div>
 
 <product-selection-menu id="productSelectionMenu"
-    selected-url="[[selectedItem.url]]"
+    selected-url="[[getSelectedUrl_(selectedItem)]]"
     on-close-menu="onCloseMenu_">
 </product-selection-menu>
diff --git a/chrome/browser/resources/commerce/product_specifications/product_selector.ts b/chrome/browser/resources/commerce/product_specifications/product_selector.ts
index 5ae26d2..57aeb11 100644
--- a/chrome/browser/resources/commerce/product_specifications/product_selector.ts
+++ b/chrome/browser/resources/commerce/product_specifications/product_selector.ts
@@ -63,6 +63,10 @@
   private getUrl_(item: UrlListEntry) {
     return getAbbreviatedUrl(item.url);
   }
+
+  private getSelectedUrl_() {
+    return this.selectedItem?.url ?? '';
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/commerce/product_specifications/table.html b/chrome/browser/resources/commerce/product_specifications/table.html
index 84fd9d0..e277ff0 100644
--- a/chrome/browser/resources/commerce/product_specifications/table.html
+++ b/chrome/browser/resources/commerce/product_specifications/table.html
@@ -50,7 +50,8 @@
       <th class="col">
         <product-selector
             selected-item="[[column.selectedItem]]"
-            on-selected-url-change="onSelectedUrlChange_">
+            on-selected-url-change="onSelectedUrlChange_"
+            on-remove-url="onUrlRemove_">
         </product-selector>
         <div id="imgContainer">
           <img is="cr-auto-img"
diff --git a/chrome/browser/resources/commerce/product_specifications/table.ts b/chrome/browser/resources/commerce/product_specifications/table.ts
index c4e919c..0e739372 100644
--- a/chrome/browser/resources/commerce/product_specifications/table.ts
+++ b/chrome/browser/resources/commerce/product_specifications/table.ts
@@ -63,6 +63,16 @@
       },
     }));
   }
+
+  private onUrlRemove_(e: DomRepeatEvent<TableColumn>) {
+    this.dispatchEvent(new CustomEvent('url-remove', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        index: e.model.index,
+      },
+    }));
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/lens/overlay/post_selection_renderer.html b/chrome/browser/resources/lens/overlay/post_selection_renderer.html
index d69fa8e..26b1860 100644
--- a/chrome/browser/resources/lens/overlay/post_selection_renderer.html
+++ b/chrome/browser/resources/lens/overlay/post_selection_renderer.html
@@ -32,9 +32,11 @@
   }
 
   #postSelection {
+    background-color: #151C26;
     height: var(--selection-height);
     left: var(--selection-left);
     margin: auto;
+    opacity: 5%;
     position: absolute;
     top: var(--selection-top);
     width: var(--selection-width);
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.html b/chrome/browser/resources/lens/overlay/selection_overlay.html
index f7b5688..bf1d567 100644
--- a/chrome/browser/resources/lens/overlay/selection_overlay.html
+++ b/chrome/browser/resources/lens/overlay/selection_overlay.html
@@ -65,6 +65,12 @@
     opacity: 0;
   }
 
+  #defaultScrim {
+    background-color: #151C26;
+    opacity: 5%;
+    pointer-events: none;
+  }
+
   #selectionElements > * {
     inset: 0;
     position: absolute;
@@ -165,6 +171,7 @@
   background image. -->
   <div id="selectionElements">
     <!-- Other elements that need to be bounded to the image go here -->
+    <div id="defaultScrim"></div>
     <overlay-shimmer id="overlayShimmer"></overlay-shimmer>
     <post-selection-renderer id="postSelectionRenderer"
         screenshot-data-uri="[[screenshotDataUri]]">
diff --git a/chrome/browser/resources/settings/performance_page/performance_browser_proxy.ts b/chrome/browser/resources/settings/performance_page/performance_browser_proxy.ts
index a74b3024..f291ec5d 100644
--- a/chrome/browser/resources/settings/performance_page/performance_browser_proxy.ts
+++ b/chrome/browser/resources/settings/performance_page/performance_browser_proxy.ts
@@ -10,7 +10,6 @@
   openBatterySaverFeedbackDialog(): void;
   openMemorySaverFeedbackDialog(): void;
   openSpeedFeedbackDialog(): void;
-  onDiscardRingTreatmentEnabledChanged(): void;
   validateTabDiscardExceptionRule(rule: string): Promise<boolean>;
 }
 
@@ -35,10 +34,6 @@
     chrome.send('openSpeedFeedbackDialog');
   }
 
-  onDiscardRingTreatmentEnabledChanged() {
-    chrome.send('onDiscardRingTreatmentEnabledChanged');
-  }
-
   validateTabDiscardExceptionRule(rule: string) {
     return sendWithPromise('validateTabDiscardExceptionRule', rule);
   }
diff --git a/chrome/browser/resources/settings/performance_page/performance_page.ts b/chrome/browser/resources/settings/performance_page/performance_page.ts
index ea95792..eb94a98f 100644
--- a/chrome/browser/resources/settings/performance_page/performance_page.ts
+++ b/chrome/browser/resources/settings/performance_page/performance_page.ts
@@ -18,8 +18,6 @@
 import type {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js';
 import {loadTimeData} from '../i18n_setup.js';
 
-import type {PerformanceBrowserProxy} from './performance_browser_proxy.js';
-import {PerformanceBrowserProxyImpl} from './performance_browser_proxy.js';
 import type {PerformanceMetricsProxy} from './performance_metrics_proxy.js';
 import {MemorySaverModeAggressiveness, MemorySaverModeState, PerformanceMetricsProxyImpl} from './performance_metrics_proxy.js';
 import {getTemplate} from './performance_page.html.js';
@@ -96,8 +94,6 @@
 
   private numericUncheckedValues_: MemorySaverModeState[];
   private numericCheckedValue_: MemorySaverModeState[];
-  private browserProxy_: PerformanceBrowserProxy =
-      PerformanceBrowserProxyImpl.getInstance();
   private metricsProxy_: PerformanceMetricsProxy =
       PerformanceMetricsProxyImpl.getInstance();
 
@@ -131,7 +127,6 @@
   }
 
   private onDiscardRingChange_() {
-    this.browserProxy_.onDiscardRingTreatmentEnabledChanged();
     this.metricsProxy_.recordDiscardRingTreatmentEnabledChanged(
         this.getPref<boolean>(DISCARD_RING_PREF).value);
   }
diff --git a/chrome/browser/resources/webui_gallery/BUILD.gn b/chrome/browser/resources/webui_gallery/BUILD.gn
index 3be5dc0..b670e20 100644
--- a/chrome/browser/resources/webui_gallery/BUILD.gn
+++ b/chrome/browser/resources/webui_gallery/BUILD.gn
@@ -19,6 +19,9 @@
     "demos/buttons/buttons_demo.css",
     "demos/card/card_demo.css",
     "demos/cr_action_menu/cr_action_menu_demo.css",
+    "demos/cr_dialog/cr_dialog_demo.css",
+    "demos/cr_icons/cr_icons_demo.css",
+    "demos/cr_input/cr_input_demo.css",
     "demos/demo.css",
     "demos/demo_lit.css",
   ]
diff --git a/chrome/browser/resources/webui_gallery/app.html b/chrome/browser/resources/webui_gallery/app.html
index c509a65..ee7b2f8 100644
--- a/chrome/browser/resources/webui_gallery/app.html
+++ b/chrome/browser/resources/webui_gallery/app.html
@@ -52,7 +52,9 @@
       on-iron-select="onMenuItemSelect_">
     <template is="dom-repeat" items="[[demos_]]">
       <a role="menuitem" href="[[item.path]]"
-          class="cr-nav-menu-item">[[item.name]]</a>
+          class="cr-nav-menu-item" on-click="onMenuItemClick_">
+        [[item.name]]
+      </a>
     </template>
   </cr-menu-selector>
 
diff --git a/chrome/browser/resources/webui_gallery/app.ts b/chrome/browser/resources/webui_gallery/app.ts
index 50986544..894463f 100644
--- a/chrome/browser/resources/webui_gallery/app.ts
+++ b/chrome/browser/resources/webui_gallery/app.ts
@@ -185,6 +185,13 @@
   private onMenuItemSelect_(e: CustomEvent<{item: HTMLAnchorElement}>): void {
     const newUrl = new URL(e.detail.item.href);
     CrRouter.getInstance().setPath(newUrl.pathname);
+    this.onPathChanged_(newUrl.pathname);
+  }
+
+  // Prevent clicks on sidebar items from navigating and therefore reloading
+  // the page. onMenuItemSelect_() handles loading new demos when selected.
+  private onMenuItemClick_(e: MouseEvent) {
+    e.preventDefault();
   }
 
   private async onPathChanged_(newPath: string) {
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.html
index e4a39c3..4520617 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.html
@@ -1,6 +1,3 @@
-<style include="demo">
-</style>
-
 <h1>cr-chip</h1>
 <div class="demos">
   <cr-chip>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.ts b/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.ts
index 76f70a5..8cb88da 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_chip/cr_chip_demo.ts
@@ -5,19 +5,24 @@
 import '//resources/cr_elements/cr_chip/cr_chip.js';
 import '//resources/cr_elements/cr_icon/cr_icon.js';
 import '//resources/cr_elements/icons_lit.html.js';
-import '../demo.css.js';
 
-import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './cr_chip_demo.html.js';
+import {getCss} from '../demo_lit.css.js';
 
-class CrChipDemoElement extends PolymerElement {
+import {getHtml} from './cr_chip_demo.html.js';
+
+export class CrChipDemoElement extends CrLitElement {
   static get is() {
     return 'cr-chip-demo';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
+  }
+
+  override render() {
+    return getHtml.bind(this)();
   }
 }
 
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.css b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.css
new file mode 100644
index 0000000..eceb21f
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.css
@@ -0,0 +1,28 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=../demo_lit.css.js
+ * #scheme=relative
+ * #include=demo-lit
+ * #css_wrapper_metadata_end */
+
+cr-input:first-of-type {
+  margin-block-start: 16px;
+}
+
+[slot='header'] {
+  padding: 0 20px 16px;
+}
+
+cr-dialog::part(body-container) {
+  max-height: 300px;
+}
+
+#tallBlock {
+  background: var(--google-grey-200);
+  height: 800px;
+  margin: 16px 0;
+}
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.html
index 439da758..bd81b35 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.html
@@ -1,76 +1,64 @@
-<style include="demo">
-  cr-input:first-of-type {
-    margin-block-start: 16px;
-  }
-
-  [slot='header'] {
-    padding: 0 20px 16px;
-  }
-
-  cr-dialog::part(body-container) {
-    max-height: 300px;
-  }
-
-  #tallBlock {
-    background: var(--google-grey-200);
-    height: 800px;
-    margin: 16px 0;
-  }
-</style>
-
 <h1>cr-dialog</h1>
 <div class="demos">
-  <cr-checkbox checked="{{showHeader_}}">Show header</cr-checkbox>
-  <cr-checkbox checked="{{showFooter_}}">Show footer</cr-checkbox>
-  <cr-checkbox checked="{{showScrollingBody_}}">
+  <cr-checkbox ?checked="${this.showHeader_}"
+      @checked-changed="${this.onShowHeaderChanged_}">Show header</cr-checkbox>
+  <cr-checkbox ?checked="${this.showFooter_}"
+      @checked-changed="${this.onShowFooterChanged_}">Show footer</cr-checkbox>
+  <cr-checkbox ?checked="${this.showScrollingBody_}"
+      @checked-changed="${this.onShowScrollingBodyChanged_}">
     Show tall scrolling body
   </cr-checkbox>
-  <cr-checkbox checked="{{showInputs_}}">Show inputs</cr-checkbox>
-  <cr-checkbox checked="{{autofocusInput_}}" disabled$="[[!showInputs_]]">
+  <cr-checkbox ?checked="${this.showInputs_}"
+      @checked-changed="${this.onShowInputsChanged_}">
+    Show inputs
+  </cr-checkbox>
+  <cr-checkbox ?checked="${this.autofocusInput_}"
+      @checked-changed="${this.onAutofocusInputChanged_}"
+      ?disabled="${!this.showInputs_}">
     Autofocus input when dialog opens
   </cr-checkbox>
 
-  <cr-button on-click="openDialog_">Open dialog</cr-button>
+  <cr-button @click="${this.openDialog_}">Open dialog</cr-button>
   <div>
-    <template is="dom-repeat" items="[[statusTexts_]]">
-      <div>[[item]]</div>
-    </template>
+    ${this.statusTexts_.map(item => html`
+      <div>${item}</div>
+    `)}
   </div>
 </div>
 
-<template is="dom-if" if="[[isDialogOpen_]]" restamp>
+${this.isDialogOpen_ ? html`
   <cr-dialog
       id="dialog"
-      on-cr-dialog-open="onOpenDialog_"
-      on-cancel="onCancelDialog_"
-      on-close="onCloseDialog_"
+      @cr-dialog-open="${this.onOpenDialog_}"
+      @cancel="${this.onCancelDialog_}"
+      @close="${this.onCloseDialog_}"
       show-on-attach>
     <div slot="title">Dialog title</div>
-    <div slot="header" hidden$="[[!showHeader_]]">
+    <div slot="header" ?hidden="${!this.showHeader_}">
       Dialogs can also include a header between the title and the body. It is
       commonly used to display status updates or tabs.
     </div>
     <div slot="body">
       <div>Here is where some description text would go.</div>
-      <div hidden$="[[!showInputs_]]">
-        <cr-input label="Example input" autofocus$="[[autofocusInput_]]">
+      <div ?hidden="${!this.showInputs_}">
+        <cr-input label="Example input" ?autofocus="${this.autofocusInput_}">
         </cr-input>
         <cr-input label="Example input"></cr-input>
       </div>
-      <div hidden$="[[!showScrollingBody_]]">
+      <div ?hidden="${!this.showScrollingBody_}">
         <div id="tallBlock"></div>
       </div>
     </div>
     <div slot="button-container">
-      <cr-button class="cancel-button" on-click="onClickCancel_">
+      <cr-button class="cancel-button" @click="${this.onClickCancel_}">
         Cancel
       </cr-button>
-      <cr-button class="action-button" on-click="onClickConfirm_">
+      <cr-button class="action-button" @click="${this.onClickConfirm_}">
         Confirm
       </cr-button>
     </div>
-    <div slot="footer" hidden$="[[!showFooter_]]">
+    <div slot="footer" ?hidden="${!this.showFooter_}">
       Dialogs also have a slot for text or other elements in the footer.
     </div>
   </cr-dialog>
-</template>
+` : ''}
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.ts b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.ts
index 333eb711..a9668c0 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_dialog/cr_dialog_demo.ts
@@ -10,80 +10,105 @@
 import '../demo.css.js';
 
 import type {CrDialogElement} from '//resources/cr_elements/cr_dialog/cr_dialog.js';
-import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './cr_dialog_demo.html.js';
+import {getCss} from './cr_dialog_demo.css.js';
+import {getHtml} from './cr_dialog_demo.html.js';
 
-interface CrDialogDemoElement {
+export interface CrDialogDemoElement {
   $: {
     dialog: CrDialogElement,
   };
 }
 
-class CrDialogDemoElement extends PolymerElement {
+export class CrDialogDemoElement extends CrLitElement {
   static get is() {
     return 'cr-dialog-demo';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
     return {
-      autofocusInputs_: Boolean,
-      isDialogOpen_: Boolean,
-      showHeader_: Boolean,
-      showFooter_: Boolean,
-      showInputs_: Boolean,
-      showScrollingBody_: Boolean,
-      statusTexts_: Array,
+      autofocusInput_: {type: Boolean},
+      isDialogOpen_: {type: Boolean},
+      showHeader_: {type: Boolean},
+      showFooter_: {type: Boolean},
+      showInputs_: {type: Boolean},
+      showScrollingBody_: {type: Boolean},
+      statusTexts_: {type: Array},
     };
   }
 
-  private autofocusInputs_: boolean = false;
-  private isDialogOpen_: boolean = false;
-  private showHeader_: boolean = false;
-  private showFooter_: boolean = false;
-  private showInputs_: boolean = false;
-  private showScrollingBody_: boolean = false;
-  private statusTexts_: string[] = [];
+  protected autofocusInput_: boolean = false;
+  protected isDialogOpen_: boolean = false;
+  protected showHeader_: boolean = false;
+  protected showFooter_: boolean = false;
+  protected showInputs_: boolean = false;
+  protected showScrollingBody_: boolean = false;
+  protected statusTexts_: string[] = [];
 
   private getDialog_(): CrDialogElement|null {
     return this.shadowRoot!.querySelector('cr-dialog');
   }
 
-  private openDialog_() {
+  protected openDialog_() {
     this.isDialogOpen_ = true;
   }
 
-  private onClickCancel_() {
+  protected onClickCancel_() {
     const dialog = this.getDialog_();
     if (dialog) {
       dialog.cancel();
     }
   }
 
-  private onClickConfirm_() {
+  protected onClickConfirm_() {
     const dialog = this.getDialog_();
     if (dialog) {
       dialog.close();
     }
   }
 
-  private onOpenDialog_() {
+  protected onOpenDialog_() {
     this.statusTexts_ =
         ['Dialog was opened and fired a `cr-dialog-open` event.'];
   }
 
-  private onCancelDialog_() {
-    this.push(
-        'statusTexts_', 'Dialog was canceled and fired a `cancel` event.');
+  protected onCancelDialog_() {
+    this.statusTexts_.push('Dialog was canceled and fired a `cancel` event.');
+    this.requestUpdate();
   }
 
-  private onCloseDialog_() {
+  protected onCloseDialog_() {
     this.isDialogOpen_ = false;
-    this.push('statusTexts_', 'Dialog was closed and fired a `close` event.');
+    this.statusTexts_.push('Dialog was closed and fired a `close` event.');
+  }
+
+  protected onShowHeaderChanged_(e: CustomEvent<{value: boolean}>) {
+    this.showHeader_ = e.detail.value;
+  }
+
+  protected onShowFooterChanged_(e: CustomEvent<{value: boolean}>) {
+    this.showFooter_ = e.detail.value;
+  }
+
+  protected onShowScrollingBodyChanged_(e: CustomEvent<{value: boolean}>) {
+    this.showScrollingBody_ = e.detail.value;
+  }
+
+  protected onShowInputsChanged_(e: CustomEvent<{value: boolean}>) {
+    this.showInputs_ = e.detail.value;
+  }
+
+  protected onAutofocusInputChanged_(e: CustomEvent<{value: boolean}>) {
+    this.autofocusInput_ = e.detail.value;
   }
 }
 
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.css b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.css
new file mode 100644
index 0000000..2ca8ef85
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.css
@@ -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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=../demo_lit.css.js
+ * #scheme=relative
+ * #include=demo-lit
+ * #css_wrapper_metadata_end */
+
+.icons {
+  display: grid;
+  font-size: 11px;
+  gap: 12px;
+  grid-auto-rows: 75px;
+  grid-template-columns: repeat(auto-fill, 75px);
+  text-align: center;
+  width: 100%;
+}
+
+.icon {
+  align-items: center;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.icon cr-icon,
+.icon .cr-icon {
+  margin-block-end: 12px;
+}
+
+.label {
+  height: 30%;
+}
+
+cr-input {
+  --cr-input-error-display: none;
+}
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.html
index f81e1a7..90c7128 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.html
@@ -1,49 +1,17 @@
-<style include="cr-icons demo">
-  .icons {
-    display: grid;
-    font-size: 11px;
-    gap: 12px;
-    grid-auto-rows: 75px;
-    grid-template-columns: repeat(auto-fill, 75px);
-    text-align: center;
-    width: 100%;
-  }
-
-  .icon {
-    align-items: center;
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-  }
-
-  .icon cr-icon,
-  .icon .cr-icon {
-    margin-block-end: 12px;
-  }
-
-  .label {
-    height: 30%;
-  }
-
-  cr-input {
-    --cr-input-error-display: none;
-  }
-</style>
-
 <h1>cr-icons from the common cr-iconsets defined in icons_lit.html</h1>
 <div class="demos">
   <div>Commonly used cr-icons across WebUI built with SVG.</div>
-  <div class="icons" style$="
-      --iron-icon-fill-color: [[iconColor_]];
-      --iron-icon-height: [[iconSize_]]px;
-      --iron-icon-width: [[iconSize_]]px;
+  <div class="icons" .style="
+      --iron-icon-fill-color: ${this.iconColor_};
+      --iron-icon-height: ${this.iconSize_}px;
+      --iron-icon-width: ${this.iconSize_}px;
   ">
-    <template is="dom-repeat" items="[[icons_]]" as="icon">
+    ${this.icons_.map(icon => html`
       <div class="icon">
-        <cr-icon icon="[[icon]]"></cr-icon>
-        <div class="label">[[icon]]</div>
+        <cr-icon icon="${icon}"></cr-icon>
+        <div class="label">${icon}</div>
       </div>
-    </template>
+    `)}
   </div>
 </div>
 
@@ -57,10 +25,10 @@
       </defs>
     </svg>
   </cr-iconset>
-  <div class="icons" style$="
-      --iron-icon-fill-color: [[iconColor_]];
-      --iron-icon-height: [[iconSize_]]px;
-      --iron-icon-width: [[iconSize_]]px;
+  <div class="icons" .style="
+      --iron-icon-fill-color: ${this.iconColor_};
+      --iron-icon-height: ${this.iconSize_}px;
+      --iron-icon-width: ${this.iconSize_}px;
   ">
     <div class="icon">
       <cr-icon icon="desserts:cake"></cr-icon>
@@ -72,27 +40,29 @@
 <h1>CSS classes for icons, defined in cr_icons.css</h1>
 <div class="demos">
   <div>CSS classes to display icons, typically for cr-icon-button.</div>
-  <div class="icons" style$="
-      --cr-icon-color: [[iconColor_]];
-      --cr-icon-ripple-size: [[iconSize_]]px;
-      --cr-icon-size: [[iconSize_]]px;
+  <div class="icons" .style="
+      --cr-icon-color: ${this.iconColor_};
+      --cr-icon-ripple-size: ${this.iconSize_}px;
+      --cr-icon-size: ${this.iconSize_}px;
   ">
-    <template is="dom-repeat" items="[[crIcons_]]" as="icon">
+    ${this.crIcons_.map(icon => html`
       <div class="icon">
-        <div class$="cr-icon no-overlap [[icon]]"></div>
-        <div class="label">.[[icon]]</div>
+        <div class="cr-icon no-overlap ${icon}"></div>
+        <div class="label">.${icon}</div>
       </div>
-    </template>
+    `)}
   </div>
 </div>
 
 <h1>Custom controls</h1>
 <div class="demos">
-  <cr-input type="number" min="12" max="128" value="{{iconSize_}}"
+  <cr-input type="number" min="12" max="128" .value="${this.iconSize_}"
+      @value-changed="${this.onIconSizeChanged_}"
       label="Icon size"></cr-input>
 
   <label>
-    <input type="color" value="[[iconColor_]]" on-input="onIconColorInput_">
+    <input type="color" .value="${this.iconColor_}"
+        @input="${this.onIconColorInput_}">
     Icon fill color
   </label>
 </div>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.ts b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.ts
index 0015cb58..cf5d6d5 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_icons/cr_icons_demo.ts
@@ -11,29 +11,34 @@
 
 import type {CrIconsetElement} from '//resources/cr_elements/cr_icon/cr_iconset.js';
 import {assert} from '//resources/js/assert.js';
-import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './cr_icons_demo.html.js';
+import {getCss} from './cr_icons_demo.css.js';
+import {getHtml} from './cr_icons_demo.html.js';
 
-class CrIconsDemoElement extends PolymerElement {
+export class CrIconsDemoElement extends CrLitElement {
   static get is() {
     return 'cr-icons-demo';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
     return {
-      crIcons_: Array,
-      iconColor_: String,
-      iconSize_: String,
-      icons_: Array,
+      crIcons_: {type: Array},
+      iconColor_: {type: String},
+      iconSize_: {type: String},
+      icons_: {type: Array},
     };
   }
 
-  private crIcons_: string[] = [
+  protected crIcons_: string[] = [
     'icon-arrow-back',  'icon-arrow-dropdown', 'icon-cancel',
     'icon-clear',       'icon-copy-content',   'icon-delete-gray',
     'icon-edit',        'icon-folder-open',    'icon-picture-delete',
@@ -42,13 +47,11 @@
     'icon-settings',    'icon-visibility',     'icon-visibility-off',
     'subpage-arrow',
   ];
-  private iconColor_: string = '#000000';
-  private iconSize_: string = '24';
-  private icons_: string[] = [];
+  protected iconColor_: string = '#000000';
+  protected iconSize_: string = '24';
+  protected icons_: string[] = [];
 
-  override ready() {
-    super.ready();
-
+  override firstUpdated() {
     function getIconNames(iconset: CrIconsetElement) {
       return Array.from(iconset.querySelectorAll('g[id]'))
           .map((el: Element) => {
@@ -61,18 +64,24 @@
     const crIconsSet = document.head.querySelector<CrIconsetElement>(
         'cr-iconset[name=cr]');
     assert(crIconsSet);
-    this.push('icons_', ...getIconNames(crIconsSet));
+    this.icons_.push(...getIconNames(crIconsSet));
 
     const cr20IconsSet = document.head.querySelector<CrIconsetElement>(
         'cr-iconset[name=cr20]');
     assert(cr20IconsSet);
-    this.push('icons_', ...getIconNames(cr20IconsSet));
+    this.icons_.push(...getIconNames(cr20IconsSet));
+
+    this.requestUpdate();
   }
 
-  private onIconColorInput_(e: Event) {
+  protected onIconColorInput_(e: Event) {
     const color = (e.target as HTMLInputElement).value;
     this.iconColor_ = color;
   }
+
+  protected onIconSizeChanged_(e: CustomEvent<{value: string}>) {
+    this.iconSize_ = e.detail.value;
+  }
 }
 
 export const tagName = CrIconsDemoElement.is;
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.css b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.css
new file mode 100644
index 0000000..e8629569
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.css
@@ -0,0 +1,31 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=../demo_lit.css.js
+ * #import=//resources/cr_elements/cr_hidden_style_lit.css.js
+ * #import=//resources/cr_elements/cr_icons_lit.css.js
+ * #scheme=relative
+ * #include=cr-hidden-style-lit cr-icons-lit demo-lit
+ * #css_wrapper_metadata_end */
+
+cr-input,
+cr-textarea {
+  max-width: 400px;
+  width: 100%;
+}
+
+.no-error {
+  --cr-input-error-display: none;
+}
+
+.domain-name {
+  padding-inline-end: 8px;
+}
+
+label {
+  flex: 1;
+  white-space: nowrap;
+}
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.html
index 81d8875..5951df5 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.html
@@ -1,24 +1,3 @@
-<style include="cr-hidden-style cr-icons demo">
-  cr-input,
-  cr-textarea {
-    max-width: 400px;
-    width: 100%;
-  }
-
-  .no-error {
-    --cr-input-error-display: none;
-  }
-
-  .domain-name {
-    padding-inline-end: 8px;
-  }
-
-  label {
-    flex: 1;
-    white-space: nowrap;
-  }
-</style>
-
 <h1>cr-input</h1>
 <h2>Default "filled" inputs with built-in labels</h2>
 <div class="demos">
@@ -27,18 +6,20 @@
       type="text"
       label="Standard input"
       placeholder="Placeholder text"
-      value="{{textValue_}}">
+      .value="${this.textValue_}"
+      @value-changed="${this.onTextValueChanged_}">
   </cr-input>
 
   <cr-input
       class="no-error"
       type="search"
       placeholder="Search a query"
-      value="{{searchValue_}}">
+      .value="${this.searchValue_}"
+      @value-changed="${this.onSearchValueChanged_}">
     <div slot="inline-prefix" class="cr-icon icon-search" alt=""></div>
     <cr-icon-button class="icon-cancel"
-        hidden$="[[!searchValue_]]" slot="inline-suffix"
-        on-click="onClearSearchClick_"
+        ?hidden="${!this.searchValue_}" slot="inline-suffix"
+        @click="${this.onClearSearchClick_}"
         title="Clear search">
     </cr-icon-button>
   </cr-input>
@@ -48,7 +29,8 @@
       label="Email address"
       type="text"
       placeholder="username"
-      value="{{emailValue_}}">
+      .value="${this.emailValue_}"
+      @value-changed="${this.onEmailValueChanged_}">
     <div slot="inline-suffix" class="domain-name">@chromium.org</div>
   </cr-input>
 
@@ -60,8 +42,9 @@
       max="200"
       placeholder="A number between 5 and 200"
       error-message="Number needs to be between 5 and 200"
-      value="{{numberValue_}}">
-    <cr-button slot="suffix" on-click="onValidateClick_">Validate</cr-button>
+      .value="${this.numberValue_}"
+      @value-changed="${this.onNumberValueChanged_}">
+    <cr-button slot="suffix" @click="${this.onValidateClick_}">Validate</cr-button>
   </cr-input>
 
   <cr-input
@@ -70,7 +53,7 @@
       placeholder="Enter a pin of 4 digits"
       pattern="[0-9]{4}"
       error-message="Pin must be 4 digits"
-      value="{{pinValue_}}"
+      .value="${this.pinValue_}" @value-changed="${this.onPinValueChanged_}"
       auto-validate>
   </cr-input>
 
@@ -93,16 +76,17 @@
   <cr-textarea
       type="text"
       label="Textarea"
-      value="{{textareaValue_}}">
+      .value="${this.textareaValue_}"
+      @value-changed="${this.onTextareaValueChanged_}">
   </cr-textarea>
 
   <div>
-    <div>Text input value: [[textValue_]]</div>
-    <div>Search input value: [[searchValue_]]</div>
-    <div>Email input value: [[emailValue_]]</div>
-    <div>Number input value: [[numberValue_]]</div>
-    <div>Pin input value: [[pinValue_]]</div>
-    <div>Textarea value: [[textareaValue_]]</div>
+    <div>Text input value: ${this.textValue_}</div>
+    <div>Search input value: ${this.searchValue_}</div>
+    <div>Email input value: ${this.emailValue_}</div>
+    <div>Number input value: ${this.numberValue_}</div>
+    <div>Pin input value: ${this.pinValue_}</div>
+    <div>Textarea value: ${this.textareaValue_}</div>
   </div>
 </div>
 
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.ts b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.ts
index 5f3d055c..c030bff2 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_input/cr_input_demo.ts
@@ -3,58 +3,84 @@
 // found in the LICENSE file.
 
 import '//resources/cr_elements/cr_button/cr_button.js';
-import '//resources/cr_elements/cr_hidden_style.css.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
-import '//resources/cr_elements/cr_icons.css.js';
 import '//resources/cr_elements/cr_input/cr_input.js';
 import '//resources/cr_elements/cr_textarea/cr_textarea.js';
-import '../demo.css.js';
 
 import type {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
-import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './cr_input_demo.html.js';
+import {getCss} from './cr_input_demo.css.js';
+import {getHtml} from './cr_input_demo.html.js';
 
-interface CrInputDemoElement {
+export interface CrInputDemoElement {
   $: {
     numberInput: CrInputElement,
   };
 }
 
-class CrInputDemoElement extends PolymerElement {
+export class CrInputDemoElement extends CrLitElement {
   static get is() {
     return 'cr-input-demo';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
     return {
-      emailValue_: String,
-      numberValue_: String,
-      pinValue_: String,
-      searchValue_: String,
-      textValue_: String,
-      textareaValue_: String,
+      emailValue_: {type: String},
+      numberValue_: {type: String},
+      pinValue_: {type: String},
+      searchValue_: {type: String},
+      textValue_: {type: String},
+      textareaValue_: {type: String},
     };
   }
 
-  private emailValue_: string;
-  private numberValue_: string;
-  private pinValue_: string;
-  private searchValue_: string;
-  private textValue_: string;
-  private textareaValue_: string;
+  protected emailValue_: string;
+  protected numberValue_: string;
+  protected pinValue_: string;
+  protected searchValue_: string;
+  protected textValue_: string;
+  protected textareaValue_: string;
 
-  private onClearSearchClick_() {
+  protected onClearSearchClick_() {
     this.searchValue_ = '';
   }
 
-  private onValidateClick_() {
+  protected onValidateClick_() {
     this.$.numberInput.validate();
   }
+
+  protected onTextValueChanged_(e: CustomEvent<{value: string}>) {
+    this.textValue_ = e.detail.value;
+  }
+
+  protected onSearchValueChanged_(e: CustomEvent<{value: string}>) {
+    this.searchValue_ = e.detail.value;
+  }
+
+  protected onEmailValueChanged_(e: CustomEvent<{value: string}>) {
+    this.emailValue_ = e.detail.value;
+  }
+
+  protected onNumberValueChanged_(e: CustomEvent<{value: string}>) {
+    this.numberValue_ = e.detail.value;
+  }
+
+  protected onPinValueChanged_(e: CustomEvent<{value: string}>) {
+    this.pinValue_ = e.detail.value;
+  }
+
+  protected onTextareaValueChanged_(e: CustomEvent<{value: string}>) {
+    this.textareaValue_ = e.detail.value;
+  }
 }
 
 export const tagName = CrInputDemoElement.is;
diff --git a/chrome/browser/shortcuts/shortcut_creator_win.cc b/chrome/browser/shortcuts/shortcut_creator_win.cc
index 77ce845c..38a2812 100644
--- a/chrome/browser/shortcuts/shortcut_creator_win.cc
+++ b/chrome/browser/shortcuts/shortcut_creator_win.cc
@@ -73,8 +73,8 @@
                             ShortcutCreatorResult::kError);
     return;
   }
-  base::FilePath shortcut_path = desktop.Append(base::StrCat(
-      {base::UTF16ToWide(shortcut_metadata.shortcut_title), L".lnk"}));
+  base::FilePath shortcut_path = GetUniquePath(desktop.Append(base::StrCat(
+      {base::UTF16ToWide(shortcut_metadata.shortcut_title), L".lnk"})));
 
   base::win::ShortcutProperties target_and_args_properties;
   target_and_args_properties.set_target(chrome_proxy_path);
diff --git a/chrome/browser/storage/shared_storage_browsertest.cc b/chrome/browser/storage/shared_storage_browsertest.cc
index 2fd2c15..0183f27 100644
--- a/chrome/browser/storage/shared_storage_browsertest.cc
+++ b/chrome/browser/storage/shared_storage_browsertest.cc
@@ -6,11 +6,14 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include "base/containers/contains.h"
+#include "base/json/json_reader.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_base.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
@@ -231,6 +234,52 @@
   }
 }
 
+int GetSampleCountForHistogram(const std::string& histogram_name) {
+  auto* histogram = base::StatisticsRecorder::FindHistogram(histogram_name);
+  if (!histogram) {
+    return 0;
+  }
+  std::string json_output;
+  histogram->WriteJSON(
+      &json_output,
+      base::JSONVerbosityLevel::JSON_VERBOSITY_LEVEL_OMIT_BUCKETS);
+  std::optional<base::Value::Dict> json_dict =
+      base::JSONReader::ReadDict(json_output);
+  if (!json_dict) {
+    LOG(ERROR) << "Error parsing JSON of histogram data";
+    return 0;
+  }
+  std::optional<int> count = json_dict->FindInt("count");
+  if (!count) {
+    LOG(ERROR) << "Error: count missing from histogram data";
+    return 0;
+  }
+  return *count;
+}
+
+void WaitForHistogramsWithSampleCounts(
+    std::vector<std::tuple<std::string, int>> histogram_names_and_counts) {
+  while (true) {
+    content::FetchHistogramsFromChildProcesses();
+    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+    std::vector<std::tuple<std::string, int>> still_waiting;
+    for (const auto& [name, count] : histogram_names_and_counts) {
+      if (GetSampleCountForHistogram(name) < count) {
+        still_waiting.emplace_back(name, count);
+      }
+    }
+
+    histogram_names_and_counts = std::move(still_waiting);
+
+    if (histogram_names_and_counts.empty()) {
+      break;
+    }
+
+    DelayBy(base::Seconds(1));
+  }
+}
+
 // Return the active RenderFrameHost loaded in the last iframe in `parent_rfh`.
 content::RenderFrameHost* LastChild(content::RenderFrameHost* parent_rfh) {
   int child_end = 0;
@@ -1021,14 +1070,14 @@
 
   EXPECT_TRUE(attestations_console_observer.messages().empty());
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -1066,15 +1115,17 @@
                                  GetSharedStorageDisabledErrorMessage()));
     VerifyDebugErrorMessage(run_op_result.error);
 
-    // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-    EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                       GURL(url::kAboutBlankURL)));
-    WaitForHistograms({kErrorTypeHistogram, kWorkletNumPerPageHistogram});
+    WaitForHistogramsWithSampleCounts(
+        {std::make_tuple(kErrorTypeHistogram, 2)});
     histogram_tester_.ExpectBucketCount(
         kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
     histogram_tester_.ExpectBucketCount(
         kErrorTypeHistogram,
         blink::SharedStorageWorkletErrorType::kRunWebVisible, 1);
+
+    // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+    EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                       GURL(url::kAboutBlankURL)));
     histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
     return;
   }
@@ -1088,14 +1139,16 @@
   EXPECT_EQ("Finish executing \'test-operation\'",
             base::UTF16ToUTF8(run_op_console_observer.messages()[0].message));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-                     kTimingDocumentRunHistogram, kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kErrorTypeHistogram, 2),
+       std::make_tuple(kTimingDocumentRunHistogram, 1)});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -1168,17 +1221,18 @@
                          GetSharedStorageSelectURLDisabledErrorMessage()));
     VerifyDebugErrorMessage(run_url_op_result.error);
 
-    // Navigate away to record `kWorkletNumPerPageHistogram`,
-    // `kSelectUrlCallsPerPageHistogram` histograms.
-    EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                       GURL(url::kAboutBlankURL)));
-    WaitForHistograms({kErrorTypeHistogram, kWorkletNumPerPageHistogram,
-                       kSelectUrlCallsPerPageHistogram});
+    WaitForHistogramsWithSampleCounts(
+        {std::make_tuple(kErrorTypeHistogram, 2)});
     histogram_tester_.ExpectBucketCount(
         kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
     histogram_tester_.ExpectBucketCount(
         kErrorTypeHistogram,
         blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
+
+    // Navigate away to record `kWorkletNumPerPageHistogram`,
+    // `kSelectUrlCallsPerPageHistogram` histograms.
+    EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                       GURL(url::kAboutBlankURL)));
     histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
     histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
     return;
@@ -1203,18 +1257,17 @@
       "Finish executing \'test-url-selection-operation\'",
       base::UTF16ToUTF8(run_url_op_console_observer.messages()[0].message));
 
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kErrorTypeHistogram, 2),
+       std::make_tuple(kTimingDocumentSelectUrlHistogram, 1)});
+  histogram_tester_.ExpectUniqueSample(
+      kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
+  histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 1);
+
   // Navigate away to record `kWorkletNumPerPageHistogram`,
   // `kSelectUrlCallsPerPageHistogram` histograms.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
                                      GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-                     kTimingDocumentSelectUrlHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(
-      kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
-  histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 1);
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -1334,16 +1387,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletSetHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletSetHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletAppend) {
@@ -1366,16 +1418,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletAppendHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletAppendHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletDelete) {
@@ -1398,16 +1449,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletDeleteHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletDeleteHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletClear) {
@@ -1430,16 +1480,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletClearHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletClearHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletGet) {
@@ -1472,17 +1521,16 @@
   EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
       GetActiveWebContents(), script, "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletSetHistogram, kTimingWorkletGetHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletSetHistogram, 1);
     histogram_tester_.ExpectTotalCount(kTimingWorkletGetHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletKeys) {
@@ -1507,16 +1555,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletKeysHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletEntries) {
@@ -1541,16 +1588,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletEntriesHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletLength) {
@@ -1573,16 +1619,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingWorkletLengthHistogram});
     histogram_tester_.ExpectTotalCount(kTimingWorkletLengthHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletRemainingBudget) {
@@ -1605,16 +1650,15 @@
     )",
       "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
-
   if (SuccessExpected()) {
     WaitForHistograms({kTimingRemainingBudgetHistogram});
     histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 1);
   }
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
@@ -1642,17 +1686,13 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-       kTimingDocumentAddModuleHistogram, kTimingDocumentRunHistogram,
-       kTimingWorkletKeysHistogram, kTimingWorkletEntriesHistogram,
-       kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
-       kIteratedEntriesBenchmarksHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
+                     kTimingWorkletEntriesHistogram,
+                     kEntriesQueuedCountHistogram,
+                     kReceivedEntriesBenchmarksHistogram,
+                     kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -1704,6 +1744,11 @@
                                       2);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
                                       2);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 // See crbug.com/1453981: A CL on V8 side (https://crrev.com/c/4582948) made
@@ -1746,17 +1791,12 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-                     kTimingDocumentAddModuleHistogram,
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
                      kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
                      kEntriesQueuedCountHistogram,
                      kReceivedEntriesBenchmarksHistogram,
                      kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -1807,6 +1847,11 @@
                                       0);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
                                       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 // See crbug.com/1453981: A CL on V8 side (https://crrev.com/c/4582948) made
@@ -1849,17 +1894,12 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
   WaitForHistograms(
-      {kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-       kTimingDocumentAddModuleHistogram, kTimingDocumentRunHistogram,
-       kTimingWorkletEntriesHistogram, kEntriesQueuedCountHistogram,
-       kReceivedEntriesBenchmarksHistogram,
+      {kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+       kTimingDocumentRunHistogram, kTimingWorkletEntriesHistogram,
+       kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
        kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -1910,6 +1950,11 @@
                                       1);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
                                       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
@@ -1937,17 +1982,13 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-       kTimingDocumentAddModuleHistogram, kTimingDocumentRunHistogram,
-       kTimingWorkletKeysHistogram, kTimingWorkletEntriesHistogram,
-       kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
-       kIteratedEntriesBenchmarksHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
+                     kTimingWorkletEntriesHistogram,
+                     kEntriesQueuedCountHistogram,
+                     kReceivedEntriesBenchmarksHistogram,
+                     kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -1999,6 +2040,11 @@
                                       2);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
                                       2);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
@@ -2040,17 +2086,13 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-       kTimingDocumentAddModuleHistogram, kTimingDocumentRunHistogram,
-       kTimingWorkletKeysHistogram, kTimingWorkletEntriesHistogram,
-       kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
-       kIteratedEntriesBenchmarksHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
+                     kTimingWorkletEntriesHistogram,
+                     kEntriesQueuedCountHistogram,
+                     kReceivedEntriesBenchmarksHistogram,
+                     kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -2100,6 +2142,11 @@
                                       1);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
                                       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
@@ -2125,17 +2172,13 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kWorkletNumPerPageHistogram, kErrorTypeHistogram,
-       kTimingDocumentAddModuleHistogram, kTimingDocumentRunHistogram,
-       kTimingWorkletKeysHistogram, kTimingWorkletEntriesHistogram,
-       kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
-       kIteratedEntriesBenchmarksHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
+                     kTimingWorkletEntriesHistogram,
+                     kEntriesQueuedCountHistogram,
+                     kReceivedEntriesBenchmarksHistogram,
+                     kIteratedEntriesBenchmarksHistogram});
 
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -2169,6 +2212,11 @@
                                       2);
   histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
                                       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
@@ -2305,17 +2353,19 @@
       result.error,
       testing::HasSubstr("addModule() can only be invoked once per worklet"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -2359,11 +2409,9 @@
           'test-operation-1', {data: {}});
     )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
@@ -2371,6 +2419,10 @@
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOperationNotFound,
       1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -2395,17 +2447,19 @@
           'test-operation', {data: {}});
     )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -2430,17 +2484,19 @@
           'test-operation', {data: {}});
     )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -2466,17 +2522,19 @@
           'test-operation', {data: {'customField': 'customValue123'}});
     )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -2561,13 +2619,9 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
@@ -2576,6 +2630,11 @@
       blink::SharedStorageWorkletErrorType::
           kSelectURLNonWebVisibleOperationNotFound,
       1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -2621,19 +2680,20 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -2678,19 +2738,20 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -2736,19 +2797,20 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram,
       blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -2794,13 +2856,9 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
@@ -2809,6 +2867,11 @@
       blink::SharedStorageWorkletErrorType::
           kSelectURLNonWebVisibleReturnValueOutOfRange,
       1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -2854,13 +2917,9 @@
         })()
       )"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kTimingDocumentAddModuleHistogram, kErrorTypeHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+  WaitForHistogramsWithSampleCounts(
+      {std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
+       std::make_tuple(kErrorTypeHistogram, 2)});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
   histogram_tester_.ExpectBucketCount(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
@@ -2869,6 +2928,11 @@
       blink::SharedStorageWorkletErrorType::
           kSelectURLNonWebVisibleReturnValueToInt,
       1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
 }
@@ -3467,16 +3531,12 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(
-      GetActiveWebContents(),
-      https_server()->GetURL(kCrossOriginHost, kSimplePagePath)));
-  WaitForHistograms(
-      {kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-       kTimingDocumentRunHistogram, kTimingWorkletSetHistogram,
-       kTimingWorkletAppendHistogram, kTimingWorkletGetHistogram,
-       kTimingWorkletLengthHistogram, kTimingWorkletDeleteHistogram,
-       kTimingWorkletClearHistogram, kWorkletNumPerPageHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentRunHistogram, kTimingWorkletSetHistogram,
+                     kTimingWorkletAppendHistogram, kTimingWorkletGetHistogram,
+                     kTimingWorkletLengthHistogram,
+                     kTimingWorkletDeleteHistogram,
+                     kTimingWorkletClearHistogram});
 
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
@@ -3488,6 +3548,10 @@
   histogram_tester_.ExpectTotalCount(kTimingWorkletLengthHistogram, 1);
   histogram_tester_.ExpectTotalCount(kTimingWorkletDeleteHistogram, 2);
   histogram_tester_.ExpectTotalCount(kTimingWorkletClearHistogram, 1);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
@@ -3523,15 +3587,16 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
   WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-                     kTimingDocumentRunHistogram, kWorkletNumPerPageHistogram});
+                     kTimingDocumentRunHistogram});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 4);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 2);
   histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 2);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 2, 1);
 }
 
@@ -3582,18 +3647,17 @@
                                      "Finished script",
                                      /*use_select_url=*/true));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram`,
-  // `kSelectUrlCallsPerPageHistogram` histograms.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
   WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-                     kTimingDocumentSelectUrlHistogram,
-                     kWorkletNumPerPageHistogram,
-                     kSelectUrlCallsPerPageHistogram});
+                     kTimingDocumentSelectUrlHistogram});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 6);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 3);
   histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 3);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram`,
+  // `kSelectUrlCallsPerPageHistogram` histograms.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 3, 1);
   histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 3, 1);
 }
@@ -3780,13 +3844,10 @@
   EXPECT_DOUBLE_EQ(RemainingBudget(new_iframe, /*should_add_module=*/true),
                    kBudgetAllowed - std::log2(3));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-       kTimingDocumentSelectUrlHistogram, kTimingDocumentRunHistogram,
-       kTimingRemainingBudgetHistogram, kWorkletNumPerPageHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentSelectUrlHistogram,
+                     kTimingDocumentRunHistogram,
+                     kTimingRemainingBudgetHistogram});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 5);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 2);
@@ -3794,6 +3855,10 @@
   histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 2);
   histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 2);
 
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+
   // In the MPArch case, some additional pageloads with worklet count 0 are
   // recorded, so we do not use `ExpectUniqueSample()` here.
   histogram_tester_.ExpectBucketCount(kWorkletNumPerPageHistogram, 1, 2);
@@ -3852,13 +3917,10 @@
   EXPECT_DOUBLE_EQ(RemainingBudget(iframe3, /*should_add_module=*/true),
                    kBudgetAllowed - std::log2(3) - std::log2(3));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms(
-      {kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
-       kTimingDocumentSelectUrlHistogram, kTimingDocumentRunHistogram,
-       kTimingRemainingBudgetHistogram, kWorkletNumPerPageHistogram});
+  WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
+                     kTimingDocumentSelectUrlHistogram,
+                     kTimingDocumentRunHistogram,
+                     kTimingRemainingBudgetHistogram});
   histogram_tester_.ExpectUniqueSample(
       kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 9);
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 3);
@@ -3866,6 +3928,10 @@
   histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 4);
   histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 4);
 
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+
   // In the MPArch case, some additional pageloads with worklet count 0 are
   // recorded, so we do not use `ExpectUniqueSample()` here.
   histogram_tester_.ExpectBucketCount(kWorkletNumPerPageHistogram, 1, 3);
@@ -3961,12 +4027,7 @@
     )",
                                      "Finished script"));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram,
-                     kPrivateAggregationHostPipeResultHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
+  WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
   histogram_tester_.ExpectUniqueSample(
       kPrivateAggregationHostPipeResultHistogram,
       SuccessExpected()
@@ -3977,6 +4038,11 @@
   histogram_tester_.ExpectTotalCount(
       kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrivateAggregationChromeBrowserTest,
@@ -4000,12 +4066,7 @@
                                      "Finished script",
                                      /*use_select_url=*/true));
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram,
-                     kPrivateAggregationHostPipeResultHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
+  WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
   histogram_tester_.ExpectUniqueSample(
       kPrivateAggregationHostPipeResultHistogram,
       SuccessExpected()
@@ -4015,6 +4076,11 @@
   histogram_tester_.ExpectTotalCount(
       kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
       0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrivateAggregationChromeBrowserTest,
@@ -4038,12 +4104,7 @@
                                                       'example_id'}});
       )");
 
-  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
-  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
-                                     GURL(url::kAboutBlankURL)));
-  WaitForHistograms({kWorkletNumPerPageHistogram,
-                     kPrivateAggregationHostPipeResultHistogram});
-  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
+  WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
   histogram_tester_.ExpectUniqueSample(
       kPrivateAggregationHostPipeResultHistogram,
       SuccessExpected()
@@ -4053,6 +4114,11 @@
   histogram_tester_.ExpectTotalCount(
       kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
       SuccessExpected() ? 1 : 0);
+
+  // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
+  EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
+                                     GURL(url::kAboutBlankURL)));
+  histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
 class SharedStorageHeaderPrefBrowserTest : public SharedStoragePrefBrowserTest {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e8093d3..e494ebb 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3028,7 +3028,7 @@
       "views/bruschetta/bruschetta_installer_view.h",
       "views/bruschetta/bruschetta_uninstaller_view.cc",
       "views/bruschetta/bruschetta_uninstaller_view.h",
-      "views/chrome_views_delegate_chromeos.cc",
+      "views/chrome_views_delegate_ash.cc",
       "views/crostini/crostini_ansible_software_config_view.cc",
       "views/crostini/crostini_ansible_software_config_view.h",
       "views/crostini/crostini_app_restart_dialog.cc",
@@ -3813,6 +3813,7 @@
       "//ash/webui/print_preview_cros",
       "//ash/webui/projector_app",
       "//ash/webui/projector_app/public/mojom:projector_mojo_bindings",
+      "//ash/webui/sanitize_ui",
       "//ash/webui/scanning",
       "//ash/webui/shimless_rma",
       "//ash/webui/shortcut_customization_ui",
@@ -4220,6 +4221,8 @@
       "lens/lens_overlay_side_panel_coordinator.h",
       "lens/lens_overlay_side_panel_navigation_throttle.cc",
       "lens/lens_overlay_side_panel_navigation_throttle.h",
+      "lens/lens_overlay_theme_utils.cc",
+      "lens/lens_overlay_theme_utils.h",
       "lens/lens_search_bubble_controller.cc",
       "lens/lens_search_bubble_controller.h",
       "lens/lens_untrusted_ui.cc",
diff --git a/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_even.xml b/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_even.xml
index 5aae015d..8217be5e 100644
--- a/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_even.xml
+++ b/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_even.xml
@@ -12,14 +12,12 @@
     android:id="@+id/google_bottom_bar_container"
     android:layout_width="match_parent"
     android:layout_height="@dimen/google_bottom_bar_height"
-    android:paddingLeft="@dimen/google_bottom_bar_padding"
-    android:paddingRight="@dimen/google_bottom_bar_padding"
     android:background="@color/google_bottom_bar_background_color"
     android:baselineAligned="false"
     android:orientation="horizontal">
-  <ImageView
+  <ImageButton
       android:id="@+id/google_bottom_bar_save_button"
-      android:layout_width="0dp"
+      android:layout_width="@dimen/google_bottom_bar_button_size"
       android:layout_weight="1"
       android:layout_height="@dimen/google_bottom_bar_button_size"
       android:layout_marginTop="@dimen/google_bottom_bar_button_margin_vertical"
@@ -28,10 +26,11 @@
       android:layout_gravity="center_vertical"
       android:importantForAccessibility="yes"
       android:contentDescription="@null"
-      android:src="@drawable/bookmark" />
-  <ImageView
+      android:src="@drawable/bookmark"
+      android:background="@color/google_bottom_bar_background_color" />
+  <ImageButton
       android:id="@+id/google_bottom_bar_page_insights_button"
-      android:layout_width="0dp"
+      android:layout_width="@dimen/google_bottom_bar_button_size"
       android:layout_weight="1"
       android:layout_height="@dimen/google_bottom_bar_button_size"
       android:layout_marginTop="@dimen/google_bottom_bar_button_margin_vertical"
@@ -42,10 +41,10 @@
       android:contentDescription="@null"
       android:visibility="visible"
       android:src="@drawable/page_insights_icon"
-      app:tint="@color/default_icon_color_baseline" />
-  <ImageView
+      android:background="@color/google_bottom_bar_background_color" />
+  <ImageButton
       android:id="@+id/google_bottom_bar_share_button"
-      android:layout_width="0dp"
+      android:layout_width="@dimen/google_bottom_bar_button_size"
       android:layout_weight="1"
       android:layout_height="@dimen/google_bottom_bar_button_size"
       android:layout_marginTop="@dimen/google_bottom_bar_button_margin_vertical"
@@ -56,5 +55,5 @@
       android:contentDescription="@null"
       android:visibility="visible"
       android:src="@drawable/ic_share_white_24dp"
-      app:tint="@color/default_icon_color_baseline" />
+      android:background="@color/google_bottom_bar_background_color" />
 </LinearLayout>
diff --git a/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_spotlight.xml b/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_spotlight.xml
index bae524a..ca2d8e5e 100644
--- a/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_spotlight.xml
+++ b/chrome/browser/ui/android/google_bottom_bar/java/res/layout/google_bottom_bar_spotlight.xml
@@ -17,19 +17,17 @@
     android:background="@color/google_bottom_bar_background_color"
     android:baselineAligned="false"
     android:orientation="horizontal">
-  <ImageView
+  <ImageButton
       android:id="@+id/google_bottom_bar_page_insights_button"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:paddingStart="2dp"
-      android:paddingTop="2dp"
+      android:layout_width="@dimen/google_bottom_bar_button_size"
+      android:layout_height="@dimen/google_bottom_bar_button_size"
       android:scaleType="centerInside"
       android:layout_gravity="center"
       android:importantForAccessibility="yes"
       android:contentDescription="@null"
       android:visibility="visible"
       android:src="@drawable/page_insights_icon"
-      app:tint="@color/default_icon_color_baseline" />
+      android:background="@color/google_bottom_bar_background_color" />
   <LinearLayout
       android:id="@+id/google_bottom_bar_non_spotlit_buttons_container"
       android:layout_width="0dp"
@@ -40,7 +38,7 @@
       android:paddingStart="@dimen/google_bottom_bar_expanded_button_padding_horizontal"
       android:paddingEnd="@dimen/google_bottom_bar_expanded_button_padding_horizontal"
       android:orientation="horizontal">
-    <ImageView
+    <ImageButton
         android:id="@+id/google_bottom_bar_save_button"
         android:layout_width="@dimen/google_bottom_bar_button_size"
         android:layout_height="@dimen/google_bottom_bar_button_size"
@@ -49,8 +47,9 @@
         android:layout_marginStart="@dimen/google_bottom_bar_button_padding"
         android:importantForAccessibility="yes"
         android:contentDescription="@null"
-        android:src="@drawable/bookmark" />
-    <ImageView
+        android:src="@drawable/bookmark"
+        android:background="@color/google_bottom_bar_background_color" />
+    <ImageButton
         android:id="@+id/google_bottom_bar_share_button"
         android:layout_width="@dimen/google_bottom_bar_button_size"
         android:layout_height="@dimen/google_bottom_bar_button_size"
@@ -61,6 +60,6 @@
         android:contentDescription="@null"
         android:visibility="visible"
         android:src="@drawable/ic_share_white_24dp"
-        app:tint="@color/default_icon_color_baseline" />
+        android:background="@color/google_bottom_bar_background_color" />
   </LinearLayout>
 </LinearLayout>
diff --git a/chrome/browser/ui/android/google_bottom_bar/java/src/org/chromium/chrome/browser/ui/google_bottom_bar/GoogleBottomBarViewCreator.java b/chrome/browser/ui/android/google_bottom_bar/java/src/org/chromium/chrome/browser/ui/google_bottom_bar/GoogleBottomBarViewCreator.java
index 93e18fce..ba392865 100644
--- a/chrome/browser/ui/android/google_bottom_bar/java/src/org/chromium/chrome/browser/ui/google_bottom_bar/GoogleBottomBarViewCreator.java
+++ b/chrome/browser/ui/android/google_bottom_bar/java/src/org/chromium/chrome/browser/ui/google_bottom_bar/GoogleBottomBarViewCreator.java
@@ -8,7 +8,7 @@
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.ImageView;
+import android.widget.ImageButton;
 
 import androidx.annotation.Nullable;
 
@@ -83,7 +83,7 @@
 
     private void initButton(int viewId, @ButtonId int buttonConfigId) {
         assert mRootView != null;
-        ImageView button = mRootView.findViewById(viewId);
+        ImageButton button = mRootView.findViewById(viewId);
         ButtonConfig buttonConfig = findButtonConfig(buttonConfigId);
         maybeUpdateButton(button, buttonConfig, /* isFirstTimeShown= */ true);
     }
@@ -92,7 +92,7 @@
         if (buttonConfig == null) {
             return false;
         }
-        ImageView button = mRootView.findViewById(buttonConfig.getId());
+        ImageButton button = mRootView.findViewById(buttonConfig.getId());
         return maybeUpdateButton(button, buttonConfig, /* isFirstTimeShown= */ false);
     }
 
@@ -114,7 +114,7 @@
     }
 
     private boolean maybeUpdateButton(
-            @Nullable ImageView button,
+            @Nullable ImageButton button,
             @Nullable ButtonConfig buttonConfig,
             boolean isFirstTimeShown) {
         if (button == null || buttonConfig == null) {
diff --git a/chrome/browser/ui/ash/quick_settings_integration_test.cc b/chrome/browser/ui/ash/quick_settings_integration_test.cc
index 8848ecc..094fa93 100644
--- a/chrome/browser/ui/ash/quick_settings_integration_test.cc
+++ b/chrome/browser/ui/ash/quick_settings_integration_test.cc
@@ -167,7 +167,9 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(QuickSettingsLacrosIntegrationTest, ManagedDeviceInfo) {
+// TODO(b/341659658): Fails to open Lacros window.
+IN_PROC_BROWSER_TEST_F(QuickSettingsLacrosIntegrationTest,
+                       DISABLED_ManagedDeviceInfo) {
   // On VM tryservers like chromeos-amd64-generic Lacros fails to start up
   // correctly (it restarts in a loop). b/303359438
   if (base::CPU().is_running_in_vm()) {
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 1e312b74..99af967 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -435,14 +435,30 @@
 void ReloadInternal(Browser* browser,
                     WindowOpenDisposition disposition,
                     bool bypass_cache) {
-  const WebContents* active_contents =
+  const WebContents* const active_contents =
       browser->tab_strip_model()->GetActiveWebContents();
-  const auto& selected_indices =
-      browser->tab_strip_model()->selection_model().selected_indices();
-  for (int index : selected_indices) {
-    WebContents* selected_tab =
-        browser->tab_strip_model()->GetWebContentsAt(index);
-    WebContents* new_tab =
+
+  // Reloading a tab may change the selection (see crbug.com/339061099), so take
+  // a defensive copy into a more stable form before we begin. We take
+  // WebContents* so we can follow the tabs as they shift within the same
+  // tabstrip (e.g. if `disposition` is NEW_BACKGROUND_TAB).
+  std::vector<WebContents*> selected_tabs;
+  for (const int selected_index :
+       browser->tab_strip_model()->selection_model().selected_indices()) {
+    selected_tabs.push_back(
+        browser->tab_strip_model()->GetWebContentsAt(selected_index));
+  }
+
+  for (WebContents* const selected_tab : selected_tabs) {
+    // Skip this tab if it is no longer part of this tabstrip. N.B. we do this
+    // instead of using WeakPtr<WebContents> because we do not want to reload
+    // tabs that move to another browser.
+    if (browser->tab_strip_model()->GetIndexOfWebContents(selected_tab) ==
+        TabStripModel::kNoTab) {
+      continue;
+    }
+
+    WebContents* const new_tab =
         GetTabAndRevertIfNecessaryHelper(browser, disposition, selected_tab);
 
     // If the selected_tab is the activated page, give the focus to it, as this
@@ -455,7 +471,7 @@
     // User reloads is a possible breakage indicator from blocking 3P cookies.
     RecordReloadWithCookieBlocking(browser, selected_tab);
 
-    DevToolsWindow* devtools =
+    DevToolsWindow* const devtools =
         DevToolsWindow::GetInstanceForInspectedWebContents(new_tab);
     constexpr content::ReloadType kBypassingType =
         content::ReloadType::BYPASSING_CACHE;
diff --git a/chrome/browser/ui/color/chrome_color_mixers.cc b/chrome/browser/ui/color/chrome_color_mixers.cc
index dbc6656..9698e807 100644
--- a/chrome/browser/ui/color/chrome_color_mixers.cc
+++ b/chrome/browser/ui/color/chrome_color_mixers.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/ui/color/omnibox_color_mixer.h"
 #include "chrome/browser/ui/color/product_specifications_color_mixer.h"
 #include "chrome/browser/ui/color/tab_strip_color_mixer.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_provider_utils.h"
 
 namespace {
@@ -66,13 +65,11 @@
   AddProductSpecificationsColorMixer(provider, key);
   AddTabStripColorMixer(provider, key);
 
-  if (features::IsChromeRefresh2023()) {
-    AddMaterialChromeColorMixer(provider, key);
-    AddMaterialNewTabPageColorMixer(provider, key);
-    AddMaterialOmniboxColorMixer(provider, key);
-    AddMaterialSidePanelColorMixer(provider, key);
-    AddMaterialTabStripColorMixer(provider, key);
-  }
+  AddMaterialChromeColorMixer(provider, key);
+  AddMaterialNewTabPageColorMixer(provider, key);
+  AddMaterialOmniboxColorMixer(provider, key);
+  AddMaterialSidePanelColorMixer(provider, key);
+  AddMaterialTabStripColorMixer(provider, key);
 
   // Must be the last one in order to override other mixer colors.
   AddNativeChromeColorMixer(provider, key);
diff --git a/chrome/browser/ui/color/chrome_color_provider_utils.cc b/chrome/browser/ui/color/chrome_color_provider_utils.cc
index 0297792..357e9bf 100644
--- a/chrome/browser/ui/color/chrome_color_provider_utils.cc
+++ b/chrome/browser/ui/color/chrome_color_provider_utils.cc
@@ -128,6 +128,5 @@
   // contrast.
   // TODO(tluk): Switch to using the high contrast configuration for ref tokens
   // generated by the material_color_utilities when supported.
-  return features::IsChromeRefresh2023() && !key.custom_theme &&
-         !ShouldApplyHighContrastColors(key);
+  return !key.custom_theme && !ShouldApplyHighContrastColors(key);
 }
diff --git a/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc b/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
index d349459..543e515 100644
--- a/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
+++ b/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/win/titlebar_config.h"
 #include "chrome/grit/theme_resources.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
@@ -101,11 +100,9 @@
       SkColorSetRGB(0xE8, 0xE8, 0xE8);
   constexpr SkColor kSystemMicaDarkFrameColor = SkColorSetRGB(0x20, 0x20, 0x20);
 
-  // Dwm colors should always be applied if present for pervasive accent colors
-  // pre-refresh. With refresh enabled we should only attempt to paint
-  // system-style frames if configured to do so in the key.
+  // We should only attempt to paint system-style frames if configured to do so
+  // in the key.
   const bool use_native_colors =
-      !features::IsChromeRefresh2023() ||
       (key.frame_type == ui::ColorProviderKey::FrameType::kChromium &&
        key.frame_style == ui::ColorProviderKey::FrameStyle::kSystem);
 
diff --git a/chrome/browser/ui/color/win/native_chrome_color_mixer_win_browsertest.cc b/chrome/browser/ui/color/win/native_chrome_color_mixer_win_browsertest.cc
index ef508980..381e805 100644
--- a/chrome/browser/ui/color/win/native_chrome_color_mixer_win_browsertest.cc
+++ b/chrome/browser/ui/color/win/native_chrome_color_mixer_win_browsertest.cc
@@ -57,13 +57,9 @@
   accent_color_observer->SetAccentColorForTesting(kAccentColor);
   accent_color_observer->SetUseDwmFrameColorForTesting(true);
 
-  // When running refresh the header color should be unaffected as the theme
-  // service has not been set to follow the device theme. When running
-  // pre-refresh the header color should be set to the accent color as part of
-  // pervasive accent colors.
-  EXPECT_EQ(
-      features::IsChromeRefresh2023() ? initial_header_color : kAccentColor,
-      get_header_color());
+  // The header color should be unaffected as the theme service has not been set
+  // to follow the device theme.
+  EXPECT_EQ(initial_header_color, get_header_color());
 
   // Set the browser to follow the device colors. The header color should be
   // updated to track the accent color.
diff --git a/chrome/browser/ui/exclusive_access/keyboard_lock_controller.cc b/chrome/browser/ui/exclusive_access/keyboard_lock_controller.cc
index 5bd6670..5a5f01a5 100644
--- a/chrome/browser/ui/exclusive_access/keyboard_lock_controller.cc
+++ b/chrome/browser/ui/exclusive_access/keyboard_lock_controller.cc
@@ -204,6 +204,7 @@
   manager->fullscreen_controller()->HandleUserPressedEscape();
   manager->pointer_lock_controller()->HandleUserPressedEscape();
   HandleUserPressedEscape();
+  base::RecordAction(base::UserMetricsAction("UnlockKeyboard_PressAndHoldEsc"));
 }
 
 void KeyboardLockController::ReShowExitBubbleIfNeeded() {
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 856764a..c3e76ab 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/ui/lens/lens_overlay_permission_utils.h"
 #include "chrome/browser/ui/lens/lens_overlay_query_controller.h"
 #include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h"
+#include "chrome/browser/ui/lens/lens_overlay_theme_utils.h"
 #include "chrome/browser/ui/lens/lens_overlay_url_builder.h"
 #include "chrome/browser/ui/lens/lens_permission_bubble_controller.h"
 #include "chrome/browser/ui/lens/lens_search_bubble_controller.h"
@@ -159,18 +160,6 @@
   return top_level_widget->GetNativeView();
 }
 
-// TODO(b/341360519): Move this to a separate file.
-bool SidePanelShouldUseDarkMode(ThemeService* theme_service) {
-  if (!lens::features::UseBrowserDarkModeSettingForLensOverlay()) {
-    return false;
-  }
-  ThemeService::BrowserColorScheme color_scheme =
-      theme_service->GetBrowserColorScheme();
-  return color_scheme == ThemeService::BrowserColorScheme::kSystem
-             ? ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()
-             : color_scheme == ThemeService::BrowserColorScheme::kDark;
-}
-
 }  // namespace
 
 LensOverlayController::LensOverlayController(
@@ -339,7 +328,7 @@
       base::BindRepeating(&LensOverlayController::HandleThumbnailCreated,
                           weak_factory_.GetWeakPtr()),
       variations_client_, identity_manager_, invocation_source,
-      SidePanelShouldUseDarkMode(theme_service_));
+      lens::LensOverlayShouldUseDarkMode(theme_service_));
 
   side_panel_coordinator_ =
       SidePanelUtil::GetSidePanelCoordinatorForBrowser(tab_browser);
@@ -441,7 +430,7 @@
   page_.Bind(std::move(page));
 
   InitializeOverlayUI(*initialization_data_);
-  base::UmaHistogramBoolean("Desktop.LensOverlay.Shown", true);
+  base::UmaHistogramBoolean("Lens.Overlay.Shown", true);
   state_ = State::kOverlay;
 
   // Only start the query flow again if we don't already have a full image
diff --git a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
index ce31730..7a318a9 100644
--- a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
+++ b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
@@ -166,6 +166,9 @@
 
 void LensOverlaySidePanelCoordinator::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
+  // Focus the web contents immediately, so that hotkey presses (i.e. escape)
+  // are handled.
+  GetSidePanelWebContents()->Focus();
   // We only care about the navigation if it is the results frame, is HTTPS,
   // renderer initiated and NOT a same document navigation.
   if (!navigation_handle->IsRendererInitiated() ||
diff --git a/chrome/browser/ui/lens/lens_overlay_theme_utils.cc b/chrome/browser/ui/lens/lens_overlay_theme_utils.cc
new file mode 100644
index 0000000..fa12a18
--- /dev/null
+++ b/chrome/browser/ui/lens/lens_overlay_theme_utils.cc
@@ -0,0 +1,24 @@
+// 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/lens/lens_overlay_theme_utils.h"
+
+#include "chrome/browser/themes/theme_service.h"
+#include "components/lens/lens_features.h"
+#include "ui/native_theme/native_theme.h"
+
+namespace lens {
+
+bool LensOverlayShouldUseDarkMode(ThemeService* theme_service) {
+  if (!lens::features::UseBrowserDarkModeSettingForLensOverlay()) {
+    return false;
+  }
+  ThemeService::BrowserColorScheme color_scheme =
+      theme_service->GetBrowserColorScheme();
+  return color_scheme == ThemeService::BrowserColorScheme::kSystem
+             ? ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors()
+             : color_scheme == ThemeService::BrowserColorScheme::kDark;
+}
+
+}  // namespace lens
diff --git a/chrome/browser/ui/lens/lens_overlay_theme_utils.h b/chrome/browser/ui/lens/lens_overlay_theme_utils.h
new file mode 100644
index 0000000..98ef64e
--- /dev/null
+++ b/chrome/browser/ui/lens/lens_overlay_theme_utils.h
@@ -0,0 +1,18 @@
+// 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_LENS_LENS_OVERLAY_THEME_UTILS_H_
+#define CHROME_BROWSER_UI_LENS_LENS_OVERLAY_THEME_UTILS_H_
+
+class ThemeService;
+
+namespace lens {
+
+// Returns true if the lens overlay results and searchbox in the
+// side panel should use dark mode.
+bool LensOverlayShouldUseDarkMode(ThemeService* theme_service);
+
+}  // namespace lens
+
+#endif  // CHROME_BROWSER_UI_LENS_LENS_OVERLAY_THEME_UTILS_H_
diff --git a/chrome/browser/ui/lens/lens_untrusted_ui.cc b/chrome/browser/ui/lens/lens_untrusted_ui.cc
index c65ec6d..390f778e 100644
--- a/chrome/browser/ui/lens/lens_untrusted_ui.cc
+++ b/chrome/browser/ui/lens/lens_untrusted_ui.cc
@@ -6,7 +6,9 @@
 
 #include "base/strings/strcat.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_theme_utils.h"
 #include "chrome/browser/ui/webui/searchbox/realbox_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/pref_names.h"
@@ -85,6 +87,10 @@
                           lens::features::GetLensOverlayTapRegionHeight());
   html_source->AddInteger("tapRegionWidth",
                           lens::features::GetLensOverlayTapRegionWidth());
+  html_source->AddBoolean(
+      "darkMode",
+      lens::LensOverlayShouldUseDarkMode(
+          ThemeServiceFactory::GetForProfile(Profile::FromWebUI(web_ui))));
 
   // Allow FrameSrc from all Google subdomains as redirects can occur.
   GURL results_side_panel_url =
diff --git a/chrome/browser/ui/side_panel/companion/companion_utils.cc b/chrome/browser/ui/side_panel/companion/companion_utils.cc
index ccf0f94..70a6fe03 100644
--- a/chrome/browser/ui/side_panel/companion/companion_utils.cc
+++ b/chrome/browser/ui/side_panel/companion/companion_utils.cc
@@ -140,14 +140,6 @@
          ShouldEnableOpenCompanionForImageSearch();
 }
 
-bool IsNewBadgeEnabledForSearchMenuItem(const Browser* browser) {
-  if (!browser) {
-    return false;
-  }
-  return base::FeatureList::IsEnabled(
-      features::kCompanionEnableNewBadgesInContextMenu);
-}
-
 void UpdateCompanionDefaultPinnedToToolbarState(Profile* profile) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   CHECK(profile);
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.cc
index a3eddedc..805f601 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.cc
@@ -29,11 +29,16 @@
   if (ui::PageTransitionIsRedirect(page_transition)) {
     return false;
   }
+
   if (!ui::PageTransitionIsMainFrame(page_transition)) {
     return false;
   }
 
-  if (navigation_handle->IsSameDocument()) {
+  if (!navigation_handle->HasCommitted()) {
+    return false;
+  }
+
+  if (!navigation_handle->ShouldUpdateHistory()) {
     return false;
   }
 
@@ -109,8 +114,6 @@
     return;
   }
 
-  handle_from_sync_update_ = nullptr;
-
   if (!IsSaveableNavigation(navigation_handle)) {
     return;
   }
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.cc b/chrome/browser/ui/tabs/tab_renderer_data.cc
index 3bfec8e..7a3f599 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.cc
+++ b/chrome/browser/ui/tabs/tab_renderer_data.cc
@@ -7,7 +7,6 @@
 #include "base/process/kill.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/browser_process.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom-shared.h"
@@ -22,7 +21,6 @@
 #include "chrome/browser/ui/web_applications/web_app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/web_app_tabbed_utils.h"
 #include "components/performance_manager/public/features.h"
-#include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
@@ -112,10 +110,7 @@
       memory_saver::IsURLSupported(contents->GetURL()) &&
       contents->WasDiscarded() && discard_reason.has_value() &&
       (discard_reason.value() == mojom::LifecycleUnitDiscardReason::PROACTIVE ||
-       discard_reason.value() ==
-           mojom::LifecycleUnitDiscardReason::SUGGESTED) &&
-      performance_manager::user_tuning::prefs::ShouldShowDiscardRingTreatment(
-          g_browser_process->local_state());
+       discard_reason.value() == mojom::LifecycleUnitDiscardReason::SUGGESTED);
 
   if (contents->WasDiscarded()) {
     data.discarded_memory_savings_in_bytes =
diff --git a/chrome/browser/ui/views/DEPS b/chrome/browser/ui/views/DEPS
index 9ee592f..fba5f9b 100644
--- a/chrome/browser/ui/views/DEPS
+++ b/chrome/browser/ui/views/DEPS
@@ -17,7 +17,7 @@
   "browser_user_education_service.cc": [
     "+ash/user_education/views/help_bubble_factory_views_ash.h",
   ],
-  "chrome_views_delegate_chromeos\.cc": [
+  "chrome_views_delegate_ash\.cc": [
     "+ash/shell.h",
   ],
   "isolated_web_app_installer_view_browsertest\.cc": [
diff --git a/chrome/browser/ui/views/chrome_views_delegate_chromeos.cc b/chrome/browser/ui/views/chrome_views_delegate_ash.cc
similarity index 100%
rename from chrome/browser/ui/views/chrome_views_delegate_chromeos.cc
rename to chrome/browser/ui/views/chrome_views_delegate_ash.cc
diff --git a/chrome/browser/ui/views/chrome_views_delegate_lacros.cc b/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
index 38181b7d..e1baa9e5 100644
--- a/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
+++ b/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
@@ -2,17 +2,67 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/chrome_views_delegate.h"
-
 #include <memory>
 
+#include "chrome/browser/ui/views/chrome_views_delegate.h"
+#include "chromeos/ui/base/window_properties.h"
+#include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/frame_utils.h"
+#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
 #include "chromeos/ui/frame/non_client_frame_view_base.h"
+#include "ui/aura/window_observer.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// A NonClientFrameView for framed lacros widgets supporting immersive
+// fullscreen.
+class NonClientFrameViewLacros : public chromeos::NonClientFrameViewBase,
+                                 public aura::WindowObserver {
+  METADATA_HEADER(NonClientFrameViewLacros, chromeos::NonClientFrameViewBase)
+
+ public:
+  explicit NonClientFrameViewLacros(views::Widget* frame)
+      : NonClientFrameViewBase(frame) {
+    window_observation_.Observe(frame->GetNativeWindow());
+    immersive_fullscreen_controller_.Init(GetHeaderView(), frame,
+                                          GetHeaderView());
+  }
+  NonClientFrameViewLacros(const NonClientFrameViewLacros&) = delete;
+  NonClientFrameViewLacros& operator=(const NonClientFrameViewLacros&) = delete;
+  ~NonClientFrameViewLacros() override = default;
+
+  // aura::WindowObserver:
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override {
+    if (key == chromeos::kWindowStateTypeKey) {
+      const bool is_fullscreen =
+          window->GetProperty(chromeos::kWindowStateTypeKey) ==
+          chromeos::WindowStateType::kFullscreen;
+      chromeos::ImmersiveFullscreenController::EnableForWidget(frame_,
+                                                               is_fullscreen);
+    }
+  }
+  void OnWindowDestroying(aura::Window* window) override {
+    window_observation_.Reset();
+  }
+
+ private:
+  chromeos::ImmersiveFullscreenController immersive_fullscreen_controller_;
+  base::ScopedObservation<aura::Window, aura::WindowObserver>
+      window_observation_{this};
+};
+
+BEGIN_METADATA(NonClientFrameViewLacros)
+END_METADATA
+
+}  // namespace
 
 std::unique_ptr<views::NonClientFrameView>
 ChromeViewsDelegate::CreateDefaultNonClientFrameView(views::Widget* widget) {
-  return std::make_unique<chromeos::NonClientFrameViewBase>(widget);
+  return std::make_unique<NonClientFrameViewLacros>(widget);
 }
 
 bool ChromeViewsDelegate::ShouldWindowHaveRoundedCorners(
diff --git a/chrome/browser/ui/views/chrome_views_delegate_lacros_browsertest.cc b/chrome/browser/ui/views/chrome_views_delegate_lacros_browsertest.cc
new file mode 100644
index 0000000..1c4775cb
--- /dev/null
+++ b/chrome/browser/ui/views/chrome_views_delegate_lacros_browsertest.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 "base/test/run_until.h"
+#include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
+#include "content/public/test/browser_test.h"
+#include "ui/views/widget/unique_widget_ptr.h"
+#include "ui/views/widget/widget.h"
+
+using ChromeViewsDelegateLacrosBrowsertest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(ChromeViewsDelegateLacrosBrowsertest,
+                       DefaultNonClientFrameViewEntersImmersiveMode) {
+  views::UniqueWidgetPtr widget(std::make_unique<views::Widget>());
+  widget->Init(
+      views::Widget::InitParams(views::Widget::InitParams::TYPE_WINDOW));
+  widget->Show();
+
+  auto* immersive_fullscreen_controller =
+      chromeos::ImmersiveFullscreenController::Get(widget.get());
+  chromeos::ImmersiveFullscreenControllerTestApi(
+      immersive_fullscreen_controller)
+      .SetupForTest();
+
+  EXPECT_FALSE(immersive_fullscreen_controller->IsEnabled());
+
+  // Fullscreen the framed widget, it should enter immersive mode.
+  widget->SetFullscreen(true);
+  EXPECT_TRUE(base::test::RunUntil(
+      [&]() { return immersive_fullscreen_controller->IsEnabled(); }));
+
+  // Exiting fullscreen should also exit immersive mode.
+  widget->SetFullscreen(false);
+  EXPECT_TRUE(base::test::RunUntil(
+      [&]() { return !immersive_fullscreen_controller->IsEnabled(); }));
+}
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_pane_view.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_pane_view.cc
index c0c2a6c..6a71de0 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_pane_view.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_pane_view.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/browser/ui/views/desktop_capture/desktop_media_permission_pane_view.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/scroll_view.h"
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
index c03b8ae..17de0338 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
@@ -280,9 +280,7 @@
 
 std::unique_ptr<views::ScrollView> CreateScrollView(bool audio_requested) {
   auto scroll_view = std::make_unique<views::ScrollView>();
-  scroll_view->SetBackgroundThemeColorId(
-      features::IsChromeRefresh2023() ? ui::kColorSysSurface4
-                                      : ui::kColorSubtleEmphasisBackground);
+  scroll_view->SetBackgroundThemeColorId(ui::kColorSysSurface4);
   // The overflow indicator is disabled to reduce clutter next to the
   // separator to the audio control when audio is requested or the bottom of
   // the dialog when audio is not requested.
@@ -367,9 +365,7 @@
   SetModalType(params.modality);
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_SHARE));
-  if (features::IsChromeRefresh2023()) {
-    SetButtonStyle(ui::DIALOG_BUTTON_CANCEL, ui::ButtonStyle::kTonal);
-  }
+  SetButtonStyle(ui::DIALOG_BUTTON_CANCEL, ui::ButtonStyle::kTonal);
   RegisterDeleteDelegateCallback(base::BindOnce(
       [](DesktopMediaPickerDialogView* dialog) {
         // If the dialog is being closed then notify the parent about it.
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_source_view.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_source_view.cc
index 61b72fe..6f35ee64 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_source_view.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_source_view.cc
@@ -13,7 +13,6 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/canvas.h"
@@ -55,19 +54,15 @@
       source_id_(source_id),
       selected_(false) {
   icon_view_ = AddChildView(std::make_unique<views::ImageView>());
-  image_view_ = AddChildView(features::IsChromeRefresh2023()
-                                 ? std::make_unique<RoundedCornerImageView>()
-                                 : std::make_unique<views::ImageView>());
+  image_view_ = AddChildView(std::make_unique<RoundedCornerImageView>());
   label_ = AddChildView(std::make_unique<views::Label>());
   icon_view_->SetCanProcessEventsWithinSubtree(false);
   image_view_->SetCanProcessEventsWithinSubtree(false);
   SetFocusBehavior(FocusBehavior::ALWAYS);
   SetStyle(style);
   views::FocusRing::Install(this);
-  if (features::IsChromeRefresh2023()) {
-    views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
-                                                  kCornerRadius);
-  }
+  views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                kCornerRadius);
 }
 
 DesktopMediaSourceView::~DesktopMediaSourceView() {}
@@ -102,23 +97,14 @@
       }
     }
 
-    if (features::IsChromeRefresh2023()) {
-      SetBackground(views::CreateRoundedRectBackground(
-          GetColorProvider()->GetColor(ui::kColorSysTonalContainer),
-          kCornerRadius));
-    } else {
-      image_view_->SetBackground(views::CreateSolidBackground(
-          GetColorProvider()->GetColor(ui::kColorMenuItemBackgroundSelected)));
-    }
+    SetBackground(views::CreateRoundedRectBackground(
+        GetColorProvider()->GetColor(ui::kColorSysTonalContainer),
+        kCornerRadius));
     label_->SetFontList(label_->font_list().Derive(0, gfx::Font::NORMAL,
                                                    gfx::Font::Weight::BOLD));
     parent_->OnSelectionChanged();
   } else {
-    if (features::IsChromeRefresh2023()) {
-      SetBackground(nullptr);
-    } else {
-      image_view_->SetBackground(nullptr);
-    }
+    SetBackground(nullptr);
     label_->SetFontList(label_->font_list().Derive(0, gfx::Font::NORMAL,
                                                    gfx::Font::Weight::NORMAL));
   }
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
index b60c6707..b899d42 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
@@ -18,7 +18,6 @@
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
@@ -200,17 +199,13 @@
 
 std::unique_ptr<views::ScrollView> CreateScrollViewWithTable(
     std::unique_ptr<views::TableView> table) {
-  if (features::IsChromeRefresh2023()) {
-    auto scroll_view = std::make_unique<views::ScrollView>(
-        views::ScrollView::ScrollWithLayers::kEnabled);
-    scroll_view->SetDrawOverflowIndicator(false);
-    scroll_view->SetViewportRoundedCornerRadius(gfx::RoundedCornersF(8));
-    scroll_view->SetContents(std::move(table));
-    scroll_view->SetBorder(nullptr);
-    return scroll_view;
-  } else {
-    return views::TableView::CreateScrollViewWithTable(std::move(table));
-  }
+  auto scroll_view = std::make_unique<views::ScrollView>(
+      views::ScrollView::ScrollWithLayers::kEnabled);
+  scroll_view->SetDrawOverflowIndicator(false);
+  scroll_view->SetViewportRoundedCornerRadius(gfx::RoundedCornersF(8));
+  scroll_view->SetContents(std::move(table));
+  scroll_view->SetBorder(nullptr);
+  return scroll_view;
 }
 
 }  // namespace
@@ -352,33 +347,17 @@
   DesktopMediaListController::ListView::OnThemeChanged();
 
   const ui::ColorProvider* const color_provider = GetColorProvider();
-  if (features::IsChromeRefresh2023()) {
-    table_->SetBorder(nullptr);
-  } else {
-    table_->SetBorder(views::CreateSolidBorder(
-        /*thickness=*/1,
-        color_provider->GetColor(kColorDesktopMediaTabListBorder)));
-  }
+  table_->SetBorder(nullptr);
 
-  if (features::IsChromeRefresh2023()) {
-    scroll_view_->SetBackground(views::CreateRoundedRectBackground(
-        GetColorProvider()->GetColor(ui::kColorSysSurface4), 8));
-    const SkColor background_color =
-        color_provider->GetColor(ui::kColorSysTonalContainer);
-    preview_wrapper_->SetBackground(
-        views::CreateRoundedRectBackground(background_color, 8));
-    empty_preview_label_->SetBackground(
-        views::CreateRoundedRectBackground(background_color, 8));
-    empty_preview_label_->SetBackgroundColor(background_color);
-  } else {
-    const SkColor background_color =
-        color_provider->GetColor(kColorDesktopMediaTabListPreviewBackground);
-    preview_wrapper_->SetBackground(
-        views::CreateSolidBackground(background_color));
-    empty_preview_label_->SetBackground(
-        views::CreateSolidBackground(background_color));
-    empty_preview_label_->SetBackgroundColor(background_color);
-  }
+  scroll_view_->SetBackground(views::CreateRoundedRectBackground(
+      GetColorProvider()->GetColor(ui::kColorSysSurface4), 8));
+  const SkColor background_color =
+      color_provider->GetColor(ui::kColorSysTonalContainer);
+  preview_wrapper_->SetBackground(
+      views::CreateRoundedRectBackground(background_color, 8));
+  empty_preview_label_->SetBackground(
+      views::CreateRoundedRectBackground(background_color, 8));
+  empty_preview_label_->SetBackgroundColor(background_color);
 }
 
 std::optional<content::DesktopMediaID> DesktopMediaTabList::GetSelection() {
diff --git a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
index 45de6e08..fb6514a3 100644
--- a/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/share_this_tab_dialog_views.cc
@@ -172,19 +172,14 @@
     CreateDialogWidget(this, params.context, nullptr)->Show();
   }
 
-  source_view_->SetBorder(features::IsChromeRefresh2023()
-                              ? views::CreateThemedRoundedRectBorder(
-                                    1, 4, ui::kColorSysPrimaryContainer)
-                              : views::CreateThemedRoundedRectBorder(
-                                    1, 2, kColorShareThisTabSourceViewBorder));
+  source_view_->SetBorder(views::CreateThemedRoundedRectBorder(
+      1, 4, ui::kColorSysPrimaryContainer));
 
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetStringUTF16(IDS_SHARE_THIS_TAB_DIALOG_ALLOW));
   SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
-  if (features::IsChromeRefresh2023()) {
-    SetButtonStyle(ui::DIALOG_BUTTON_OK, ui::ButtonStyle::kTonal);
-    SetButtonStyle(ui::DIALOG_BUTTON_CANCEL, ui::ButtonStyle::kTonal);
-  }
+  SetButtonStyle(ui::DIALOG_BUTTON_OK, ui::ButtonStyle::kTonal);
+  SetButtonStyle(ui::DIALOG_BUTTON_CANCEL, ui::ButtonStyle::kTonal);
 
   // Simply pressing ENTER without tab-key navigating to the button
   // must not accept the dialog, or else that'd be a security issue.
@@ -272,10 +267,7 @@
   audio_toggle_container->SetProperty(views::kMarginsKey,
                                       gfx::Insets::TLBR(8, 0, 0, 0));
   audio_toggle_container->SetBackground(
-      features::IsChromeRefresh2023()
-          ? views::CreateThemedRoundedRectBackground(ui::kColorSysSurface4, 8)
-          : views::CreateThemedRoundedRectBackground(
-                kColorShareThisTabAudioToggleBackground, 4));
+      views::CreateThemedRoundedRectBackground(ui::kColorSysSurface4, 8));
 
   views::ImageView* audio_icon_view = audio_toggle_container->AddChildView(
       std::make_unique<views::ImageView>());
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_icon_view.cc b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_icon_view.cc
index c604017..4416e0f 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_icon_view.cc
@@ -253,14 +253,17 @@
 }
 
 void CookieControlsIconView::ShowCookieControlsBubble() {
-  bubble_coordinator_->ShowBubble(
-      delegate()->GetWebContentsForPageActionIconView(), controller_.get());
   CHECK(browser_->window());
-  CHECK(ShouldBeVisible());
+  // Need to close IPH before opening bubble view, as on some platforms closing
+  // the IPH bubble can cause activation to move between windows, and cookie
+  // control bubble is close-on-deactivate.
   browser_->window()->CloseFeaturePromo(
       feature_engagement::kIPHCookieControlsFeature);
   browser_->window()->NotifyFeatureEngagementEvent(
       feature_engagement::events::kCookieControlsBubbleShown);
+  bubble_coordinator_->ShowBubble(
+      delegate()->GetWebContentsForPageActionIconView(), controller_.get());
+  CHECK(ShouldBeVisible());
   RecordOpenedAction(icon_visible_, protections_on_);
   if (did_animate_) {
     base::RecordAction(base::UserMetricsAction(
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_interactive_uitest.cc
index 07c74ab3..68a90c8d 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_interactive_uitest.cc
+++ b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_interactive_uitest.cc
@@ -443,7 +443,7 @@
       // Check that IPH shows, then open cookie controls bubble via icon.
       InAnyContext(WaitForShow(
           user_education::HelpBubbleView::kHelpBubbleElementIdForTesting)),
-      PressButton(kCookieControlsIconElementId),
+      FlushEvents(), PressButton(kCookieControlsIconElementId),
       // Cookie controls bubble should show and IPH should close.
       InAnyContext(
           WaitForShow(CookieControlsBubbleView::kCookieControlsBubble)),
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.cc b/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.cc
index 1b95c6d4..12690ec 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.cc
+++ b/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.cc
@@ -9,11 +9,14 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_dismissal_source.h"
 #include "chrome/browser/ui/lens/lens_untrusted_ui.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/file_select_listener.h"
+#include "content/public/common/input/native_web_keyboard_event.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 
 using SidePanelWebUIViewT_LensUntrustedUI =
@@ -22,6 +25,26 @@
                         SidePanelWebUIViewT)
 END_METADATA
 
+namespace {
+
+bool IsEscapeEvent(const content::NativeWebKeyboardEvent& event) {
+  return event.GetType() ==
+             content::NativeWebKeyboardEvent::Type::kRawKeyDown &&
+         event.windows_key_code == ui::VKEY_ESCAPE;
+}
+
+Browser* BrowserFromWebContents(content::WebContents* web_contents) {
+  BrowserWindow* window =
+      BrowserWindow::FindBrowserWindowWithWebContents(web_contents);
+  auto* browser_view = static_cast<BrowserView*>(window);
+  if (browser_view) {
+    return browser_view->browser();
+  }
+  return nullptr;
+}
+
+}  // namespace
+
 LensOverlaySidePanelWebView::LensOverlaySidePanelWebView(Profile* profile)
     : SidePanelWebUIViewT(
           base::RepeatingClosure(),
@@ -44,16 +67,36 @@
     const content::OpenURLParams& params,
     base::OnceCallback<void(content::NavigationHandle&)>
         navigation_handle_callback) {
-  BrowserWindow* window =
-      BrowserWindow::FindBrowserWindowWithWebContents(web_contents());
-  auto* browser_view = static_cast<BrowserView*>(window);
-  if (browser_view && browser_view->browser()) {
-    browser_view->browser()->OpenURL(params,
-                                     std::move(navigation_handle_callback));
+  Browser* browser = BrowserFromWebContents(web_contents());
+  if (browser) {
+    browser->OpenURL(params, std::move(navigation_handle_callback));
   }
   return nullptr;
 }
 
+bool LensOverlaySidePanelWebView::HandleKeyboardEvent(
+    content::WebContents* source,
+    const content::NativeWebKeyboardEvent& event) {
+  if (IsEscapeEvent(event)) {
+    Browser* browser = BrowserFromWebContents(web_contents());
+    if (browser) {
+      content::WebContents* tab_web_contents =
+          browser->tab_strip_model()->GetActiveWebContents();
+      LensOverlayController* controller =
+          LensOverlayController::GetController(tab_web_contents);
+      DCHECK(controller);
+
+      if (controller->IsOverlayShowing()) {
+        controller->CloseUIAsync(
+            lens::LensOverlayDismissalSource::kEscapeKeyPress);
+        return true;
+      }
+    }
+  }
+  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
+      event, GetFocusManager());
+}
+
 LensOverlaySidePanelWebView::~LensOverlaySidePanelWebView() = default;
 
 BEGIN_METADATA(LensOverlaySidePanelWebView)
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.h b/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.h
index dca39ee..e4eb80d3 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.h
+++ b/chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_web_view.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper.h"
 #include "content/public/browser/file_select_listener.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 #include "ui/views/controls/webview/webview.h"
 
 class Profile;
@@ -41,8 +42,15 @@
           navigation_handle_callback) override;
   bool HandleContextMenu(content::RenderFrameHost& render_frame_host,
                          const content::ContextMenuParams& params) override;
+  bool HandleKeyboardEvent(
+      content::WebContents* source,
+      const content::NativeWebKeyboardEvent& event) override;
 
  private:
+  // A handler to handle unhandled keyboard messages coming back from the
+  // renderer process.
+  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
+
   base::WeakPtrFactory<LensOverlaySidePanelWebView> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 6737e17..cc6f387 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -57,6 +57,7 @@
 #include "components/feature_engagement/public/tracker.h"
 #include "components/omnibox/browser/autocomplete_classifier.h"
 #include "components/omnibox/browser/autocomplete_match.h"
+#include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/saved_tab_groups/features.h"
 #include "components/tab_groups/tab_group_color.h"
 #include "components/tab_groups/tab_group_id.h"
@@ -199,6 +200,15 @@
     menu_model_factory_ = std::make_unique<TabMenuModelFactory>();
   }
   model_->SetTabStripUI(this);
+
+  should_show_discard_indicator_ = g_browser_process->local_state()->GetBoolean(
+      performance_manager::user_tuning::prefs::kDiscardRingTreatmentEnabled);
+  local_state_registrar_.Init(g_browser_process->local_state());
+  local_state_registrar_.Add(
+      performance_manager::user_tuning::prefs::kDiscardRingTreatmentEnabled,
+      base::BindRepeating(
+          &BrowserTabStripController::OnDiscardRingTreatmentEnabledChanged,
+          base::Unretained(this)));
 }
 
 BrowserTabStripController::~BrowserTabStripController() {
@@ -805,6 +815,9 @@
 
   tabstrip_->AddTabAt(index, TabRendererData::FromTabInModel(model_, index));
 
+  tabstrip_->tab_at(index)->SetShouldShowDiscardIndicator(
+      should_show_discard_indicator_);
+
   // Try to show tab search IPH if needed.
   constexpr int kTabSearchIPHTriggerThreshold = 8;
   if (tabstrip_->GetTabCount() >= kTabSearchIPHTriggerThreshold) {
@@ -812,3 +825,12 @@
         feature_engagement::kIPHTabSearchFeature);
   }
 }
+
+void BrowserTabStripController::OnDiscardRingTreatmentEnabledChanged() {
+  should_show_discard_indicator_ = g_browser_process->local_state()->GetBoolean(
+      performance_manager::user_tuning::prefs::kDiscardRingTreatmentEnabled);
+  for (int tab_index = 0; tab_index < tabstrip_->GetTabCount(); ++tab_index) {
+    tabstrip_->tab_at(tab_index)->SetShouldShowDiscardIndicator(
+        should_show_discard_indicator_);
+  }
+}
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index af30168..46ef73c 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -158,6 +158,8 @@
   // Adds a tab.
   void AddTab(content::WebContents* contents, int index);
 
+  void OnDiscardRingTreatmentEnabledChanged();
+
   raw_ptr<TabStripModel> model_;
 
   raw_ptr<TabStrip> tabstrip_;
@@ -175,9 +177,11 @@
   // tabs.
   std::unique_ptr<ImmersiveRevealedLock> immersive_reveal_lock_;
 
-  PrefChangeRegistrar local_pref_registrar_;
+  PrefChangeRegistrar local_state_registrar_;
 
   std::unique_ptr<TabMenuModelFactory> menu_model_factory_;
+
+  bool should_show_discard_indicator_ = true;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_BROWSER_TAB_STRIP_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 660bde8..07f93f0a 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -998,6 +998,10 @@
   return alert_states[0];
 }
 
+void Tab::SetShouldShowDiscardIndicator(bool enabled) {
+  icon_->SetShouldShowDiscardIndicator(enabled);
+}
+
 void Tab::MaybeAdjustLeftForPinnedTab(gfx::Rect* bounds,
                                       int visual_width) const {
   if (ShouldRenderAsNormalTab()) {
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 7a7cfdf9..c5bf5d6 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -191,6 +191,8 @@
     return alert_indicator_button_;
   }
 
+  void SetShouldShowDiscardIndicator(bool enabled);
+
  private:
   class TabCloseButtonObserver;
   friend class AlertIndicatorButtonTest;
diff --git a/chrome/browser/ui/views/tabs/tab_icon.cc b/chrome/browser/ui/views/tabs/tab_icon.cc
index fc97943..9ebdb23 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.cc
+++ b/chrome/browser/ui/views/tabs/tab_icon.cc
@@ -11,6 +11,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "base/trace_event/trace_event.h"
 #include "cc/paint/paint_flags.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
@@ -23,6 +24,7 @@
 #include "chrome/common/webui_url_constants.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/grit/components_scaled_resources.h"
+#include "components/performance_manager/public/user_tuning/prefs.h"
 #include "content/public/common/url_constants.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -240,6 +242,24 @@
   increased_discard_indicator_radius_ = radius;
 }
 
+void TabIcon::SetShouldShowDiscardIndicator(bool enabled) {
+  should_show_discard_indicator_ = enabled;
+  bool show_discard_indicator = is_discarded_ && should_show_discard_indicator_;
+  if (was_discard_indicator_shown_ != show_discard_indicator) {
+    was_discard_indicator_shown_ = show_discard_indicator;
+
+    // Directly set animations to their end states and do not animate.
+    if (show_discard_indicator) {
+      tab_discard_animation_.SetCurrentValue(1);
+      favicon_size_animation_.Reset(0);
+    } else {
+      tab_discard_animation_.SetCurrentValue(0);
+      favicon_size_animation_.Reset(1);
+    }
+    SchedulePaint();
+  }
+}
+
 void TabIcon::OnPaint(gfx::Canvas* canvas) {
   // Compute the bounds adjusted for the hiding fraction.
   gfx::Rect contents_bounds = GetContentsBounds();
@@ -521,10 +541,12 @@
   UpdateThemedFavicon();
 }
 
-void TabIcon::SetDiscarded(bool should_show_discard_status) {
-  if (was_discard_indicator_shown_ != should_show_discard_status) {
-    was_discard_indicator_shown_ = should_show_discard_status;
-    if (should_show_discard_status) {
+void TabIcon::SetDiscarded(bool discarded) {
+  is_discarded_ = discarded;
+  bool show_discard_indicator = is_discarded_ && should_show_discard_indicator_;
+  if (was_discard_indicator_shown_ != show_discard_indicator) {
+    was_discard_indicator_shown_ = show_discard_indicator;
+    if (show_discard_indicator) {
       tab_discard_animation_.Start();
       favicon_size_animation_.Hide();
 
diff --git a/chrome/browser/ui/views/tabs/tab_icon.h b/chrome/browser/ui/views/tabs/tab_icon.h
index caca980..1843712 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.h
+++ b/chrome/browser/ui/views/tabs/tab_icon.h
@@ -83,6 +83,7 @@
   bool GetActiveStateForTesting() { return is_active_tab_; }
 
   void EnlargeDiscardIndicatorRadius(int radius);
+  void SetShouldShowDiscardIndicator(bool enabled);
 
  private:
   class CrashAnimation;
@@ -191,6 +192,13 @@
   // fade out
   gfx::LinearAnimation tab_discard_animation_;
 
+  // The discard indicator will be shown only if the tab is discarded and the
+  // discard ring treatment pref is enabled. Keep track of both of the component
+  // booleans, in order to determine if the discard indicator is shown/unshown
+  // due to a change in the discard status or a change to the pref, because
+  // we don't want to animate the discard ring in the latter case.
+  bool is_discarded_ = false;
+  bool should_show_discard_indicator_ = true;
   bool was_discard_indicator_shown_ = false;
 
   // Crash animation (in place of favicon). Lazily created since most of the
diff --git a/chrome/browser/ui/views/user_education/feature_promo_lifecycle_interactive_uitest.cc b/chrome/browser/ui/views/user_education/feature_promo_lifecycle_interactive_uitest.cc
index 9b302d7..ef793333 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_lifecycle_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_lifecycle_interactive_uitest.cc
@@ -41,6 +41,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/event_modifiers.h"
 #include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/views/interaction/widget_focus_observer.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
@@ -438,8 +439,22 @@
 IN_PROC_BROWSER_TEST_F(FeaturePromoLifecycleUiTest,
                        PressingEscRecordsHistogram) {
   const ui::Accelerator kEsc(ui::VKEY_ESCAPE, ui::MODIFIER_NONE);
+  views::Widget* bubble_widget = nullptr;
   RunTestSequence(
       ShowPromoRecordingTime(kFeaturePromoLifecycleTestPromo),
+      // Ensure that the bubble is active before trying to send an accelerator;
+      // widgets cannot accept accelerators before they become active.
+      // TODO(dfried): need to create a common WaitForActivation() verb.
+      WithView(user_education::HelpBubbleView::kHelpBubbleElementIdForTesting,
+               [&bubble_widget](views::View* view) {
+                 bubble_widget = view->GetWidget();
+               }),
+      If([&bubble_widget]() { return !bubble_widget->IsActive(); },
+         Steps(ObserveState(views::test::kCurrentWidgetFocus),
+               WaitForState(views::test::kCurrentWidgetFocus,
+                            [&bubble_widget]() {
+                              return bubble_widget->GetNativeView();
+                            }))),
       SendAccelerator(
           user_education::HelpBubbleView::kHelpBubbleElementIdForTesting, kEsc),
       WaitForHide(
diff --git a/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
index 6b0ebc4b..b8030fd5 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
@@ -128,10 +128,11 @@
       ShowHelpBubble(kTabGroupEditorBubbleId, std::move(params)),
       // Activate the help bubble. This should not cause the editor to close.
       ActivateSurface(HelpBubbleView::kHelpBubbleElementIdForTesting),
-      // Re-Activate the dialog.
-      ActivateSurface(kTabGroupEditorBubbleId),
       // Close the help bubble.
       CloseHelpBubble(),
+      // Re-Activate the dialog. It may or may not receive activation when the
+      // help bubble closes.
+      ActivateSurface(kTabGroupEditorBubbleId),
       // Now that the help bubble is gone, locate the editor again and transfer
       // activation to its primary window widget (the browser window) - this
       // should close the editor as it is no longer pinned by the help bubble.
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index 61554ef..9ca13c6e 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -51,6 +51,7 @@
       "//ash/webui/os_feedback_ui",
       "//ash/webui/print_management",
       "//ash/webui/print_preview_cros",
+      "//ash/webui/sanitize_ui",
       "//ash/webui/scanning",
       "//ash/webui/shimless_rma",
       "//ash/webui/shortcut_customization_ui",
diff --git a/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
index d66ab17..362f4a2 100644
--- a/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
+++ b/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
@@ -33,6 +33,7 @@
 #include "ash/webui/personalization_app/personalization_app_ui.h"
 #include "ash/webui/print_management/print_management_ui.h"
 #include "ash/webui/print_preview_cros/print_preview_cros_ui.h"
+#include "ash/webui/sanitize_ui/sanitize_ui.h"
 #include "ash/webui/scanning/scanning_ui.h"
 #include "ash/webui/shimless_rma/shimless_rma.h"
 #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
@@ -203,6 +204,15 @@
   return std::make_unique<eche_app::EcheAppUIConfig>(create_controller_func);
 }
 
+std::unique_ptr<content::WebUIConfig> MakeSanitizeUIConfig() {
+  CreateWebUIControllerFunc create_controller_func = base::BindRepeating(
+      [](content::WebUI* web_ui,
+         const GURL& url) -> std::unique_ptr<content::WebUIController> {
+        return std::make_unique<SanitizeDialogUI>(web_ui);
+      });
+  return std::make_unique<SanitizeDialogUIConfig>(create_controller_func);
+}
+
 void RegisterAshChromeWebUIConfigs() {
   // Add `WebUIConfig`s for Ash ChromeOS to the list here.
   //
@@ -296,6 +306,7 @@
       std::make_unique<printing::print_preview::PrintPreviewCrosUIConfig>());
   map.AddWebUIConfig(std::make_unique<multidevice::ProximityAuthUIConfig>());
   map.AddWebUIConfig(std::make_unique<RemoteMaintenanceCurtainUIConfig>());
+  map.AddWebUIConfig(MakeSanitizeUIConfig());
   map.AddWebUIConfig(
       MakeComponentConfigWithDelegate<ScanningUIConfig, ScanningUI,
                                       ChromeScanningAppDelegate>());
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 17c9955..6a0c524 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
@@ -418,6 +418,8 @@
        IDS_SETTINGS_APP_PARENTAL_CONTROLS_CONFIRM_PIN_TITLE},
       {"appParentalControlsForgotPinLinkName",
        IDS_SETTINGS_APP_PARENTAL_CONTROLS_FORGOT_PIN_LINK_NAME},
+      {"appParentalControlsPinMismatchErrorText",
+       IDS_SETTINGS_APP_PARENTAL_CONTROLS_PIN_MISMATCH_ERROR_TEXT},
       {"appParentalControlsTitle", IDS_OS_SETTINGS_APP_PARENTAL_CONTROLS_LABEL},
       {"appParentalControlsSubtitle",
        IDS_OS_SETTINGS_APP_PARENTAL_CONTROLS_SUBLABEL},
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index c3064ce..8b7ba4e 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -1072,6 +1072,7 @@
     GURL(chrome::kChromeUIOSSettingsURL),
     GURL(chrome::kChromeUIPowerUrl),
     GURL(chrome::kChromeUIPrintManagementUrl),
+    GURL(chrome::kChromeUISanitizeAppURL),
     GURL(chrome::kChromeUIScanningAppURL),
     GURL(chrome::kChromeUISensorInfoURL),
     GURL(chrome::kChromeUISetTimeURL),
diff --git a/chrome/browser/ui/webui/commerce/product_specifications_ui.cc b/chrome/browser/ui/webui/commerce/product_specifications_ui.cc
index a252fff..0c57e2f 100644
--- a/chrome/browser/ui/webui/commerce/product_specifications_ui.cc
+++ b/chrome/browser/ui/webui/commerce/product_specifications_ui.cc
@@ -68,6 +68,7 @@
       {"openTabs", IDS_PRODUCT_SPECIFICATIONS_OPEN_TABS_SECTION},
       {"recentlyViewedTabs",
        IDS_PRODUCT_SPECIFICATIONS_RECENTLY_VIEWED_TABS_SECTION},
+      {"removeColumn", IDS_PRODUCT_SPECIFICATIONS_REMOVE_COLUMN},
       {"renameGroup", IDS_PRODUCT_SPECIFICATIONS_RENAME_GROUP},
       {"seeAll", IDS_PRODUCT_SPECIFICATIONS_SEE_ALL},
   };
diff --git a/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc b/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
index 54ad810..6239981a 100644
--- a/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_list_tracker.cc
@@ -468,11 +468,6 @@
   file_value->has_safe_browsing_verdict =
       WasSafeBrowsingVerdictObtained(download_item);
 
-  if (download_model.IsDangerous()) {
-    base::UmaHistogramBoolean(
-        "Download.DownloadsPageDangerousWarningWasShownBefore",
-        download_model.WasUIWarningShown());
-  }
   MaybeRecordDangerousDownloadWarningShown(download_model);
 
   if (download_item->IsDangerous()) {
diff --git a/chrome/browser/ui/webui/downloads/downloads_ui.cc b/chrome/browser/ui/webui/downloads/downloads_ui.cc
index a33d79d..2162bcb 100644
--- a/chrome/browser/ui/webui/downloads/downloads_ui.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_ui.cc
@@ -134,11 +134,15 @@
       {"controlOpenAnyway", IDS_OPEN_DOWNLOAD_ANYWAY},
       {"toastClearedAll", IDS_DOWNLOAD_TOAST_CLEARED_ALL},
       {"toastRemovedFromList", IDS_DOWNLOAD_TOAST_REMOVED_FROM_LIST},
+      {"toastDeletedFromHistoryStillOnDevice",
+       IDS_DOWNLOADS_TOAST_DELETED_FROM_HISTORY_STILL_ON_DEVICE},
+      {"toastDeletedFromHistory", IDS_DOWNLOADS_TOAST_DELETED_FROM_HISTORY},
       {"undo", IDS_DOWNLOAD_UNDO},
       {"controlKeepDangerous", IDS_DOWNLOAD_KEEP_DANGEROUS_FILE},
       {"controlKeepSuspicious", IDS_DOWNLOAD_KEEP_SUSPICIOUS_FILE},
       {"controlKeepUnverified", IDS_DOWNLOAD_KEEP_UNVERIFIED_FILE},
       {"controlKeepInsecure", IDS_DOWNLOAD_KEEP_INSECURE_FILE},
+      {"controlDeleteFromHistory", IDS_DOWNLOAD_DELETE_FROM_HISTORY},
 
       // Accessible labels for file icons.
       {"accessibleLabelDangerous",
@@ -172,6 +176,9 @@
       {"esbDownloadRowPromoString", IDS_DOWNLOAD_ROW_ESB_PROMOTION},
       {"esbDownloadRowPromoA11y", IDS_DOWNLOAD_ROW_ESB_PROMO_A11Y},
 #endif
+      // Dangerous File
+      {"noSafeBrowsingDesc",
+       IDS_BLOCK_DOWNLOAD_REASON_UNVERIFIED_NO_SAFE_BROWSING},
   };
   source->AddLocalizedStrings(kStrings);
 
@@ -221,16 +228,6 @@
                              improved_download_warnings_ux
                                  ? IDS_BLOCK_DOWNLOAD_REASON_INSECURE
                                  : IDS_BLOCK_REASON_INSECURE_DOWNLOAD);
-  source->AddLocalizedString(
-      "noSafeBrowsingDesc",
-      IDS_BLOCK_DOWNLOAD_REASON_UNVERIFIED_NO_SAFE_BROWSING);
-  source->AddLocalizedString("controlDeleteFromHistory",
-                             IDS_DOWNLOAD_DELETE_FROM_HISTORY);
-  source->AddLocalizedString(
-      "toastDeletedFromHistoryStillOnDevice",
-      IDS_DOWNLOADS_TOAST_DELETED_FROM_HISTORY_STILL_ON_DEVICE);
-  source->AddLocalizedString("toastDeletedFromHistory",
-                             IDS_DOWNLOADS_TOAST_DELETED_FROM_HISTORY);
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   // Download Row ESB Promo:
diff --git a/chrome/browser/ui/webui/settings/performance_handler.cc b/chrome/browser/ui/webui/settings/performance_handler.cc
index b3739d4..c361130 100644
--- a/chrome/browser/ui/webui/settings/performance_handler.cc
+++ b/chrome/browser/ui/webui/settings/performance_handler.cc
@@ -47,11 +47,6 @@
       base::BindRepeating(&PerformanceHandler::HandleOpenSpeedFeedbackDialog,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
-      "onDiscardRingTreatmentEnabledChanged",
-      base::BindRepeating(
-          &PerformanceHandler::HandleSetDiscardRingTreatmentEnabled,
-          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
       "validateTabDiscardExceptionRule",
       base::BindRepeating(
           &PerformanceHandler::HandleValidateTabDiscardExceptionRule,
@@ -155,21 +150,6 @@
                            unused, unused, category_tag, unused);
 }
 
-void PerformanceHandler::HandleSetDiscardRingTreatmentEnabled(
-    const base::Value::List& args) {
-  for (Browser* browser : *BrowserList::GetInstance()) {
-    TabStripModel* tab_strip_model = browser->tab_strip_model();
-    TabStrip* tab_strip =
-        BrowserView::GetBrowserViewForBrowser(browser)->tabstrip();
-
-    for (int tab_index = 0; tab_index < tab_strip_model->count(); ++tab_index) {
-      TabRendererData tab_data =
-          TabRendererData::FromTabInModel(tab_strip_model, tab_index);
-      tab_strip->SetTabData(tab_index, tab_data);
-    }
-  }
-}
-
 void PerformanceHandler::HandleValidateTabDiscardExceptionRule(
     const base::Value::List& args) {
   CHECK_EQ(2U, args.size());
diff --git a/chrome/browser/ui/webui/settings/performance_handler.h b/chrome/browser/ui/webui/settings/performance_handler.h
index f7db51f9..d64af3e 100644
--- a/chrome/browser/ui/webui/settings/performance_handler.h
+++ b/chrome/browser/ui/webui/settings/performance_handler.h
@@ -54,7 +54,6 @@
   void HandleOpenMemorySaverFeedbackDialog(const base::Value::List& args);
   void HandleOpenSpeedFeedbackDialog(const base::Value::List& args);
   void HandleOpenFeedbackDialog(const std::string category_tag);
-  void HandleSetDiscardRingTreatmentEnabled(const base::Value::List& args);
   void HandleValidateTabDiscardExceptionRule(const base::Value::List& args);
 };
 
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index effe1ea9..2898ada 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1716198910-7302f5984cf26e384d93e0ba7cac1e687a2f3214-949984c85d55f282b8c4481e6521278779bc834b.profdata
+chrome-android64-main-1716220190-2c2a5c796ffdc7b4a581384157167f4f7fab8cdf-678d8f10d39d33e2e735073ce1601c09c8a8bd15.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 7533bcfe..9745d35 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1716206072-48d789455a742d3d2e83fa237677da00074a3ea9-867547ce2c98b7406af3c55b71159380f29396ff.profdata
+chrome-linux-main-1716227899-0d1c76fe8e7ff6a9c05f6fd9f90cd5de8ab61b73-bf4aa26514a0038ceeb4b7a7d5b8c4fa06818c41.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index d5c7684..3508422 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1716206072-7219c4e08bddc0fd39739921ec25dfd4b5ae95e7-867547ce2c98b7406af3c55b71159380f29396ff.profdata
+chrome-win-arm64-main-1716227899-a68315bb1cb209581be07e6c4093562aa63685c7-bf4aa26514a0038ceeb4b7a7d5b8c4fa06818c41.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 4083d039..a0e6475 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1716206072-127069e7622c3a5304876e728addd92257e09c71-867547ce2c98b7406af3c55b71159380f29396ff.profdata
+chrome-win32-main-1716227899-54f9bf9c31c7f04e926f3c3841ddaf2d3cfecc9c-bf4aa26514a0038ceeb4b7a7d5b8c4fa06818c41.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index ceb1e5e..b050d0d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1716206072-926e9a255408f922e88a7d2c9a37b36963a17ecc-867547ce2c98b7406af3c55b71159380f29396ff.profdata
+chrome-win64-main-1716227899-626552aa2f4c2428da68fcfe649ad94790346a6a-bf4aa26514a0038ceeb4b7a7d5b8c4fa06818c41.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 81bbfbf..64d0d048 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -255,6 +255,7 @@
         "$root_gen_dir/ash/webui/ash_projector_annotator_untrusted_resources.pak",
         "$root_gen_dir/ash/webui/ash_projector_app_untrusted_resources.pak",
         "$root_gen_dir/ash/webui/ash_projector_common_resources.pak",
+        "$root_gen_dir/ash/webui/ash_sanitize_app_resources.pak",
         "$root_gen_dir/ash/webui/ash_scanning_app_resources.pak",
         "$root_gen_dir/ash/webui/ash_shimless_rma_resources.pak",
         "$root_gen_dir/ash/webui/ash_shortcut_customization_app_resources.pak",
@@ -349,6 +350,7 @@
         "//ash/webui/resources:projector_app_bundle_resources",
         "//ash/webui/resources:projector_app_untrusted_resources",
         "//ash/webui/resources:projector_common_resources",
+        "//ash/webui/sanitize_ui/resources:resources",
         "//ash/webui/scanning/resources:resources",
         "//ash/webui/shimless_rma/resources:resources",
         "//ash/webui/shortcut_customization_ui/resources:resources",
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 3f61499..3bf43d72 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -412,6 +412,7 @@
 const char kChromeUIPowerHost[] = "power";
 const char kChromeUIPowerUrl[] = "chrome://power";
 const char kChromeUIRemoteManagementCurtainHost[] = "security-curtain";
+const char kChromeUISanitizeAppURL[] = "chrome://sanitize";
 const char kChromeUIScanningAppURL[] = "chrome://scanning";
 const char kChromeUISetTimeHost[] = "set-time";
 const char kChromeUISetTimeURL[] = "chrome://set-time/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 56ed503..86dfd75c 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -377,6 +377,7 @@
 extern const char kChromeUIPowerHost[];
 extern const char kChromeUIPowerUrl[];
 extern const char kChromeUIRemoteManagementCurtainHost[];
+extern const char kChromeUISanitizeAppURL[];
 extern const char kChromeUIScanningAppURL[];
 extern const char kChromeUISetTimeHost[];
 extern const char kChromeUISetTimeURL[];
diff --git a/chrome/services/sharing/nearby/platform/BUILD.gn b/chrome/services/sharing/nearby/platform/BUILD.gn
index fae85f9..9d5c3d5 100644
--- a/chrome/services/sharing/nearby/platform/BUILD.gn
+++ b/chrome/services/sharing/nearby/platform/BUILD.gn
@@ -70,6 +70,8 @@
     "webrtc.h",
     "wifi_direct_medium.cc",
     "wifi_direct_medium.h",
+    "wifi_direct_server_socket.cc",
+    "wifi_direct_server_socket.h",
     "wifi_lan_medium.cc",
     "wifi_lan_medium.h",
     "wifi_lan_server_socket.cc",
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
index 70801d0..14ee75d 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
@@ -123,12 +123,29 @@
 
 namespace nearby::chrome {
 
+std::unique_ptr<BleV2GattClient::GattService>
+BleV2GattClient::GattService::Factory::Create() {
+  return base::WrapUnique(new BleV2GattClient::GattService());
+}
+
+BleV2GattClient::GattService::Factory::~Factory() = default;
+
+BleV2GattClient::GattService::GattService() = default;
+BleV2GattClient::GattService::~GattService() = default;
+
 BleV2GattClient::BleV2GattClient(
-    mojo::PendingRemote<bluetooth::mojom::Device> device)
+    mojo::PendingRemote<bluetooth::mojom::Device> device,
+    std::unique_ptr<GattService::Factory> gatt_service_factory)
     : task_runner_(
           base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
+      gatt_service_factory_(std::move(gatt_service_factory)),
       remote_device_(std::move(device), /*bind_task_runner=*/task_runner_) {
   CHECK(remote_device_.is_bound());
+
+  remote_device_.set_disconnect_handler(
+      base::BindOnce(&BleV2GattClient::OnMojoDisconnect,
+                     weak_ptr_factory_.GetWeakPtr()),
+      task_runner_);
 }
 
 BleV2GattClient::~BleV2GattClient() {
@@ -288,10 +305,6 @@
   remote_device_->Disconnect();
 }
 
-BleV2GattClient::GattService::GattService() = default;
-
-BleV2GattClient::GattService::~GattService() = default;
-
 void BleV2GattClient::DoDiscoverServices(
     base::WaitableEvent* discover_services_waitable_event) {
   CHECK(task_runner_->RunsTasksInCurrentSequence());
@@ -323,7 +336,7 @@
   for (const auto& service : services) {
     Uuid nearby_service_uuid = BluetoothUuidToNearbyUuid(service->uuid);
     uuid_to_discovered_gatt_service_map_.insert_or_assign(
-        std::string(nearby_service_uuid), std::make_unique<GattService>());
+        std::string(nearby_service_uuid), gatt_service_factory_->Create());
     uuid_to_discovered_gatt_service_map_.at(std::string(nearby_service_uuid))
         ->service_info = service.Clone();
   }
@@ -476,11 +489,15 @@
 void BleV2GattClient::Shutdown(base::WaitableEvent* shutdown_waitable_event) {
   CHECK(task_runner_->RunsTasksInCurrentSequence());
 
-  Disconnect();
+  // The `remote_device_` might have already been reset in `OnMojoDisconnect()`.
+  if (remote_device_.is_bound()) {
+    Disconnect();
 
-  // Note that resetting the Remote will cancel any pending callbacks, including
-  // those already in the task queue.
-  remote_device_.reset();
+    // Note that resetting the Remote will cancel any pending callbacks,
+    // including those already in the task queue.
+    remote_device_.reset();
+  }
+
   uuid_to_discovered_gatt_service_map_.clear();
 
   // Cancel all pending calls. This is sequence safe because all
@@ -493,4 +510,10 @@
   shutdown_waitable_event->Signal();
 }
 
+void BleV2GattClient::OnMojoDisconnect() {
+  LOG(WARNING) << __func__ << ": Device remote unexpectedly disconnected";
+  remote_device_.reset();
+  uuid_to_discovered_gatt_service_map_.clear();
+}
+
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
index add77b3..c6b88c8 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_BLE_V2_GATT_CLIENT_H_
 #define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_BLE_V2_GATT_CLIENT_H_
 
+#include "base/memory/weak_ptr.h"
 #include "device/bluetooth/public/mojom/device.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
@@ -19,8 +20,31 @@
 
 class BleV2GattClient : public ::nearby::api::ble_v2::GattClient {
  public:
-  explicit BleV2GattClient(
-      mojo::PendingRemote<bluetooth::mojom::Device> device);
+  // Representation of the remote GattServices discovered by BleV2GattClient.
+  class GattService {
+   public:
+    class Factory {
+     public:
+      virtual std::unique_ptr<GattService> Create();
+
+      virtual ~Factory();
+    };
+
+    virtual ~GattService();
+    GattService(GattService&) = delete;
+    GattService& operator=(GattService&) = delete;
+
+    bluetooth::mojom::ServiceInfoPtr service_info;
+    std::optional<std::vector<bluetooth::mojom::CharacteristicInfoPtr>>
+        characteristics;
+
+   protected:
+    GattService();
+  };
+
+  BleV2GattClient(mojo::PendingRemote<bluetooth::mojom::Device> device,
+                  std::unique_ptr<GattService::Factory> gatt_service_factory =
+                      std::make_unique<GattService::Factory>());
   ~BleV2GattClient() override;
 
   BleV2GattClient(const BleV2GattClient&) = delete;
@@ -47,15 +71,6 @@
   void Disconnect() override;
 
  private:
-  struct GattService {
-    GattService();
-    ~GattService();
-
-    bluetooth::mojom::ServiceInfoPtr service_info;
-    std::optional<std::vector<bluetooth::mojom::CharacteristicInfoPtr>>
-        characteristics;
-  };
-
   void DoDiscoverServices(
       base::WaitableEvent* discover_services_waitable_event);
   void OnGetGattServices(
@@ -87,6 +102,7 @@
       const Uuid& characteristic_uuid);
 
   void Shutdown(base::WaitableEvent* shutdown_waitable_event);
+  void OnMojoDisconnect();
 
   bool have_gatt_services_been_discovered_ = false;
   std::map<std::string, std::unique_ptr<GattService>>
@@ -102,7 +118,11 @@
   base::flat_set<raw_ptr<base::WaitableEvent>>
       pending_read_characteristic_waitable_events_;
 
+  std::unique_ptr<GattService::Factory> gatt_service_factory_;
+
   mojo::SharedRemote<bluetooth::mojom::Device> remote_device_;
+
+  base::WeakPtrFactory<BleV2GattClient> weak_ptr_factory_{this};
 };
 
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
index c8d30c2..724b795 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
@@ -6,11 +6,13 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/task/thread_pool.h"
+#include "base/test/bind.h"
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/services/sharing/nearby/test_support/fake_device.h"
+#include "device/bluetooth/public/mojom/device.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
@@ -46,6 +48,40 @@
   return characteristic_infos;
 }
 
+class FakeGattService : public nearby::chrome::BleV2GattClient::GattService {
+ public:
+  explicit FakeGattService(base::OnceClosure on_destroyed_callback)
+      : on_destroyed_callback_(std::move(on_destroyed_callback)) {}
+
+  ~FakeGattService() override {
+    if (on_destroyed_callback_) {
+      std::move(on_destroyed_callback_).Run();
+    }
+  }
+
+ private:
+  base::OnceClosure on_destroyed_callback_;
+};
+
+class FakeGattServiceFactory
+    : public nearby::chrome::BleV2GattClient::GattService::Factory {
+ public:
+  std::unique_ptr<nearby::chrome::BleV2GattClient::GattService> Create()
+      override {
+    return std::make_unique<FakeGattService>(
+        std::move(next_fake_gatt_service_destroyed_callback_));
+  }
+
+  void SetNextFakeGattServiceDestroyedCallback(
+      base::OnceClosure on_destroyed_callback) {
+    next_fake_gatt_service_destroyed_callback_ =
+        std::move(on_destroyed_callback);
+  }
+
+ private:
+  base::OnceClosure next_fake_gatt_service_destroyed_callback_;
+};
+
 }  // namespace
 
 namespace nearby::chrome {
@@ -61,22 +97,29 @@
     auto fake_device = std::make_unique<bluetooth::FakeDevice>();
     fake_device_ = fake_device.get();
     mojo::PendingRemote<bluetooth::mojom::Device> pending_device;
-    mojo::MakeSelfOwnedReceiver(
+    device_receiver_ = mojo::MakeSelfOwnedReceiver(
         std::move(fake_device),
         pending_device.InitWithNewPipeAndPassReceiver());
-    ble_v2_gatt_client_ =
-        std::make_unique<BleV2GattClient>(std::move(pending_device));
+
+    auto fake_gatt_service_factory = std::make_unique<FakeGattServiceFactory>();
+    fake_gatt_service_factory_ = fake_gatt_service_factory.get();
+    ble_v2_gatt_client_ = std::make_unique<BleV2GattClient>(
+        std::move(pending_device), std::move(fake_gatt_service_factory));
   }
 
   void TearDown() override {
-    base::RunLoop run_loop;
-    fake_device_->set_on_disconnected_callback(run_loop.QuitClosure());
-    ble_v2_gatt_client_->Disconnect();
-    run_loop.Run();
+    // `fake_device_` might be invalided if this is run during the
+    // `DisconnectHandler()` test.
+    if (fake_device_) {
+      base::RunLoop run_loop;
+      fake_device_->set_on_disconnected_callback(run_loop.QuitClosure());
+      ble_v2_gatt_client_->Disconnect();
+      run_loop.Run();
 
-    // Need to reset `fake_device_` since it gets deleted on disconnect to avoid
-    // dangling raw_ptr.
-    fake_device_ = nullptr;
+      // Need to reset `fake_device_` since it gets deleted on disconnect to
+      // avoid dangling raw_ptr.
+      fake_device_ = nullptr;
+    }
   }
 
   void CallDiscoverServiceAndCharacteristics(
@@ -125,7 +168,9 @@
  protected:
   base::test::TaskEnvironment task_environment_;
   std::unique_ptr<BleV2GattClient> ble_v2_gatt_client_;
+  raw_ptr<FakeGattServiceFactory> fake_gatt_service_factory_;
   raw_ptr<bluetooth::FakeDevice> fake_device_;
+  mojo::SelfOwnedReceiverRef<bluetooth::mojom::Device> device_receiver_;
 };
 
 TEST_F(BleV2GattClientTest, DiscoverServiceAndCharacteristics_Success) {
@@ -268,4 +313,24 @@
   run_loop.Run();
 }
 
+TEST_F(BleV2GattClientTest, DisconnectHandler) {
+  base::RunLoop run_loop;
+  bool fake_gatt_service_destroyed = false;
+  fake_gatt_service_factory_->SetNextFakeGattServiceDestroyedCallback(
+      base::BindLambdaForTesting([&]() {
+        fake_gatt_service_destroyed = true;
+        run_loop.Quit();
+      }));
+
+  SuccessfullyDiscoverServiceAndCharacteristics(kServiceUuid1,
+                                                kCharacteristicUuid1);
+
+  // Close the Mojo pipe.
+  fake_device_ = nullptr;
+  device_receiver_->Close();
+
+  run_loop.Run();
+  EXPECT_TRUE(fake_gatt_service_destroyed);
+}
+
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_server_unittest.cc b/chrome/services/sharing/nearby/platform/ble_v2_gatt_server_unittest.cc
index eabbf185..4b86d8c 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_server_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_server_unittest.cc
@@ -32,11 +32,7 @@
     device::BluetoothUUID("00001102-0000-1000-8000-00805f9b34fc");
 const char kNewCharacteristicValue[] = "1010101";
 
-}  // namespace
-
-namespace nearby::chrome {
-
-class FakeGattService : public BleV2GattServer::GattService {
+class FakeGattService : public nearby::chrome::BleV2GattServer::GattService {
  public:
   explicit FakeGattService(base::OnceClosure on_destroyed_callback)
       : on_destroyed_callback_(std::move(on_destroyed_callback)) {}
@@ -51,9 +47,11 @@
   base::OnceClosure on_destroyed_callback_;
 };
 
-class FakeGattServiceFactory : public BleV2GattServer::GattService::Factory {
+class FakeGattServiceFactory
+    : public nearby::chrome::BleV2GattServer::GattService::Factory {
  public:
-  std::unique_ptr<BleV2GattServer::GattService> Create() override {
+  std::unique_ptr<nearby::chrome::BleV2GattServer::GattService> Create()
+      override {
     return std::make_unique<FakeGattService>(
         std::move(next_fake_gatt_service_destroyed_callback_));
   }
@@ -68,6 +66,10 @@
   base::OnceClosure next_fake_gatt_service_destroyed_callback_;
 };
 
+}  // namespace
+
+namespace nearby::chrome {
+
 class BleV2GattServerTest : public testing::Test {
  public:
   BleV2GattServerTest() = default;
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_medium.cc b/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
index 07f593e..451976c 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
@@ -518,8 +518,20 @@
 
 bool BleV2Medium::GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
                                       GetRemotePeripheralCallback callback) {
-  NOTIMPLEMENTED();
-  return false;
+  auto it =
+      std::find_if(discovered_ble_peripherals_map_.begin(),
+                   discovered_ble_peripherals_map_.end(),
+                   [&](const auto& address_device_pair) {
+                     return address_device_pair.second.GetUniqueId() == id;
+                   });
+
+  if (it == discovered_ble_peripherals_map_.end()) {
+    LOG(WARNING) << __func__ << ": no match for device at id = " << id;
+    return false;
+  }
+
+  std::move(callback)(it->second);
+  return true;
 }
 
 void BleV2Medium::PresentChanged(bool present) {
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_medium_unittest.cc b/chrome/services/sharing/nearby/platform/ble_v2_medium_unittest.cc
index 29b2b83..7318af0d 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_medium_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_medium_unittest.cc
@@ -46,6 +46,7 @@
     kTestServiceUuid2.data().size())};
 const char kServiceId[] = "TestServiceId";
 const char kCharacteristicUuid[] = "1234";
+const uint64_t kUniqueId = 1053256082272529;
 
 std::vector<uint8_t> GetByteVector(const std::string& str) {
   return std::vector<uint8_t>(str.begin(), str.end());
@@ -58,7 +59,7 @@
 
   std::string GetAddress() const override { return kDeviceAddress; }
 
-  UniqueId GetUniqueId() const override { NOTREACHED_NORETURN(); }
+  UniqueId GetUniqueId() const override { return kUniqueId; }
 };
 
 }  // namespace
@@ -192,6 +193,12 @@
                       kDeviceServiceData1ByteArray);
             found_advertisement_latch.CountDown();
             OnPeripheralDiscovered();
+
+            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
+                peripheral.GetUniqueId(),
+                [&](api::ble_v2::BlePeripheral& device) {
+                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
+                }));
           }};
 
   auto scanning_session = ble_v2_medium_->StartScanning(
@@ -236,6 +243,12 @@
               const api::ble_v2::BleAdvertisementData& advertisement_data) {
             session_1_found_advertisement_latch.CountDown();
             OnPeripheralDiscovered();
+
+            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
+                peripheral.GetUniqueId(),
+                [&](api::ble_v2::BlePeripheral& device) {
+                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
+                }));
           }};
   api::ble_v2::BleMedium::ScanningCallback scanning_callback_2 = {
       .start_scanning_result =
@@ -248,6 +261,12 @@
               const api::ble_v2::BleAdvertisementData& advertisement_data) {
             session_2_found_advertisement_latch.CountDown();
             OnPeripheralDiscovered();
+
+            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
+                peripheral.GetUniqueId(),
+                [&](api::ble_v2::BlePeripheral& device) {
+                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
+                }));
           }};
 
   auto scanning_session_1 = ble_v2_medium_->StartScanning(
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.cc b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.cc
index c5625ba3..88cea73 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.cc
@@ -4,11 +4,31 @@
 
 #include "chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h"
 
+#include "device/bluetooth/public/cpp/bluetooth_address.h"
+
+namespace {
+
+nearby::chrome::BleV2RemotePeripheral::UniqueId GenerateUniqueId(
+    const std::string& device_address) {
+  std::array<uint8_t, 6> address_bytes;
+  if (!device::ParseBluetoothAddress(device_address, address_bytes)) {
+    LOG(WARNING) << __func__ << ": failed to parse device address";
+    return 0;
+  }
+
+  uint64_t unique_id = 0;
+  std::memcpy(&unique_id, address_bytes.data(), address_bytes.size());
+  return unique_id;
+}
+
+}  // namespace
+
 namespace nearby::chrome {
 
 BleV2RemotePeripheral::BleV2RemotePeripheral(
     bluetooth::mojom::DeviceInfoPtr device_info)
-    : device_info_(std::move(device_info)) {}
+    : device_info_(std::move(device_info)),
+      unique_id_(GenerateUniqueId(device_info_->address)) {}
 
 BleV2RemotePeripheral::BleV2RemotePeripheral(BleV2RemotePeripheral&&) = default;
 
@@ -22,8 +42,7 @@
 }
 
 BleV2RemotePeripheral::UniqueId BleV2RemotePeripheral::GetUniqueId() const {
-  NOTIMPLEMENTED();
-  return 0;
+  return unique_id_;
 }
 
 void BleV2RemotePeripheral::UpdateDeviceInfo(
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h
index 4cf3e7d..f0d03a8 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h
+++ b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h
@@ -30,6 +30,7 @@
 
  private:
   bluetooth::mojom::DeviceInfoPtr device_info_;
+  uint64_t unique_id_;
 };
 
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral_unittest.cc b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral_unittest.cc
index 78d821f6..f547382 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral_unittest.cc
@@ -11,7 +11,10 @@
 namespace nearby::chrome {
 
 namespace {
-const char kAddress[] = "address";
+
+const char kAddress[] = "11:12:13:14:15:16";
+constexpr uint64_t kUniqueId = 24279786918417;
+
 }  // namespace
 
 class BleV2RemotePeripheralTest : public testing::Test {
@@ -26,6 +29,13 @@
   EXPECT_EQ(peripheral.GetAddress(), kAddress);
 }
 
+TEST_F(BleV2RemotePeripheralTest, GetUniqueId) {
+  auto device_info = bluetooth::mojom::DeviceInfo::New();
+  device_info->address = kAddress;
+  BleV2RemotePeripheral peripheral{std::move(device_info)};
+  EXPECT_EQ(kUniqueId, peripheral.GetUniqueId());
+}
+
 TEST_F(BleV2RemotePeripheralTest, CanUpdateWithSameAddress) {
   auto device_info = bluetooth::mojom::DeviceInfo::New();
   device_info->address = kAddress;
diff --git a/chrome/services/sharing/nearby/platform/wifi_direct_medium.cc b/chrome/services/sharing/nearby/platform/wifi_direct_medium.cc
index cd5ee47..ba04210 100644
--- a/chrome/services/sharing/nearby/platform/wifi_direct_medium.cc
+++ b/chrome/services/sharing/nearby/platform/wifi_direct_medium.cc
@@ -45,7 +45,10 @@
 }
 
 bool WifiDirectMedium::StopWifiDirect() {
-  NOTIMPLEMENTED();
+  if (connection_) {
+    connection_.reset();
+    return true;
+  }
   return false;
 }
 
@@ -136,12 +139,27 @@
     connection_.set_disconnect_handler(
         base::BindOnce(&WifiDirectMedium::OnDisconnect, base::Unretained(this)),
         task_runner_);
+
+    // Fetch the IPv4 address from the connection.
+    connection_->GetProperties(base::BindOnce(&WifiDirectMedium::OnProperties,
+                                              base::Unretained(this),
+                                              credentials, waitable_event));
+    return;
   }
 
   // Trigger sync signal.
   waitable_event->Signal();
 }
 
+void WifiDirectMedium::OnProperties(
+    WifiDirectCredentials* credentials,
+    base::WaitableEvent* waitable_event,
+    ash::wifi_direct::mojom::WifiDirectConnectionPropertiesPtr properties) {
+  credentials->SetIPAddress(properties->ipv4_address);
+  credentials->SetGateway(properties->ipv4_address);
+  waitable_event->Signal();
+}
+
 void WifiDirectMedium::OnDisconnect() {
   // Reset the connection, since it has been disconnected at this point.
   connection_.reset();
diff --git a/chrome/services/sharing/nearby/platform/wifi_direct_medium.h b/chrome/services/sharing/nearby/platform/wifi_direct_medium.h
index a99641b..b5b282354 100644
--- a/chrome/services/sharing/nearby/platform/wifi_direct_medium.h
+++ b/chrome/services/sharing/nearby/platform/wifi_direct_medium.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_WIFI_DIRECT_MEDIUM_H_
 #define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_WIFI_DIRECT_MEDIUM_H_
 
+#include "base/synchronization/waitable_event.h"
 #include "chromeos/ash/services/nearby/public/mojom/firewall_hole.mojom.h"
 #include "chromeos/ash/services/wifi_direct/public/mojom/wifi_direct_manager.mojom.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
@@ -53,6 +54,10 @@
       ash::wifi_direct::mojom::WifiDirectOperationResult result,
       mojo::PendingRemote<ash::wifi_direct::mojom::WifiDirectConnection>
           connection);
+  void OnProperties(
+      WifiDirectCredentials* credentials,
+      base::WaitableEvent* waitable_event,
+      ash::wifi_direct::mojom::WifiDirectConnectionPropertiesPtr properties);
 
   void OnDisconnect();
 
diff --git a/chrome/services/sharing/nearby/platform/wifi_direct_medium_unittest.cc b/chrome/services/sharing/nearby/platform/wifi_direct_medium_unittest.cc
index d4d7fb4..5e9cd740 100644
--- a/chrome/services/sharing/nearby/platform/wifi_direct_medium_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/wifi_direct_medium_unittest.cc
@@ -13,10 +13,16 @@
 
 namespace {
 
+constexpr char kTestIPv4Address[] = "127.0.0.1";
+
 class FakeWifiDirectConnection
     : public ash::wifi_direct::mojom::WifiDirectConnection {
   void GetProperties(GetPropertiesCallback callback) override {
-    NOTIMPLEMENTED();
+    auto properties =
+        ash::wifi_direct::mojom::WifiDirectConnectionProperties::New();
+    properties->ipv4_address = kTestIPv4Address;
+    properties->credentials = ash::wifi_direct::mojom::WifiCredentials::New();
+    std::move(callback).Run(std::move(properties));
   }
 
   void AssociateSocket(::mojo::PlatformHandle socket,
@@ -156,6 +162,47 @@
         base::ScopedAllowBaseSyncPrimitivesForTesting allow;
         WifiDirectCredentials credentials;
         EXPECT_TRUE(medium->StartWifiDirect(&credentials));
+        EXPECT_EQ(credentials.GetIPAddress(), kTestIPv4Address);
+        EXPECT_EQ(credentials.GetGateway(), kTestIPv4Address);
+      },
+      medium()));
+}
+
+TEST_F(WifiDirectMediumTest, StopWifiDirect_MissingConnection) {
+  manager()->SetWifiDirectConnection(nullptr);
+
+  RunOnTaskRunner(base::BindOnce(
+      [](WifiDirectMedium* medium) {
+        base::ScopedAllowBaseSyncPrimitivesForTesting allow;
+        WifiDirectCredentials credentials;
+        EXPECT_FALSE(medium->StartWifiDirect(&credentials));
+      },
+      medium()));
+
+  RunOnTaskRunner(base::BindOnce(
+      [](WifiDirectMedium* medium) {
+        base::ScopedAllowBaseSyncPrimitivesForTesting allow;
+        EXPECT_FALSE(medium->StopWifiDirect());
+      },
+      medium()));
+}
+
+TEST_F(WifiDirectMediumTest, StopWifiDirect_ExistingConnection) {
+  manager()->SetWifiDirectConnection(
+      std::make_unique<FakeWifiDirectConnection>());
+
+  RunOnTaskRunner(base::BindOnce(
+      [](WifiDirectMedium* medium) {
+        base::ScopedAllowBaseSyncPrimitivesForTesting allow;
+        WifiDirectCredentials credentials;
+        EXPECT_TRUE(medium->StartWifiDirect(&credentials));
+      },
+      medium()));
+
+  RunOnTaskRunner(base::BindOnce(
+      [](WifiDirectMedium* medium) {
+        base::ScopedAllowBaseSyncPrimitivesForTesting allow;
+        EXPECT_TRUE(medium->StopWifiDirect());
       },
       medium()));
 }
diff --git a/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.cc b/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.cc
new file mode 100644
index 0000000..d0319fe
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.cc
@@ -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.
+
+#include "chrome/services/sharing/nearby/platform/wifi_direct_server_socket.h"
+
+namespace nearby::chrome {
+
+WifiDirectServerSocket::WifiDirectServerSocket() = default;
+WifiDirectServerSocket::~WifiDirectServerSocket() = default;
+
+// api::WifiDirectServerSocket
+std::string WifiDirectServerSocket::GetIPAddress() const {
+  NOTIMPLEMENTED();
+  return std::string();
+}
+
+int WifiDirectServerSocket::GetPort() const {
+  NOTIMPLEMENTED();
+  return -1;
+}
+
+std::unique_ptr<api::WifiDirectSocket> WifiDirectServerSocket::Accept() {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+Exception WifiDirectServerSocket::Close() {
+  NOTIMPLEMENTED();
+  return {Exception::kSuccess};
+}
+}  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.h b/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.h
new file mode 100644
index 0000000..97d8c80b
--- /dev/null
+++ b/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.h
@@ -0,0 +1,30 @@
+// 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_SERVICES_SHARING_NEARBY_PLATFORM_WIFI_DIRECT_SERVER_SOCKET_H_
+#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_WIFI_DIRECT_SERVER_SOCKET_H_
+
+#include "chromeos/ash/services/nearby/public/mojom/firewall_hole.mojom.h"
+#include "third_party/nearby/src/internal/platform/exception.h"
+#include "third_party/nearby/src/internal/platform/implementation/wifi_direct.h"
+
+namespace nearby::chrome {
+
+class WifiDirectServerSocket : public api::WifiDirectServerSocket {
+ public:
+  WifiDirectServerSocket();
+  WifiDirectServerSocket(const WifiDirectServerSocket&) = delete;
+  WifiDirectServerSocket& operator=(const WifiDirectServerSocket&) = delete;
+  ~WifiDirectServerSocket() override;
+
+  // api::WifiDirectServerSocket
+  std::string GetIPAddress() const override;
+  int GetPort() const override;
+  std::unique_ptr<api::WifiDirectSocket> Accept() override;
+  Exception Close() override;
+};
+
+}  // namespace nearby::chrome
+
+#endif  // CHROME_SERVICES_SHARING_NEARBY_PLATFORM_WIFI_DIRECT_SERVER_SOCKET_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a02e378a..f4e0850 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -815,6 +815,8 @@
       "../browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h",
       "../browser/ash/login/test/network_portal_detector_mixin.cc",
       "../browser/ash/login/test/network_portal_detector_mixin.h",
+      "../browser/ash/login/users/avatar/mock_user_image_loader_delegate.cc",
+      "../browser/ash/login/users/avatar/mock_user_image_loader_delegate.h",
       "../browser/ash/login/users/avatar/user_image_manager_test_util.cc",
       "../browser/ash/login/users/avatar/user_image_manager_test_util.h",
       "../browser/ash/policy/dlp/test/files_policy_notification_manager_test_utils.cc",
@@ -860,6 +862,7 @@
       "//ash/webui/personalization_app:test_support",
       "//ash/webui/print_management:url_constants",
       "//ash/webui/print_preview_cros:url_constants",
+      "//ash/webui/sanitize_ui:url_constants",
       "//ash/webui/scanning:url_constants",
       "//ash/webui/shortcut_customization_ui:url_constants",
       "//chrome/browser/ash",
@@ -5742,6 +5745,7 @@
       "../browser/ui/lacros/screen_capture_notification_ui_lacros_browsertest.cc",
       "../browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_lacros_browsertest.cc",
       "../browser/ui/startup/first_run_service_browsertest.cc",
+      "../browser/ui/views/chrome_views_delegate_lacros_browsertest.cc",
       "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc",
       "../browser/ui/views/frame/immersive_mode_browser_view_test.cc",
       "../browser/ui/views/frame/immersive_mode_controller_chromeos_browsertest.cc",
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 92e0ff9c..3f2170a 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -117,6 +117,7 @@
       "chromeos/personalization_app/personalization_app_sea_pen_browsertest.cc",
       "chromeos/print_management/print_management_browsertest.cc",
       "chromeos/print_preview_cros/print_preview_cros_browsertest.cc",
+      "chromeos/sanitize_ui/sanitize_ui_browsertest.cc",
       "chromeos/scanning/scanning_app_browsertest.cc",
       "chromeos/settings/os_settings_browsertest.cc",
       "chromeos/shimless_rma/shimless_rma_browsertest.cc",
@@ -525,6 +526,7 @@
       "chromeos/personalization_app:build_grdp",
       "chromeos/print_management:build_grdp",
       "chromeos/print_preview_cros:build_grdp",
+      "chromeos/sanitize_ui:build_grdp",
       "chromeos/scanning:build_grdp",
       "chromeos/settings:build_grdp",
       "chromeos/shimless_rma:build_grdp",
@@ -557,6 +559,7 @@
       "$target_gen_dir/chromeos/print_preview_cros/resources.grdp",
       "$target_gen_dir/chromeos/resources.grdp",
       "$target_gen_dir/chromeos/vc_background_ui/resources.grdp",
+      "$target_gen_dir/chromeos/sanitize_ui/resources.grdp",
       "$target_gen_dir/chromeos/scanning/resources.grdp",
       "$target_gen_dir/chromeos/settings/resources.grdp",
       "$target_gen_dir/chromeos/shimless_rma/resources.grdp",
diff --git a/chrome/test/data/webui/chromeos/ash_common/shortcut_input_test.ts b/chrome/test/data/webui/chromeos/ash_common/shortcut_input_test.ts
index a47d519c..31ccde7 100644
--- a/chrome/test/data/webui/chromeos/ash_common/shortcut_input_test.ts
+++ b/chrome/test/data/webui/chromeos/ash_common/shortcut_input_test.ts
@@ -17,36 +17,65 @@
 
 function getConfirmKeyElement(shortcutInputElement: ShortcutInputElement|
                               null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#confirmKey');
 }
 
 function getPendingKeyElement(shortcutInputElement: ShortcutInputElement|
                               null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#pendingKey');
 }
 
 function getCtrlElement(shortcutInputElement: ShortcutInputElement|
                         null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#ctrlKey');
 }
 
 function getShiftElement(shortcutInputElement: ShortcutInputElement|
                          null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#shiftKey');
 }
 
 function getAltElement(shortcutInputElement: ShortcutInputElement|
                        null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#altKey');
 }
 
 function getSearchElement(shortcutInputElement: ShortcutInputElement|
                           null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#searchKey');
 }
 
+function getFunctionElement(shortcutInputElement: ShortcutInputElement|
+                            null): ShortcutInputKeyElement|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
+  return shortcutInputElement!.shadowRoot!.querySelector('#functionKey');
+}
+
 function getKeySeparator(shortcutInputElement: ShortcutInputElement|
                          null): Element|null {
+  if (shortcutInputElement === null) {
+    return null;
+  }
   return shortcutInputElement!.shadowRoot!.querySelector('#keySeparator');
 }
 
@@ -64,6 +93,7 @@
     document.body.appendChild(element);
     element.shortcutInputProvider = shortcutInputProvider;
     element.showSeparator = true;
+    element.hasFunctionKey = true;
     element.addEventListener('shortcut-input-event', function() {
       ++numShortcutInputEvents;
     });
@@ -107,11 +137,13 @@
     const altKey = getAltElement(shortcutInputElement);
     const searchKey = getSearchElement(shortcutInputElement);
     const shiftKey = getShiftElement(shortcutInputElement);
+    const functionKey = getFunctionElement(shortcutInputElement);
     assertFalse(isVisible(pendingKey));
     assertFalse(isVisible(ctrlKey));
     assertFalse(isVisible(altKey));
     assertFalse(isVisible(searchKey));
     assertFalse(isVisible(shiftKey));
+    assertFalse(isVisible(functionKey));
   });
 
   test('DisplayAlphaKey', async () => {
@@ -136,15 +168,18 @@
     const altKey = getAltElement(shortcutInputElement);
     const searchKey = getSearchElement(shortcutInputElement);
     const shiftKey = getShiftElement(shortcutInputElement);
+    const functionKey = getFunctionElement(shortcutInputElement);
     // All keys should be visible and not selected.
     assertTrue(isVisible(ctrlKey));
     assertTrue(isVisible(altKey));
     assertTrue(isVisible(searchKey));
     assertTrue(isVisible(shiftKey));
+    assertTrue(isVisible(functionKey));
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey!.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, altKey!.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, searchKey!.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, shiftKey!.keyState);
+    assertEquals(KeyInputState.NOT_SELECTED, functionKey!.keyState);
 
     const confirmContainer =
         shortcutInputElement!.shadowRoot!.querySelector('#confirmContainer');
@@ -172,16 +207,19 @@
     const ctrlKey = getCtrlElement(shortcutInputElement);
     const altKey = getAltElement(shortcutInputElement);
     const searchKey = getSearchElement(shortcutInputElement);
+    const functionKey = getFunctionElement(shortcutInputElement);
     const shiftKey = getShiftElement(shortcutInputElement);
 
     // Only shift key should be selected, but all should be visible.
     assertTrue(isVisible(ctrlKey));
     assertTrue(isVisible(altKey));
     assertTrue(isVisible(searchKey));
+    assertTrue(isVisible(functionKey));
     assertTrue(isVisible(shiftKey));
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey!.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, altKey!.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, searchKey!.keyState);
+    assertEquals(KeyInputState.NOT_SELECTED, functionKey!.keyState);
     assertEquals(KeyInputState.MODIFIER_SELECTED, shiftKey!.keyState);
 
     const confirmContainer =
@@ -196,8 +234,8 @@
       vkey: VKey.kKeyA,
       domCode: 0,
       domKey: 0,
-      modifiers:
-          Modifier.SHIFT | Modifier.CONTROL | Modifier.ALT | Modifier.COMMAND,
+      modifiers: Modifier.SHIFT | Modifier.CONTROL | Modifier.ALT |
+          Modifier.COMMAND | Modifier.FN_KEY,
       keyDisplay: 'a',
     };
     shortcutInputProvider.sendKeyPressEvent(keyEvent, keyEvent);
@@ -212,16 +250,19 @@
     const altKey = getAltElement(shortcutInputElement);
     const searchKey = getSearchElement(shortcutInputElement);
     const shiftKey = getShiftElement(shortcutInputElement);
+    const functionKey = getFunctionElement(shortcutInputElement);
 
     // All modifier keys should be selected.
     assertTrue(isVisible(ctrlKey));
     assertTrue(isVisible(altKey));
     assertTrue(isVisible(searchKey));
     assertTrue(isVisible(shiftKey));
+    assertTrue(isVisible(functionKey));
     assertEquals(KeyInputState.MODIFIER_SELECTED, ctrlKey!.keyState);
     assertEquals(KeyInputState.MODIFIER_SELECTED, altKey!.keyState);
     assertEquals(KeyInputState.MODIFIER_SELECTED, searchKey!.keyState);
     assertEquals(KeyInputState.MODIFIER_SELECTED, shiftKey!.keyState);
+    assertEquals(KeyInputState.MODIFIER_SELECTED, functionKey!.keyState);
 
     const confirmContainer =
         shortcutInputElement!.shadowRoot!.querySelector('#confirmContainer');
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
index 52d027f..91ee11d 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {beginLoadRecentSeaPenImagesAction, beginLoadSelectedImageAction, beginLoadSelectedRecentSeaPenImageAction, beginSearchSeaPenThumbnailsAction, beginSelectRecentSeaPenImageAction, beginSelectSeaPenThumbnailAction, endSelectRecentSeaPenImageAction, endSelectSeaPenThumbnailAction, getRecentSeaPenImages, getSeaPenStore, SeaPenState, SeaPenStoreAdapter, SeaPenStoreInterface, searchSeaPenThumbnails, selectRecentSeaPenImage, selectSeaPenWallpaper, setCurrentSeaPenQueryAction, setRecentSeaPenImagesAction, setSeaPenThumbnailsAction, setSelectedRecentSeaPenImageAction, setThumbnailResponseStatusCodeAction, WallpaperLayout, WallpaperType} from 'chrome://personalization/js/personalization_app.js';
+import {beginLoadRecentSeaPenImagesAction, beginLoadSelectedImageAction, beginLoadSelectedRecentSeaPenImageAction, beginSearchSeaPenThumbnailsAction, beginSelectRecentSeaPenImageAction, beginSelectSeaPenThumbnailAction, endSelectRecentSeaPenImageAction, endSelectSeaPenThumbnailAction, getRecentSeaPenImageIds, getSeaPenStore, getSeaPenThumbnails, SeaPenState, SeaPenStoreAdapter, SeaPenStoreInterface, selectRecentSeaPenImage, selectSeaPenThumbnail, setCurrentSeaPenQueryAction, setRecentSeaPenImagesAction, setSeaPenThumbnailsAction, setSelectedRecentSeaPenImageAction, setThumbnailResponseStatusCodeAction, WallpaperLayout, WallpaperType} from 'chrome://personalization/js/personalization_app.js';
 import {MantaStatusCode} from 'chrome://resources/ash/common/sea_pen/sea_pen.mojom-webui.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
@@ -27,7 +27,7 @@
   });
 
   test('sets recent sea pen images in store', async () => {
-    await getRecentSeaPenImages(seaPenProvider, seaPenStore);
+    await getRecentSeaPenImageIds(seaPenProvider, seaPenStore);
 
     assertDeepEquals(
         [
@@ -44,13 +44,13 @@
 
   test('sets sea pen thumbnails in store', async () => {
     const query = {textQuery: 'test_query'};
-    await searchSeaPenThumbnails(query, seaPenProvider, seaPenStore);
+    await getSeaPenThumbnails(query, seaPenProvider, seaPenStore);
     assertDeepEquals(
         [
           beginSearchSeaPenThumbnailsAction(query),
           setCurrentSeaPenQueryAction(query),
           setThumbnailResponseStatusCodeAction(MantaStatusCode.kOk),
-          setSeaPenThumbnailsAction(query, seaPenProvider.images),
+          setSeaPenThumbnailsAction(query, seaPenProvider.thumbnails),
         ],
         personalizationStore.actions, 'expected actions match');
 
@@ -128,7 +128,7 @@
               recentImageData: {},
               recentImages: null,
               thumbnailResponseStatusCode: MantaStatusCode.kOk,
-              thumbnails: seaPenProvider.images,
+              thumbnails: seaPenProvider.thumbnails,
               currentSeaPenQuery: query,
               pendingSelected: null,
               currentSelected: null,
@@ -154,7 +154,7 @@
     };
     personalizationStore.data.wallpaper.seaPen.currentSelected = 123;
 
-    const promise = selectSeaPenWallpaper(
+    const promise = selectSeaPenThumbnail(
         {image: {url: ''}, id: 456}, seaPenProvider, seaPenStore);
 
     assertDeepEquals(
@@ -235,7 +235,7 @@
         Promise.resolve({success: false});
 
     const thumbnail = {image: {url: ''}, id: 456};
-    await selectSeaPenWallpaper(thumbnail, seaPenProvider, seaPenStore);
+    await selectSeaPenThumbnail(thumbnail, seaPenProvider, seaPenStore);
 
     assertDeepEquals(
         [
@@ -258,7 +258,7 @@
 
     // Try and fail again.
     const promise =
-        selectSeaPenWallpaper(thumbnail, seaPenProvider, seaPenStore);
+        selectSeaPenThumbnail(thumbnail, seaPenProvider, seaPenStore);
 
     // Error reset to null while attempting to select again.
     assertEquals(null, personalizationStore.data.wallpaper.seaPen.error);
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_images_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_images_element_test.ts
index 0fac820..c17af884 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_images_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_images_element_test.ts
@@ -75,7 +75,7 @@
   test('displays loading thumbnail placeholders', async () => {
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
 
     // Initialize |seaPenImagesElement|.
     seaPenImagesElement = initElement(SeaPenImagesElement);
@@ -95,7 +95,7 @@
   test('thumbnail placeholders not active when hidden', async () => {
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
 
     // Initialize |seaPenImagesElement|.
     seaPenImagesElement = initElement(SeaPenImagesElement);
@@ -112,7 +112,7 @@
   test('displays image thumbnails', async () => {
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
 
     // Initialize |seaPenImagesElement|.
     seaPenImagesElement = initElement(SeaPenImagesElement);
@@ -127,10 +127,10 @@
     personalizationStore.setReducersEnabled(true);
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
     // Index 1 is currently set as wallpaper.
     personalizationStore.data.wallpaper.seaPen.currentSelected =
-        seaPenProvider.images[1]!.id;
+        seaPenProvider.thumbnails[1]!.id;
 
     seaPenImagesElement = initElement(SeaPenImagesElement);
     await waitAfterNextRender(seaPenImagesElement);
@@ -205,7 +205,7 @@
     await waitAfterNextRender(seaPenImagesElement);
     // Simulate receiving a confirmation that the sea pen image was selected.
     personalizationStore.dispatch(
-        setSelectedRecentSeaPenImageAction(seaPenProvider.images[0]!.id));
+        setSelectedRecentSeaPenImageAction(seaPenProvider.thumbnails[0]!.id));
     await waitAfterNextRender(seaPenImagesElement);
 
     thumbnails = getWallpaperGridItems();
@@ -227,7 +227,7 @@
   test('display feedback buttons', async () => {
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
 
     seaPenImagesElement = initElement(SeaPenImagesElement);
     await waitAfterNextRender(seaPenImagesElement);
@@ -348,7 +348,7 @@
     personalizationStore.setReducersEnabled(true);
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = true;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
 
     // Initialize |seaPenImagesElement|.
     seaPenImagesElement = initElement(SeaPenImagesElement);
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_input_query_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_input_query_element_test.ts
index 6bf580c..c35050b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_input_query_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_input_query_element_test.ts
@@ -33,7 +33,7 @@
 
   test('displays recreate button if thumbnails exist', async () => {
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
     seaPenInputQueryElement = initElement(SeaPenInputQueryElement);
     await waitAfterNextRender(seaPenInputQueryElement);
 
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
index 3f6bb93..8671e13 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_router_element_test.ts
@@ -157,7 +157,7 @@
     await waitAfterNextRender(routerElement);
     personalizationStore.data.wallpaper.seaPen.loading.thumbnails = false;
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
     personalizationStore.notifyObservers();
 
     assertEquals(
@@ -202,7 +202,7 @@
 
     // Clicking the inspire button should match the rendered template.
     const initialQuery: SeaPenQuery =
-        await seaPenProvider.whenCalled('searchWallpaper');
+        await seaPenProvider.whenCalled('getSeaPenThumbnails');
     assertEquals(
         initialQuery.templateQuery!.id, initialTemplate,
         'Initial query template id should match');
@@ -219,7 +219,7 @@
 
     // After switching templates, we should match the new template.
     const finalQuery: SeaPenQuery =
-        await seaPenProvider.whenCalled('searchWallpaper');
+        await seaPenProvider.whenCalled('getSeaPenThumbnails');
     assertEquals(
         finalQuery.templateQuery!.id, finalTemplate,
         'Final query template id should match');
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_template_query_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_template_query_element_test.ts
index 5f4168d..70917790 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_template_query_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_template_query_element_test.ts
@@ -80,7 +80,7 @@
 
   test('displays recreate button if thumbnails exist', async () => {
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
     seaPenTemplateQueryElement = initElement(SeaPenTemplateQueryElement, {
       templateId: SeaPenTemplateId.kFlower.toString(),
     });
@@ -117,7 +117,7 @@
 
   test('displays create button when selected option changes', async () => {
     personalizationStore.data.wallpaper.seaPen.thumbnails =
-        seaPenProvider.images;
+        seaPenProvider.thumbnails;
     seaPenTemplateQueryElement = initElement(SeaPenTemplateQueryElement, {
       templateId: SeaPenTemplateId.kFlower.toString(),
     });
@@ -442,7 +442,7 @@
     await waitAfterNextRender(seaPenTemplateQueryElement);
 
     const query: SeaPenQuery =
-        await seaPenProvider.whenCalled('searchWallpaper');
+        await seaPenProvider.whenCalled('getSeaPenThumbnails');
     assertEquals(
         query.templateQuery!.id, SeaPenTemplateId.kFlower,
         'Query template id should match');
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_sea_pen_interface_provider.ts b/chrome/test/data/webui/chromeos/personalization_app/test_sea_pen_interface_provider.ts
index 0904e1f..d71ed88 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_sea_pen_interface_provider.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_sea_pen_interface_provider.ts
@@ -12,7 +12,7 @@
 
 export class TestSeaPenProvider extends TestBrowserProxy implements
     SeaPenProviderInterface {
-  images: SeaPenThumbnail[] = [
+  thumbnails: SeaPenThumbnail[] = [
     {
       id: 1,
       image: {url: 'https://sea-pen-images.googleusercontent.com/1'},
@@ -94,10 +94,10 @@
 
   constructor() {
     super([
-      'searchWallpaper',
+      'getSeaPenThumbnails',
       'selectSeaPenThumbnail',
       'selectRecentSeaPenImage',
-      'getRecentSeaPenImages',
+      'getRecentSeaPenImageIds',
       'getRecentSeaPenImageThumbnail',
       'deleteRecentSeaPenImage',
       'shouldShowSeaPenIntroductionDialog',
@@ -105,10 +105,10 @@
     ]);
   }
 
-  searchWallpaper(query: SeaPenQuery) {
-    this.methodCalled('searchWallpaper', query);
+  getSeaPenThumbnails(query: SeaPenQuery) {
+    this.methodCalled('getSeaPenThumbnails', query);
     return Promise.resolve({
-      images: this.images,
+      thumbnails: this.thumbnails,
       statusCode: MantaStatusCode.kOk,
     });
   }
@@ -127,8 +127,8 @@
     return this.selectSeaPenRecentImageResponse;
   }
 
-  getRecentSeaPenImages() {
-    this.methodCalled('getRecentSeaPenImages');
+  getRecentSeaPenImageIds() {
+    this.methodCalled('getRecentSeaPenImageIds');
     return Promise.resolve({ids: this.recentImageIds});
   }
 
diff --git a/chrome/test/data/webui/chromeos/sanitize_ui/BUILD.gn b/chrome/test/data/webui/chromeos/sanitize_ui/BUILD.gn
new file mode 100644
index 0000000..1c4c8d4
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/sanitize_ui/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+import("../../build_webui_tests.gni")
+
+build_webui_tests("build_webui_tests") {
+  ts_path_mappings =
+      [ "chrome://sanitize/*|" +
+        rebase_path("$root_gen_dir/ash/webui/sanitize_ui/resources/tsc/*",
+                    target_gen_dir) ]
+  files = [ "sanitize_ui_test.ts" ]
+  ts_deps = [
+    "//ash/webui/common/resources:build_ts",
+    "//ash/webui/common/resources/cr_elements:build_ts",
+    "//ash/webui/sanitize_ui/resources:build_ts",
+    "//chrome/test/data/webui/chromeos:build_ts",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/js:build_ts",
+  ]
+}
diff --git a/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_browsertest.cc b/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_browsertest.cc
new file mode 100644
index 0000000..f1dd165
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_browsertest.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 <string>
+
+#include "ash/webui/sanitize_ui/url_constants.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/test/base/web_ui_mocha_browser_test.h"
+#include "content/public/test/browser_test.h"
+
+/**
+ * @fileoverview Test suite for chrome://sanitize.
+ */
+
+namespace ash {
+
+namespace {
+
+class SanitizeUIBrowserTest : public WebUIMochaBrowserTest {
+ public:
+  SanitizeUIBrowserTest() {
+    set_test_loader_host(::ash::kChromeUISanitizeAppHost);
+  }
+
+ protected:
+  void RunTestAtPath(const std::string& testFilePath) {
+    auto testPath =
+        base::StringPrintf("chromeos/sanitize_ui/%s", testFilePath.c_str());
+    WebUIMochaBrowserTest::RunTest(testPath, "mocha.run()");
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(SanitizeUIBrowserTest, SanitizeInitialize) {
+  RunTestAtPath("sanitize_ui_test.js");
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_test.ts b/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_test.ts
new file mode 100644
index 0000000..5fe7dfc
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/sanitize_ui/sanitize_ui_test.ts
@@ -0,0 +1,25 @@
+// 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.
+
+import {assert} from 'chrome://resources/js/assert.js';
+import {SanitizeDoneElement} from 'chrome://sanitize/sanitize_done.js';
+import {assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+function initSanitizeDoneElement(): SanitizeDoneElement {
+  const element = new SanitizeDoneElement();
+  document.body.appendChild(element);
+  flushTasks();
+  return element;
+}
+suite('SanitizeUITest', function() {
+  test('SanitizeDonePopulation', () => {
+    const doneElement = initSanitizeDoneElement();
+    const headerDiv = doneElement.shadowRoot!.querySelector('#header');
+    // Verify the header element exists
+    assert(headerDiv);
+    // Check the header content
+    assertEquals('Sanitize Done', headerDiv!.textContent);
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/settings/os_apps_page/os_apps_page_test.ts b/chrome/test/data/webui/chromeos/settings/os_apps_page/os_apps_page_test.ts
index a430b90..9a23864 100644
--- a/chrome/test/data/webui/chromeos/settings/os_apps_page/os_apps_page_test.ts
+++ b/chrome/test/data/webui/chromeos/settings/os_apps_page/os_apps_page_test.ts
@@ -16,7 +16,7 @@
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
-import {clearBody} from '../utils.js';
+import {clearBody, hasStringProperty} from '../utils.js';
 
 import {FakeAppNotificationHandler} from './app_notifications_page/fake_app_notification_handler.js';
 import {TestAndroidAppsBrowserProxy} from './test_android_apps_browser_proxy.js';
@@ -54,6 +54,11 @@
       },
     },
     on_device_app_controls: {
+      pin: {
+        key: 'on_device_app_controls.pin',
+        type: chrome.settingsPrivate.PrefType.STRING,
+        value: '',
+      },
       setup_completed: {
         key: 'on_device_app_controls.setup_completed',
         type: chrome.settingsPrivate.PrefType.BOOLEAN,
@@ -409,16 +414,37 @@
                 parentalControlsRow.querySelector<HTMLElement>('#setupPin');
             assertTrue(!!setupPinDialog);
 
-            // TODO(b/332936223): When setup flow is implemented, simulate a
-            // successful PIN submission here instead.
-            const cancelPinSetupButton =
+            // Simulate PIN entry.
+            const pin = '123456';
+            const setupPinKeyboard =
+                setupPinDialog.shadowRoot!.getElementById('setupPinKeyboard');
+            assertTrue(!!setupPinKeyboard);
+            const pinKeyboard =
+                setupPinKeyboard.shadowRoot!.getElementById('pinKeyboard');
+            assertTrue(!!pinKeyboard);
+            assertTrue(hasStringProperty(pinKeyboard, 'value'));
+            pinKeyboard.value = pin;
+
+            const continuePinSetupButton =
                 setupPinDialog.shadowRoot!
                     .querySelector<HTMLElement>('#dialog')!
-                    .querySelector<HTMLElement>('.cancel-button');
-            assertTrue(!!cancelPinSetupButton);
-            cancelPinSetupButton.click();
+                    .querySelector<HTMLElement>('.action-button');
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
+
+            // Verify that the PIN keyboard has been reset.
+            assertTrue(pinKeyboard.value === '');
+
+            // Re-enter the PIN to confirm it.
+            pinKeyboard.value = pin;
+
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
             await waitAfterNextRender(appsPage);
 
+            assertTrue(appsPage.prefs.on_device_app_controls.pin.value === pin);
+            assertTrue(
+                appsPage.prefs.on_device_app_controls.setup_completed.value);
             assertTrue(!!appsPage.shadowRoot!.querySelector(
                 'settings-app-parental-controls-subpage'));
           });
@@ -444,16 +470,37 @@
                 parentalControlsRow.querySelector<HTMLElement>('#setupPin');
             assertTrue(!!setupPinDialog);
 
-            // TODO(b/332936223): When setup flow is implemented, simulate a
-            // successful PIN submission here instead.
-            const cancelPinSetupButton =
+            // Simulate PIN entry.
+            const pin = '123456';
+            const setupPinKeyboard =
+                setupPinDialog.shadowRoot!.getElementById('setupPinKeyboard');
+            assertTrue(!!setupPinKeyboard);
+            const pinKeyboard =
+                setupPinKeyboard.shadowRoot!.getElementById('pinKeyboard');
+            assertTrue(!!pinKeyboard);
+            assertTrue(hasStringProperty(pinKeyboard, 'value'));
+            pinKeyboard.value = pin;
+
+            const continuePinSetupButton =
                 setupPinDialog.shadowRoot!
                     .querySelector<HTMLElement>('#dialog')!
-                    .querySelector<HTMLElement>('.cancel-button');
-            assertTrue(!!cancelPinSetupButton);
-            cancelPinSetupButton.click();
+                    .querySelector<HTMLElement>('.action-button');
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
+
+            // Verify that the PIN keyboard has been reset.
+            assertTrue(pinKeyboard.value === '');
+
+            // Re-enter the PIN to confirm it.
+            pinKeyboard.value = pin;
+
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
             await waitAfterNextRender(appsPage);
 
+            assertTrue(appsPage.prefs.on_device_app_controls.pin.value === pin);
+            assertTrue(
+                appsPage.prefs.on_device_app_controls.setup_completed.value);
             assertTrue(!!appsPage.shadowRoot!.querySelector(
                 'settings-app-parental-controls-subpage'));
 
@@ -509,16 +556,37 @@
                 parentalControlsRow.querySelector<HTMLElement>('#setupPin');
             assertTrue(!!setupPinDialog);
 
-            // TODO(b/332936223): When setup flow is implemented, simulate a
-            // successful PIN submission here instead.
-            const cancelPinSetupButton =
+            // Simulate PIN entry.
+            const pin = '123456';
+            const setupPinKeyboard =
+                setupPinDialog.shadowRoot!.getElementById('setupPinKeyboard');
+            assertTrue(!!setupPinKeyboard);
+            const pinKeyboard =
+                setupPinKeyboard.shadowRoot!.getElementById('pinKeyboard');
+            assertTrue(!!pinKeyboard);
+            assertTrue(hasStringProperty(pinKeyboard, 'value'));
+            pinKeyboard.value = pin;
+
+            const continuePinSetupButton =
                 setupPinDialog.shadowRoot!
                     .querySelector<HTMLElement>('#dialog')!
-                    .querySelector<HTMLElement>('.cancel-button');
-            assertTrue(!!cancelPinSetupButton);
-            cancelPinSetupButton.click();
+                    .querySelector<HTMLElement>('.action-button');
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
+
+            // Verify that the PIN keyboard has been reset.
+            assertTrue(pinKeyboard.value === '');
+
+            // Re-enter the PIN to confirm it.
+            pinKeyboard.value = pin;
+
+            assertTrue(!!continuePinSetupButton);
+            continuePinSetupButton.click();
             await waitAfterNextRender(appsPage);
 
+            assertTrue(appsPage.prefs.on_device_app_controls.pin.value === pin);
+            assertTrue(
+                appsPage.prefs.on_device_app_controls.setup_completed.value);
             assertTrue(!!appsPage.shadowRoot!.querySelector(
                 'settings-app-parental-controls-subpage'));
 
diff --git a/chrome/test/data/webui/commerce/product_specifications/app_test.ts b/chrome/test/data/webui/commerce/product_specifications/app_test.ts
index 1f676bc..0d856d6 100644
--- a/chrome/test/data/webui/commerce/product_specifications/app_test.ts
+++ b/chrome/test/data/webui/commerce/product_specifications/app_test.ts
@@ -37,6 +37,7 @@
       {
         productClusterId: BigInt(0),
         title: '',
+        productUrl: {url: ''},
         imageUrl: {url: ''},
         productDimensionValues: new Map<bigint, string[]>(),
         summary: '',
@@ -261,11 +262,13 @@
     const info1 = createInfo({
       clusterId: BigInt(123),
       title: 'qux',
+      productUrl: {url: 'https://example.com/'},
       imageUrl: {url: 'qux.com/image'},
     });
     const info2 = createInfo({
       clusterId: BigInt(231),
       title: 'foobar',
+      productUrl: {url: 'https://example2.com/'},
       imageUrl: {url: 'foobar.com/image'},
     });
 
@@ -287,14 +290,14 @@
           {
             selectedItem: {
               title: specsProduct1.title,
-              url: 'https://example.com',
+              url: 'https://example.com/',
               imageUrl: info1.imageUrl.url,
             },
           },
           {
             selectedItem: {
               title: '',
-              url: 'https://example.com',
+              url: 'https://example2.com/',
               imageUrl: info2.imageUrl.url,
             },
           },
@@ -322,7 +325,7 @@
     const table =
         appElement.shadowRoot!.querySelector('product-specifications-table');
     assertTrue(!!table);
-    const newUrl = 'https://example3.com';
+    const newUrl = 'https://example3.com/';
     table.dispatchEvent(new CustomEvent('url-change', {
       detail: {
         url: newUrl,
@@ -337,6 +340,53 @@
         shoppingServiceApi.getArgs('getProductSpecificationsForUrls')[1]);
   });
 
+  test('updates on removal', async () => {
+    const urlsParam = ['https://example.com/'];
+    const promiseValues = createAppPromiseValues({urlsParam: urlsParam});
+    await createAppElementWithPromiseValues(promiseValues);
+
+    assertEquals(
+        1, shoppingServiceApi.getCallCount('getProductSpecificationsForUrls'));
+    assertEquals(1, shoppingServiceApi.getCallCount('getProductInfoForUrl'));
+    assertArrayEquals(
+        urlsParam.map(url => ({url})),
+        shoppingServiceApi.getArgs('getProductSpecificationsForUrls')[0]);
+
+    const table =
+        appElement.shadowRoot!.querySelector('product-specifications-table');
+    assertTrue(!!table);
+    table.dispatchEvent(new CustomEvent('url-remove', {
+      detail: {
+        index: 0,
+      },
+    }));
+
+    // Should not get called on an empty url list
+    assertEquals(
+        1, shoppingServiceApi.getCallCount('getProductSpecificationsForUrls'));
+    assertEquals(1, shoppingServiceApi.getCallCount('getProductInfoForUrl'));
+    assertEquals(0, table.columns.length);
+  });
+
+  test('deletes product specification set', async () => {
+    const urlsParam = ['https://example.com/'];
+    const promiseValues = createAppPromiseValues(
+        {urlsParam: urlsParam, specsSet: createSpecsSet()});
+    await createAppElementWithPromiseValues(promiseValues);
+
+    const uuid =
+        shoppingServiceApi.getArgs('addProductSpecificationsSet')[0][2];
+    const header =
+        appElement.shadowRoot!.querySelector('product-specifications-header');
+    assertTrue(!!header);
+    header.dispatchEvent(new CustomEvent('delete-click'));
+
+    assertEquals(
+        1, shoppingServiceApi.getCallCount('deleteProductSpecificationsSet'));
+    assertEquals(
+        uuid, shoppingServiceApi.getArgs('deleteProductSpecificationsSet')[1]);
+  });
+
   suite('Header', () => {
     test('displays correct subtitle for retrieved sets', async () => {
       const specsSet = createSpecsSet({
@@ -395,7 +445,7 @@
     });
 
     test('hides empty state after product selection', async () => {
-      const url = 'https://example.com';
+      const url = 'https://example.com/';
       const openTabs = [{
         title: 'title',
         url: stringToMojoUrl(url),
@@ -406,7 +456,10 @@
           'getUrlInfosForRecentlyViewedTabs', Promise.resolve({urlInfos: []}));
       const promiseValues = createAppPromiseValues({
         urlsParam: [],
-        infos: [createInfo({clusterId: BigInt(123)})],
+        infos: [createInfo({
+          clusterId: BigInt(123),
+          productUrl: {url: 'https://example.com/'},
+        })],
       });
       createAppElementWithPromiseValues(promiseValues);
 
diff --git a/chrome/test/data/webui/commerce/product_specifications/product_selection_menu_test.ts b/chrome/test/data/webui/commerce/product_specifications/product_selection_menu_test.ts
index 0ec1636..2f8845d 100644
--- a/chrome/test/data/webui/commerce/product_specifications/product_selection_menu_test.ts
+++ b/chrome/test/data/webui/commerce/product_specifications/product_selection_menu_test.ts
@@ -198,7 +198,7 @@
 
     const listElements =
         menu.$.menu.get().querySelectorAll<HTMLElement>('.dropdown-item');
-    assertEquals(1, listElements.length);
+    assertEquals(2, listElements.length);
 
     const tabUrl = listElements[0]!.shadowRoot!.querySelector<HTMLElement>(
         '.description-text');
@@ -226,6 +226,24 @@
     assertFalse(crActionMenu.open);
   });
 
+  test('fires removal event', async () => {
+    initUrlInfos();
+    const menu = await createMenu();
+    menu.showAt(document.body);
+    await flushTasks();
+
+    const crActionMenu = menu.$.menu.get();
+    assertTrue(crActionMenu.open);
+    const removeButton = crActionMenu.querySelector<HTMLElement>('#remove');
+    assertTrue(!!removeButton);
+    const eventPromise = eventToPromise('remove-url', menu);
+    removeButton.click();
+    const event = await eventPromise;
+
+    assertTrue(!!event);
+    assertFalse(crActionMenu.open);
+  });
+
   test('updates when infos change', async () => {
     initRecentlyViewedTabUrlInfos([]);
     initOpenTabUrlInfos([]);
diff --git a/chrome/test/data/webui/commerce/product_specifications/table_test.ts b/chrome/test/data/webui/commerce/product_specifications/table_test.ts
index 233bb38..935f66b 100644
--- a/chrome/test/data/webui/commerce/product_specifications/table_test.ts
+++ b/chrome/test/data/webui/commerce/product_specifications/table_test.ts
@@ -126,6 +126,25 @@
     assertEquals(0, event.detail.index);
   });
 
+  test('fires url remove event', async () => {
+    // Arrange
+    tableElement.columns = [
+      {selectedItem: {title: 'title', url: 'https://foo.com', imageUrl: ''}},
+      {selectedItem: {title: 'title2', url: 'https://bar.com', imageUrl: ''}},
+    ];
+    await waitAfterNextRender(tableElement);
+    const productSelector =
+        tableElement.shadowRoot!.querySelector('product-selector');
+    assertTrue(!!productSelector);
+    const eventPromise = eventToPromise('url-remove', tableElement);
+    productSelector.dispatchEvent(new CustomEvent('remove-url'));
+
+    // Assert.
+    const event = await eventPromise;
+    assertTrue(!!event);
+    assertEquals(0, event.detail.index);
+  });
+
   test('opens new tab', async () => {
     // Arrange
     const testUrl = 'https://example.com';
diff --git a/chrome/test/data/webui/settings/test_performance_browser_proxy.ts b/chrome/test/data/webui/settings/test_performance_browser_proxy.ts
index bdcc485..5abc130 100644
--- a/chrome/test/data/webui/settings/test_performance_browser_proxy.ts
+++ b/chrome/test/data/webui/settings/test_performance_browser_proxy.ts
@@ -17,7 +17,6 @@
       'openBatterySaverFeedbackDialog',
       'openMemorySaverFeedbackDialog',
       'openSpeedFeedbackDialog',
-      'onDiscardRingTreatmentEnabledChanged',
       'validateTabDiscardExceptionRule',
     ]);
   }
@@ -48,10 +47,6 @@
     this.methodCalled('openSpeedFeedbackDialog');
   }
 
-  onDiscardRingTreatmentEnabledChanged() {
-    this.methodCalled('onDiscardRingTreatmentEnabledChanged');
-  }
-
   setValidationResults(results: Record<string, boolean>) {
     this.validationResults_ = results;
   }
diff --git a/chromeos/ash/components/growth/campaigns_model.cc b/chromeos/ash/components/growth/campaigns_model.cc
index b217780..662606c 100644
--- a/chromeos/ash/components/growth/campaigns_model.cc
+++ b/chromeos/ash/components/growth/campaigns_model.cc
@@ -20,6 +20,7 @@
 #include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -114,39 +115,57 @@
 inline constexpr char kShelfAppButtonId[] = "shelfAppButtonId";
 
 // Image Model.
-inline constexpr char kBuiltInIcon[] = "builtInIcon";
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+inline constexpr char kImage[] = "image";
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 inline constexpr int kIconSize = 60;
+inline constexpr char kVectorIcon[] = "vectorIcon";
+
+// Vector Icon
+inline constexpr char kBuiltInVectorIcon[] = "builtInVectorIcon";
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+// Image
+inline constexpr char kBuiltInImage[] = "builtInImage";
+
 inline constexpr gfx::Size kBubbleIconSizeDip = gfx::Size(kIconSize, kIconSize);
 
 std::optional<int> GetBuiltInImageResourceId(
-    const std::optional<BuiltInIcon>& icon) {
-  if (!icon) {
+    const std::optional<BuiltInImage>& image_model_type) {
+  if (!image_model_type) {
     return std::nullopt;
   }
 
-  if (icon == BuiltInIcon::kContainerApp) {
-    return IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG;
+  switch (image_model_type.value()) {
+    case BuiltInImage::kContainerApp:
+      return IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG;
+    case BuiltInImage::kG1:
+      return IDR_GROWTH_FRAMEWORK_G1_PNG;
+    case BuiltInImage::kRebuy:
+      return IDR_GROWTH_FRAMEWORK_REBUY_PNG;
   }
-
-  if (icon == BuiltInIcon::kG1) {
-    return IDR_GROWTH_FRAMEWORK_G1_PNG;
-  }
-
-  return std::nullopt;
 }
 
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
-std::optional<BuiltInIcon> GetBuiltInIconType(
+std::optional<BuiltInImage> GetBuiltInImageType(
     const base::Value::Dict* image_dict) {
-  auto built_in_icon_value = image_dict->FindInt(kBuiltInIcon);
-  if (!built_in_icon_value) {
+  auto built_in_image_value = image_dict->FindInt(kBuiltInImage);
+  if (!built_in_image_value) {
     return std::nullopt;
   }
 
-  return static_cast<BuiltInIcon>(built_in_icon_value.value());
+  return static_cast<BuiltInImage>(built_in_image_value.value());
+}
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+std::optional<BuiltInVectorIcon> GetBuiltInVectorIconType(
+    const base::Value::Dict* vector_icon_dict) {
+  auto built_in_vector_icon_value =
+      vector_icon_dict->FindInt(kBuiltInVectorIcon);
+  if (!built_in_vector_icon_value) {
+    return std::nullopt;
+  }
+
+  return static_cast<BuiltInVectorIcon>(built_in_vector_icon_value.value());
 }
 
 }  // namespace
@@ -575,53 +594,98 @@
   return anchor_dict_->FindString(kShelfAppButtonId);
 }
 
-// Image Model.
+// Image.
 Image::Image(const base::Value::Dict* image_dict) : image_dict_(image_dict) {}
 Image::~Image() = default;
 
-const gfx::VectorIcon* Image::GetVectorIcon() const {
-  const auto icon = GetBuiltInIconType(image_dict_);
-  if (!icon || icon.value() != BuiltInIcon::kRedeem) {
+const gfx::Image* Image::GetImage() const {
+  if (!image_dict_) {
+    return nullptr;
+  }
+
+  // TODO: b/329113710- Handle other image sources.
+  return GetBuiltInImage();
+}
+
+const gfx::Image* Image::GetBuiltInImage() const {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  const auto image_id =
+      GetBuiltInImageResourceId(GetBuiltInImageType(image_dict_));
+  if (image_id) {
+    return &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+        image_id.value());
+  }
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+  // TODO: b/340895798 - record error metric.
+  LOG(ERROR) << "Unrecognized built in image.";
+
+  return nullptr;
+}
+
+// Vector Icon.
+VectorIcon::VectorIcon(const base::Value::Dict* vector_icon_dict)
+    : vector_icon_dict_(vector_icon_dict) {}
+VectorIcon::~VectorIcon() = default;
+
+const gfx::VectorIcon* VectorIcon::GetVectorIcon() const {
+  if (!vector_icon_dict_) {
+    return nullptr;
+  }
+
+  // TODO:b/329113710 - Handle other vector icon sources.
+  return GetBuiltInVectorIcon();
+}
+
+const gfx::VectorIcon* VectorIcon::GetBuiltInVectorIcon() const {
+  const auto icon = GetBuiltInVectorIconType(vector_icon_dict_);
+  if (!icon || icon.value() != BuiltInVectorIcon::kRedeem) {
+    // TODO: b/340895798 - record error metric.
+    LOG(ERROR) << "Unrecognized built in vector icon.";
+
     return nullptr;
   }
 
   return &chromeos::kRedeemIcon;
 }
 
-const std::optional<ui::ImageModel> Image::GetImage() const {
-  if (!image_dict_) {
+// Image Model.
+ImageModel::ImageModel(const base::Value::Dict* image_model_dict)
+    : image_model_dict_(image_model_dict) {}
+ImageModel::~ImageModel() = default;
+
+const std::optional<ui::ImageModel> ImageModel::GetImageModel() const {
+  if (!image_model_dict_) {
     return std::nullopt;
   }
 
   // TODO(b/329113710): Handle other image sources.
-  return GetBuiltInIcon();
+  return GetBuiltInImageModel();
 }
 
-const std::optional<ui::ImageModel> Image::GetBuiltInIcon() const {
-  const auto* vector_icon = GetVectorIcon();
+const std::optional<ui::ImageModel> ImageModel::GetBuiltInImageModel() const {
+  const auto* vector_icon =
+      VectorIcon(image_model_dict_->FindDict(kVectorIcon)).GetVectorIcon();
   if (vector_icon) {
     // Returns vector icon.
     return ui::ImageModel::FromVectorIcon(
         *vector_icon, cros_tokens::kCrosSysOnSurface, kIconSize);
   }
 
-  const auto icon = GetBuiltInIconType(image_dict_);
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  const auto resource_id = GetBuiltInImageResourceId(icon);
-  if (resource_id) {
-    gfx::ImageSkia* image =
-        ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
-            resource_id.value());
+  const auto* image = Image(image_model_dict_->FindDict(kImage)).GetImage();
+  if (image) {
+    const gfx::ImageSkia* imageSkia = image->ToImageSkia();
     gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
-        *image, skia::ImageOperations::RESIZE_BEST, kBubbleIconSizeDip);
+        *imageSkia, skia::ImageOperations::RESIZE_BEST, kBubbleIconSizeDip);
     resized_image.EnsureRepsForSupportedScales();
     return ui::ImageModel::FromImageSkia(resized_image);
   }
+  // TODO: b/340895798 - update the error type naming and description.
+  RecordCampaignsManagerError(CampaignsManagerError::kUnrecognizedBuiltInIcon);
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
-  RecordCampaignsManagerError(CampaignsManagerError::kUnrecognizedBuiltInIcon);
-  LOG(ERROR) << "Unrecognized built in icon: "
-             << static_cast<int>(icon.value());
+  LOG(ERROR) << "Unrecognized built in image model.";
   return std::nullopt;
 }
 
diff --git a/chromeos/ash/components/growth/campaigns_model.h b/chromeos/ash/components/growth/campaigns_model.h
index 9ff1fa5..d833cc93 100644
--- a/chromeos/ash/components/growth/campaigns_model.h
+++ b/chromeos/ash/components/growth/campaigns_model.h
@@ -18,6 +18,7 @@
 }  // namespace base
 
 namespace gfx {
+class Image;
 struct VectorIcon;
 }  // namespace gfx
 
@@ -40,7 +41,16 @@
 
 // These values are deserialized from Growth Campaign, so entries should not
 // be renumbered and numeric values should never be reused.
-enum class BuiltInIcon { kRedeem, kContainerApp, kG1 };
+enum class BuiltInVectorIcon { kRedeem = 0, kMaxValue = kRedeem };
+
+// These values are deserialized from Growth Campaign, so entries should not
+// be renumbered and numeric values should never be reused.
+enum class BuiltInImage {
+  kContainerApp = 0,
+  kG1 = 1,
+  kRebuy = 2,
+  kMaxValue = kRebuy
+};
 
 // Supported window anchor element.
 // These values are deserialized from Growth Campaign, so entries should not
@@ -433,7 +443,7 @@
 //
 // The structure looks like:
 // {
-//   "builtInImage": 0
+//  "builtInImage": 0
 // }
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH) Image {
  public:
@@ -442,16 +452,65 @@
   Image& operator=(const Image) = delete;
   ~Image();
 
-  const gfx::VectorIcon* GetVectorIcon() const;
-  const std::optional<ui::ImageModel> GetImage() const;
+  const gfx::Image* GetImage() const;
 
  private:
   // Get built in icon based on the given image data.
-  const std::optional<ui::ImageModel> GetBuiltInIcon() const;
+  const gfx::Image* GetBuiltInImage() const;
 
   raw_ptr<const base::Value::Dict> image_dict_;
 };
 
+// Wrapper around vector icon dictionary.
+//
+// The structure looks like:
+// {
+//  "builtVectorIcon": 0
+// }
+class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH) VectorIcon {
+ public:
+  explicit VectorIcon(const base::Value::Dict* vector_icon_dict);
+  VectorIcon(const VectorIcon&) = delete;
+  VectorIcon& operator=(const VectorIcon) = delete;
+  ~VectorIcon();
+
+  const gfx::VectorIcon* GetVectorIcon() const;
+
+ private:
+  // Get built in icon based on the given image data.
+  const gfx::VectorIcon* GetBuiltInVectorIcon() const;
+
+  raw_ptr<const base::Value::Dict> vector_icon_dict_;
+};
+
+// Wrapper around image model dictionary.
+//
+// The structure looks like:
+// {
+//   "image": {
+//    "builtInImage": 0
+//   }
+// }
+class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH) ImageModel {
+ public:
+  explicit ImageModel(const base::Value::Dict* image_model_dict);
+  ImageModel(const Image&) = delete;
+  ImageModel& operator=(const ImageModel) = delete;
+  ~ImageModel();
+
+  const std::optional<ui::ImageModel> GetImageModel() const;
+
+ private:
+  // Get built in icon based on the given image data.
+  // If given data is referring to an image, the image will be resized to 60 *
+  // 60 so it can be used in the nudge.
+  // TODO: b/340945779 - consider moving the resize logic to
+  // `ShowNudgeActionPerformer`.
+  const std::optional<ui::ImageModel> GetBuiltInImageModel() const;
+
+  raw_ptr<const base::Value::Dict> image_model_dict_;
+};
+
 }  // namespace growth
 
 #endif  // CHROMEOS_ASH_COMPONENTS_GROWTH_CAMPAIGNS_MODEL_H_
diff --git a/chromeos/ash/components/growth/resources/rebuy.png b/chromeos/ash/components/growth/resources/rebuy.png
new file mode 100644
index 0000000..84cafd6
--- /dev/null
+++ b/chromeos/ash/components/growth/resources/rebuy.png
Binary files differ
diff --git a/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.cc b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.cc
index 43723df..11bdf0d 100644
--- a/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.cc
+++ b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.cc
@@ -1095,13 +1095,12 @@
         << "; endpoint_id=" << endpoint_id;
     on_bandwidth_changed_endpoint_ids_v3_.emplace(endpoint_id);
   } else {
-    // TODO(b/325534442): Emit to a metric in the same that v1
-    // `NearbyConnectionsManagerImpl::OnBandwidthChanged()` emits
-    // "Nearby.Share.Medium.ChangedToMedium".
     CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
         << __func__ << ": (V3) Changed to medium=" << bandwidth_info->medium
         << " , quality=" << bandwidth_info->quality
         << "; endpoint_id=" << endpoint_id;
+    base::UmaHistogramEnumeration(
+        "Nearby.Connections.V3.Medium.ChangedToMedium", bandwidth_info->medium);
     current_upgraded_mediums_v3_.insert_or_assign(endpoint_id,
                                                   bandwidth_info->medium);
 
diff --git a/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl_unittest.cc b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl_unittest.cc
index 0df6b02..c16081f 100644
--- a/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl_unittest.cc
+++ b/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -2175,6 +2176,7 @@
 }
 
 TEST_F(NearbyConnectionsManagerImplTest, OnBandwidthChangedV3) {
+  base::HistogramTester histogram_tester;
   mojo::Remote<ConnectionListenerV3> connection_listener_v3_remote;
   mojo::Remote<PayloadListenerV3> payload_listener_v3_remote;
 
@@ -2227,11 +2229,15 @@
       presence_device.GetEndpointId(),
       nearby::connections::mojom::BandwidthInfo::New(BandwidthQuality::kMedium,
                                                      Medium::kBluetooth));
+  histogram_tester.ExpectTotalCount(
+      "Nearby.Connections.V3.Medium.ChangedToMedium", 0);
   connection_listener_v3_remote->OnBandwidthChangedV3(
       presence_device.GetEndpointId(),
       nearby::connections::mojom::BandwidthInfo::New(BandwidthQuality::kHigh,
                                                      Medium::kWebRtc));
   bandwidth_run_loop.Run();
+  histogram_tester.ExpectBucketCount(
+      "Nearby.Connections.V3.Medium.ChangedToMedium", Medium::kWebRtc, 1);
 }
 
 TEST_F(NearbyConnectionsManagerImplTest, PayloadListenerV3RemoteCallbacks) {
diff --git a/chromeos/ash/resources/growth_framework_resources.grdp b/chromeos/ash/resources/growth_framework_resources.grdp
index bf76cb8..caecb619 100644
--- a/chromeos/ash/resources/growth_framework_resources.grdp
+++ b/chromeos/ash/resources/growth_framework_resources.grdp
@@ -3,4 +3,5 @@
   <!-- Resources for Growth Framework. -->
   <include name="IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG" file="../resources/internal/container_app.png" type="BINDATA" />
   <include name="IDR_GROWTH_FRAMEWORK_G1_PNG" file="../components/growth/resources/logo_one_color_2x_web_96dp.png" type="BINDATA" />
+  <include name="IDR_GROWTH_FRAMEWORK_REBUY_PNG" file="../components/growth/resources/rebuy.png" type="BINDATA" />
 </grit-part>
\ No newline at end of file
diff --git a/chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom b/chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom
index 24da67e..408ea0e 100644
--- a/chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom
+++ b/chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom
@@ -274,6 +274,8 @@
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
+// This enum should be kept in sync with the `NearbyConnectionsMedium` enum at
+// //tools/metrics/histograms/metadata/nearby/enums.xml
 enum Medium {
   kUnknown = 0,
   kMdns = 1,
diff --git a/chromeos/ui/frame/default_frame_header.cc b/chromeos/ui/frame/default_frame_header.cc
index c7689b5..9cd1778 100644
--- a/chromeos/ui/frame/default_frame_header.cc
+++ b/chromeos/ui/frame/default_frame_header.cc
@@ -123,8 +123,7 @@
 #endif
   }
 
-  if (::features::IsChromeRefresh2023() &&
-      ShouldApplyDynamicColor(GetTargetWindow())) {
+  if (ShouldApplyDynamicColor(GetTargetWindow())) {
     UpdateCaptionButtonColors(mode() == MODE_ACTIVE
                                   ? ui::kColorSysPrimary
                                   : ui::kColorFrameCaptionButtonUnfocused);
diff --git a/clank b/clank
index abd3cce..aa6befe 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit abd3ccee94ac82bc22053398886ba2dfb5a518fa
+Subproject commit aa6befe0ff74c3a26e310d3e119191e064b6b197
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 3d17be6..6c582b5 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -614,6 +614,7 @@
       "//components/crash/android:java",
       "//components/crash/android:unit_tests",
       "//components/data_sharing/internal:internal_java",
+      "//components/data_sharing/internal:native_test_helper_java",
       "//components/download/internal/common:internal_java",
       "//components/embedder_support/android:native_java_unittests_java",
       "//components/embedder_support/android/metrics:test_support_java",
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index 12408b36..376048c3 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -259,9 +259,9 @@
 
   // Set delay as fido request timeout if available, otherwise set to default.
   base::TimeDelta delay = kDelayForGetUnmaskDetails;
-  if (unmask_details_.fido_request_options.has_value()) {
+  if (!unmask_details_.fido_request_options.empty()) {
     if (std::optional<int> request_timeout =
-            unmask_details_.fido_request_options->FindInt("timeout_millis")) {
+            unmask_details_.fido_request_options.FindInt("timeout_millis")) {
       delay = base::Milliseconds(*request_timeout);
     }
   }
@@ -549,15 +549,15 @@
         return;
       }
 
-      // For virtual cards the |fido_request_option| comes from the
-      // UnmaskResponseDetails while for masked server cards, it comes from the
+      // For virtual cards the `fido_request_options` come from the
+      // UnmaskResponseDetails while for masked server cards, they come from the
       // UnmaskDetails.
       base::Value::Dict fido_request_options;
       std::optional<std::string> context_token;
       if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
         context_token = virtual_card_unmask_response_details_.context_token;
         fido_request_options = std::move(
-            virtual_card_unmask_response_details_.fido_request_options.value());
+            virtual_card_unmask_response_details_.fido_request_options);
       } else {
         CHECK_EQ(card_->record_type(),
                  CreditCard::RecordType::kMaskedServerCard);
@@ -568,13 +568,13 @@
           CHECK(!risk_based_authentication_response_.context_token.empty());
           context_token = risk_based_authentication_response_.context_token;
           fido_request_options = std::move(
-              risk_based_authentication_response_.fido_request_options.value());
+              risk_based_authentication_response_.fido_request_options);
         } else {
           // If risk-based authentication is not available, the response of
           // UnmaskDetails preflight call will be used as the resource of
           // `fido_request_options`.
           fido_request_options =
-              std::move(unmask_details_.fido_request_options.value());
+              std::move(unmask_details_.fido_request_options);
         }
       }
       GetOrCreateFidoAuthenticator()->Authenticate(
@@ -704,22 +704,22 @@
     unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
   } else if (should_register_card_with_fido) {
 #if !BUILDFLAG(IS_IOS)
-    std::optional<base::Value::Dict> request_options = std::nullopt;
-    if (unmask_details_.fido_request_options.has_value()) {
+    base::Value::Dict request_options;
+    if (!unmask_details_.fido_request_options.empty()) {
       // For opted-in user (CVC then FIDO case), request options are returned in
       // unmask detail response.
-      request_options = unmask_details_.fido_request_options->Clone();
-    } else if (response.request_options.has_value()) {
+      request_options = unmask_details_.fido_request_options.Clone();
+    } else if (!response.request_options.empty()) {
       // For Android users, request_options are provided from GetRealPan if the
       // user has chosen to opt-in.
-      request_options = response.request_options->Clone();
+      request_options = response.request_options.Clone();
     }
 
     // Additionally authorizes the card with FIDO. It also delays the form
     // filling.
     GetOrCreateFidoAuthenticator()->Authorize(GetWeakPtr(),
                                               response.card_authorization_token,
-                                              request_options->Clone());
+                                              request_options.Clone());
 #endif
   }
   if (ShouldOfferFidoOptInDialog(response)) {
@@ -738,7 +738,7 @@
 #if BUILDFLAG(IS_ANDROID)
 bool CreditCardAccessManager::ShouldOfferFidoAuth() const {
   if (!unmask_details_.offer_fido_opt_in &&
-      unmask_details_.fido_request_options.has_value()) {
+      !unmask_details_.fido_request_options.empty()) {
     // Server instructed the client to not offer fido because the client is
     // already opted in. This can be verified with the presence of request
     // options in the server response.
@@ -943,7 +943,7 @@
   // by the risk-based authentication call will be used as the indicator about
   // whether the selected card is FIDO authorized.
   if (IsMaskedServerCardRiskBasedAuthAvailable()) {
-    return risk_based_authentication_response_.fido_request_options.has_value();
+    return !risk_based_authentication_response_.fido_request_options.empty();
   }
   DCHECK_NE(unmask_details_.unmask_auth_method,
             AutofillClient::UnmaskAuthMethod::kUnknown);
@@ -957,7 +957,7 @@
   // GetRealPan did not return RequestOptions (user did not specify intent to
   // opt-in) AND flow is not registering a new card, so fill the form
   // directly.
-  if (!response.request_options.has_value() &&
+  if (response.request_options.empty() &&
       unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido) {
     return true;
   }
@@ -989,11 +989,12 @@
   // For Android, we will delay the form filling for both intent-to-opt-in user
   // opting in and opted-in user registering a new card (kCvcThenFido). So we
   // check one more scenario for Android here. If the GetRealPan response
-  // includes |request_options|, that means the user showed intention to opt-in
+  // includes `request_options`, that means the user showed intention to opt-in
   // while unmasking and must complete the challenge before successfully
   // opting-in and filling the form.
-  if (response.request_options.has_value())
+  if (!response.request_options.empty()) {
     return true;
+  }
 #endif
 
   // No conditions to offer FIDO registration are met, so we return false.
@@ -1007,7 +1008,7 @@
   return false;
 #else
   if (!unmask_details_.offer_fido_opt_in &&
-      unmask_details_.fido_request_options.has_value()) {
+      !unmask_details_.fido_request_options.empty()) {
     // Server instructed the client to not offer fido because the client is
     // already opted in. This can be verified with the presence of request
     // options in the server response.
@@ -1285,7 +1286,7 @@
       // GetUnmaskDetails to determine whether the card can be enrolled into
       // FIDO.
       StartAuthenticationFlow(IsFidoAuthEnabled(
-          /*fido_auth_offered=*/response.fido_request_options.has_value() ||
+          /*fido_auth_offered=*/!response.fido_request_options.empty() ||
           unmask_details_.unmask_auth_method ==
               AutofillClient::UnmaskAuthMethod::kFido));
       break;
@@ -1361,7 +1362,7 @@
         /*show_confirmation_before_closing=*/false,
         /*no_interactive_authentication_callback=*/base::OnceClosure());
     StartAuthenticationFlow(
-        IsFidoAuthEnabled(response_details.fido_request_options.has_value()));
+        IsFidoAuthEnabled(!response_details.fido_request_options.empty()));
     return;
   }
 
diff --git a/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h
index ff73cd6f..6cbc49d 100644
--- a/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h
+++ b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h
@@ -44,8 +44,7 @@
       cvc = std::u16string(s);
       return *this;
     }
-    CvcAuthenticationResponse& with_request_options(
-        std::optional<base::Value::Dict> v) {
+    CvcAuthenticationResponse& with_request_options(base::Value::Dict v) {
       request_options = std::move(v);
       return *this;
     }
@@ -56,9 +55,9 @@
     bool did_succeed = false;
     raw_ptr<const CreditCard> card = nullptr;
     // TODO(crbug.com/40927733): Remove CVC.
-    std::u16string cvc = std::u16string();
-    std::optional<base::Value::Dict> request_options;
-    std::string card_authorization_token = std::string();
+    std::u16string cvc;
+    base::Value::Dict request_options;
+    std::string card_authorization_token;
   };
   class Requester {
    public:
diff --git a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.cc b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.cc
index ad7d204b..48793000 100644
--- a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.cc
+++ b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.cc
@@ -23,10 +23,10 @@
   result = other.result;
   error_dialog_context = other.error_dialog_context;
   card = other.card;
-  if (other.fido_request_options.has_value()) {
-    fido_request_options = other.fido_request_options->Clone();
+  if (other.fido_request_options.empty()) {
+    fido_request_options.clear();
   } else {
-    fido_request_options.reset();
+    fido_request_options = other.fido_request_options.Clone();
   }
   context_token = other.context_token;
   return *this;
@@ -141,9 +141,9 @@
       // The Payments server indicates further authentication is required.
       response.result =
           RiskBasedAuthenticationResponse::Result::kAuthenticationRequired;
-      if (response_details.fido_request_options.has_value()) {
+      if (!response_details.fido_request_options.empty()) {
         response.fido_request_options =
-            response_details.fido_request_options->Clone();
+            response_details.fido_request_options.Clone();
       }
       response.context_token = response_details.context_token;
 
diff --git a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h
index 1252c414..34181ec7 100644
--- a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h
+++ b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h
@@ -72,7 +72,7 @@
     // The items below will be set when the server response was successful and
     // the card's real pan was not returned from the server side.
     // FIDO request options will be present only when FIDO is available.
-    std::optional<base::Value::Dict> fido_request_options;
+    base::Value::Dict fido_request_options;
     // Stores the latest version of the context token, passed between Payments
     // calls and unmodified by Chrome.
     std::string context_token;
diff --git a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator_unittest.cc b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator_unittest.cc
index 1aa6e481..d2adfbe 100644
--- a/components/autofill/core/browser/payments/credit_card_risk_based_authenticator_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_risk_based_authenticator_unittest.cc
@@ -233,8 +233,8 @@
                 Result::kAuthenticationRequired);
   EXPECT_FALSE(
       requester_->risk_based_authentication_response().card.has_value());
-  EXPECT_TRUE(requester_->risk_based_authentication_response()
-                  .fido_request_options.has_value());
+  EXPECT_FALSE(requester_->risk_based_authentication_response()
+                   .fido_request_options.empty());
 
   // Expect the metrics are logged correctly.
   histogram_tester.ExpectUniqueSample(
diff --git a/components/autofill/core/browser/payments/payments_network_interface.cc b/components/autofill/core/browser/payments/payments_network_interface.cc
index 1e59ad5e..521c006 100644
--- a/components/autofill/core/browser/payments/payments_network_interface.cc
+++ b/components/autofill/core/browser/payments/payments_network_interface.cc
@@ -55,10 +55,10 @@
     const PaymentsNetworkInterface::UnmaskDetails& other) {
   unmask_auth_method = other.unmask_auth_method;
   offer_fido_opt_in = other.offer_fido_opt_in;
-  if (other.fido_request_options.has_value()) {
-    fido_request_options = other.fido_request_options->Clone();
+  if (other.fido_request_options.empty()) {
+    fido_request_options.clear();
   } else {
-    fido_request_options.reset();
+    fido_request_options = other.fido_request_options.Clone();
   }
   fido_eligible_card_ids = other.fido_eligible_card_ids;
   return *this;
@@ -125,10 +125,10 @@
   dcvv = other.dcvv;
   expiration_month = other.expiration_month;
   expiration_year = other.expiration_year;
-  if (other.fido_request_options.has_value()) {
-    fido_request_options = other.fido_request_options->Clone();
+  if (other.fido_request_options.empty()) {
+    fido_request_options.clear();
   } else {
-    fido_request_options.reset();
+    fido_request_options = other.fido_request_options.Clone();
   }
   card_authorization_token = other.card_authorization_token;
   card_unmask_challenge_options = other.card_unmask_challenge_options;
diff --git a/components/autofill/core/browser/payments/payments_network_interface.h b/components/autofill/core/browser/payments/payments_network_interface.h
index 0ece929..34b3943e 100644
--- a/components/autofill/core/browser/payments/payments_network_interface.h
+++ b/components/autofill/core/browser/payments/payments_network_interface.h
@@ -97,7 +97,7 @@
     bool offer_fido_opt_in = false;
     // Public Key Credential Request Options required for authentication.
     // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions
-    std::optional<base::Value::Dict> fido_request_options;
+    base::Value::Dict fido_request_options;
     // Set of credit cards ids that are eligible for FIDO Authentication.
     std::set<std::string> fido_eligible_card_ids;
   };
@@ -170,7 +170,7 @@
     std::string expiration_year;
     // Challenge required for authorizing user for FIDO authentication for
     // future card unmasking.
-    std::optional<base::Value::Dict> fido_request_options;
+    base::Value::Dict fido_request_options;
     // An opaque token used to logically chain consecutive UnmaskCard and
     // OptChange calls together.
     std::string card_authorization_token;
diff --git a/components/autofill/core/browser/payments/payments_network_interface_unittest.cc b/components/autofill/core/browser/payments/payments_network_interface_unittest.cc
index 378b2a3a..e986a6d8 100644
--- a/components/autofill/core/browser/payments/payments_network_interface_unittest.cc
+++ b/components/autofill/core/browser/payments/payments_network_interface_unittest.cc
@@ -710,9 +710,9 @@
   EXPECT_EQ(AutofillClient::PaymentsRpcResult::kSuccess, result_);
   EXPECT_EQ("fake_context_token", unmask_response_details()->context_token);
   // Verify the FIDO request challenge is correctly parsed.
-  EXPECT_EQ("fake_fido_challenge",
-            *unmask_response_details()->fido_request_options->FindString(
-                "challenge"));
+  EXPECT_EQ(
+      "fake_fido_challenge",
+      *unmask_response_details()->fido_request_options.FindString("challenge"));
   // Verify the three challenge options are two sms challenge options and one
   // cvc challenge option, and fields can be correctly parsed.
   ASSERT_EQ(3u,
@@ -767,9 +767,9 @@
   EXPECT_EQ(AutofillClient::PaymentsRpcResult::kSuccess, result_);
   EXPECT_EQ("fake_context_token", unmask_response_details()->context_token);
   // Verify the FIDO request challenge is correctly parsed.
-  EXPECT_EQ("fake_fido_challenge",
-            *unmask_response_details()->fido_request_options->FindString(
-                "challenge"));
+  EXPECT_EQ(
+      "fake_fido_challenge",
+      *unmask_response_details()->fido_request_options.FindString("challenge"));
   // Verify that the unknow new challenge option type won't break the parsing.
   // We ignore the unknown new type, and only return the supported challenge
   // option.
diff --git a/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc b/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
index 770fdba..7601a4e 100644
--- a/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
+++ b/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
@@ -548,7 +548,7 @@
 
 bool UnmaskCardRequest::CanPerformVirtualCardAuth() {
   return !response_details_.context_token.empty() &&
-         (response_details_.fido_request_options.has_value() ||
+         (!response_details_.fido_request_options.empty() ||
           !response_details_.card_unmask_challenge_options.empty() ||
           !response_details_.flow_status.empty());
 }
diff --git a/components/autofill/core/browser/payments/payments_requests/unmask_card_request_unittest.cc b/components/autofill/core/browser/payments/payments_requests/unmask_card_request_unittest.cc
index 7323db2..29cce0f 100644
--- a/components/autofill/core/browser/payments/payments_requests/unmask_card_request_unittest.cc
+++ b/components/autofill/core/browser/payments/payments_requests/unmask_card_request_unittest.cc
@@ -157,7 +157,7 @@
   EXPECT_EQ("fake_context_token", response_details.context_token);
   // Verify the FIDO request challenge is correctly parsed.
   EXPECT_EQ("fake_fido_challenge",
-            *response_details.fido_request_options->FindString("challenge"));
+            *response_details.fido_request_options.FindString("challenge"));
 
   // Verify that the response is considered complete.
   EXPECT_TRUE(GetRequest()->IsResponseComplete());
@@ -315,7 +315,7 @@
   EXPECT_EQ("fake_context_token", response_details.context_token);
   // Verify the FIDO request challenge is correctly parsed.
   EXPECT_EQ("fake_fido_challenge",
-            *response_details.fido_request_options->FindString("challenge"));
+            *response_details.fido_request_options.FindString("challenge"));
 
   // Verify the six (or seven, if 3DS is enabled) challenge options are two SMS
   // OTP challenge options, two CVC challenge options, two email OTP challenge
diff --git a/components/autofill/core/common/BUILD.gn b/components/autofill/core/common/BUILD.gn
index c2137e8..cbe34bb 100644
--- a/components/autofill/core/common/BUILD.gn
+++ b/components/autofill/core/common/BUILD.gn
@@ -21,8 +21,6 @@
     "autofill_internals/logging_scope.h",
     "autofill_l10n_util.cc",
     "autofill_l10n_util.h",
-    "autofill_payments_features.cc",
-    "autofill_payments_features.h",
     "autofill_prefs.cc",
     "autofill_prefs.h",
     "autofill_regex_constants.h",
@@ -94,6 +92,8 @@
   sources = [
     "autofill_features.cc",
     "autofill_features.h",
+    "autofill_payments_features.cc",
+    "autofill_payments_features.h",
   ]
 
   deps = [
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 7e523e9..9e8d09b 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -8,8 +8,6 @@
 
 namespace autofill::features {
 
-// Features
-
 // When enabled, Android N+ devices will be supported for FIDO authentication.
 BASE_FEATURE(kAutofillEnableAndroidNKeyForFidoAuthentication,
              "AutofillEnableAndroidNKeyForFidoAuthentication",
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index 6f782e3..b80e5930 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -5,68 +5,97 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PAYMENTS_FEATURES_H_
 #define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PAYMENTS_FEATURES_H_
 
+#include "base/component_export.h"
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "build/build_config.h"
 
-namespace autofill {
-namespace features {
+namespace autofill::features {
 
 // All features in alphabetical order.
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableAndroidNKeyForFidoAuthentication);
-BASE_DECLARE_FEATURE(kAutofillEnableCardArtImage);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableCardArtImage);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableCardArtServerSideStretching);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableCardBenefitsForAmericanExpress);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableCardBenefitsForCapitalOne);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableCardBenefitsSync);
-BASE_DECLARE_FEATURE(kAutofillEnableCardProductName);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableCardProductName);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableCvcStorageAndFilling);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableFIDOProgressDialog);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableFpanRiskBasedAuthentication);
 
 #if BUILDFLAG(IS_ANDROID)
-BASE_DECLARE_FEATURE(kAutofillEnableLocalIban);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableLocalIban);
 #endif
 
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableMerchantDomainInUnmaskCardRequest);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableMovingGPayLogoToTheRightOnDesktop);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableMovingGPayLogoToTheRightOnClank);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableNewCardArtAndNetworkImages);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableOffersInClankKeyboardAccessory);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnablePrefetchingRiskDataForRetrieval);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableRemadeDownstreamMetrics);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableSaveCardLoadingAndConfirmation);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableSaveCardLocalSaveFallback);
-BASE_DECLARE_FEATURE(kAutofillEnableServerIban);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableServerIban);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableStickyManualFallbackForCards);
 
 #if BUILDFLAG(IS_ANDROID)
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableSyncingOfPixBankAccounts);
 #endif
 
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVcn3dsAuthentication);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVcnEnrollLoadingAndConfirmation);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVcnGrayOutForMerchantOptOut);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVirtualCardEnrollMetricsLogger);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVerveCardSupport);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillEnableVirtualCardMetadata);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillParseVcnCardOnFileStandaloneCvcFields);
 #if BUILDFLAG(IS_ANDROID)
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillSkipAndroidBottomSheetForIban);
 #endif
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillUpdateChromeSettingsLinkToGPayWeb);
-BASE_DECLARE_FEATURE(kAutofillUpstream);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillUpstream);
 
 #if BUILDFLAG(IS_IOS)
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillUseTwoDotsForLastFourDigits);
-BASE_DECLARE_FEATURE(kAutofillEnableVirtualCards);
+COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillEnableVirtualCards);
 #endif
 
 // Return whether a [No thanks] button and new messaging is shown in the save
 // card bubbles. This will be called only on desktop platforms.
+COMPONENT_EXPORT(AUTOFILL)
 bool ShouldShowImprovedUserConsentForCreditCardSave();
 
-}  // namespace features
-}  // namespace autofill
+}  // namespace autofill::features
 
 #endif  // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PAYMENTS_FEATURES_H_
diff --git a/components/commerce_strings.grdp b/components/commerce_strings.grdp
index 06c100d..1db60df 100644
--- a/components/commerce_strings.grdp
+++ b/components/commerce_strings.grdp
@@ -540,4 +540,7 @@
   <message name="IDS_PRODUCT_SPECIFICATIONS_DELETE" translateable="false" desc="The title of the menu item for deleting the product specification list.">
     Delete product specification list
   </message>
+  <message name="IDS_PRODUCT_SPECIFICATIONS_REMOVE_COLUMN" translateable="false" desc="The title of the menu item for removing a column in the product specification list.">
+    Remove column
+  </message>
 </grit-part>
diff --git a/components/compose/core/browser/compose_metrics.h b/components/compose/core/browser/compose_metrics.h
index bfcc19e..c8b2761b 100644
--- a/components/compose/core/browser/compose_metrics.h
+++ b/components/compose/core/browser/compose_metrics.h
@@ -208,6 +208,26 @@
   kMaxValue = kRequestError,
 };
 
+// The output metric for the proactive nudge segmentation model. Represents what
+// effect the nudge had on the user's engagement. Stored as
+// "Compose.ProactiveNudge.DerivedEngagement".
+enum class ProactiveNudgeDerivedEngagement {
+  // The user didn't interact with the nudge.
+  kIgnored,
+  // The user disabled the nudge on this site using the three-dot menu.
+  kNudgeDisabledOnSingleSite,
+  // The user disabled the nudge on all sites using the three-dot menu.
+  kNudgeDisabledOnAllSites,
+  // User clicked the nudge, but didn't press generate in Compose.
+  kOpenedComposeMinimalUse,
+  // User clicked the nudge, pressed generate at least once, but didn't accept
+  // the suggestion.
+  kGeneratedComposeSuggestion,
+  // User clicked, pressed generate, and accepted a suggestion.
+  kAcceptedComposeSuggestion,
+  kMaxValue = kAcceptedComposeSuggestion,
+};
+
 // Struct containing event and logging information for an individual
 // |ComposeSession|.
 struct ComposeSessionEvents {
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index b8e93830..9c235677 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -376,6 +376,7 @@
     "java/src/org/chromium/net/impl/CronetEngineBase.java",
     "java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
     "java/src/org/chromium/net/impl/CronetExceptionImpl.java",
+    "java/src/org/chromium/net/impl/CronetMetrics.java",
     "java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
     "java/src/org/chromium/net/impl/Preconditions.java",
     "java/src/org/chromium/net/impl/QuicExceptionImpl.java",
@@ -506,7 +507,6 @@
     "java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
     "java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
     "java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-    "java/src/org/chromium/net/impl/CronetMetrics.java",
     "java/src/org/chromium/net/impl/CronetRequestCommon.java",
     "java/src/org/chromium/net/impl/CronetUploadDataStream.java",
     "java/src/org/chromium/net/impl/CronetUrlRequest.java",
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamCallbackWrapper.java b/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamCallbackWrapper.java
index 22c99c73..2643f94 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamCallbackWrapper.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamCallbackWrapper.java
@@ -13,6 +13,9 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresExtension;
 
+import org.chromium.net.CronetException;
+import org.chromium.net.RequestFinishedInfo;
+
 import java.nio.ByteBuffer;
 import java.util.Objects;
 
@@ -83,7 +86,17 @@
             android.net.http.UrlResponseInfo urlResponseInfo) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForBidirectionalStream(urlResponseInfo);
-        mBackend.onSucceeded(mWrappedStream, specializedResponseInfo);
+        try {
+            mBackend.onSucceeded(mWrappedStream, specializedResponseInfo);
+        } finally {
+            // In a scenario where this throws, the side effect is that it will be propagated to
+            // CronetUrlRequest as an error in the callback and mess with the FinalUserCallbackThrew
+            // metrics. Because we catch most the exceptions, this side effect is negligible enough
+            // to
+            // not try to figure out a workaround.
+            mWrappedStream.maybeReportMetrics(
+                    RequestFinishedInfo.SUCCEEDED, specializedResponseInfo, null);
+        }
     }
 
     @Override
@@ -93,10 +106,15 @@
             HttpException e) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForBidirectionalStream(urlResponseInfo);
-        mBackend.onFailed(
-                mWrappedStream,
-                specializedResponseInfo,
-                CronetExceptionTranslationUtils.translateCheckedAndroidCronetException(e));
+        CronetException exception =
+                CronetExceptionTranslationUtils.translateCheckedAndroidCronetException(e);
+        try {
+            mBackend.onFailed(mWrappedStream, specializedResponseInfo, exception);
+        } finally {
+            // See comment in onSucceeded.
+            mWrappedStream.maybeReportMetrics(
+                    RequestFinishedInfo.FAILED, specializedResponseInfo, exception);
+        }
     }
 
     @Override
@@ -105,7 +123,13 @@
             @Nullable android.net.http.UrlResponseInfo urlResponseInfo) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForBidirectionalStream(urlResponseInfo);
-        mBackend.onCanceled(mWrappedStream, specializedResponseInfo);
+        try {
+            mBackend.onCanceled(mWrappedStream, specializedResponseInfo);
+        } finally {
+            // See comment in onSucceeded.
+            mWrappedStream.maybeReportMetrics(
+                    RequestFinishedInfo.CANCELED, specializedResponseInfo, null);
+        }
     }
 
     void setStream(AndroidBidirectionalStreamWrapper stream) {
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamWrapper.java b/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamWrapper.java
index fe10826ca..d9593dc3 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamWrapper.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/AndroidBidirectionalStreamWrapper.java
@@ -9,14 +9,30 @@
 
 import androidx.annotation.RequiresExtension;
 
+import org.chromium.net.CronetEngine;
+import org.chromium.net.CronetException;
+import org.chromium.net.RequestFinishedInfo;
+import org.chromium.net.RequestFinishedInfo.Listener;
+
 import java.nio.ByteBuffer;
+import java.util.Collection;
 
 @RequiresExtension(extension = EXT_API_LEVEL, version = EXT_VERSION)
 class AndroidBidirectionalStreamWrapper extends org.chromium.net.ExperimentalBidirectionalStream {
     private final android.net.http.BidirectionalStream mBackend;
+    private final AndroidHttpEngineWrapper mEngine;
+    private final String mInitialUrl;
+    private final Collection<Object> mAnnotations;
 
-    private AndroidBidirectionalStreamWrapper(android.net.http.BidirectionalStream backend) {
+    AndroidBidirectionalStreamWrapper(
+            android.net.http.BidirectionalStream backend,
+            AndroidHttpEngineWrapper engine,
+            String url,
+            Collection<Object> annotations) {
         this.mBackend = backend;
+        this.mEngine = engine;
+        this.mInitialUrl = url;
+        this.mAnnotations = annotations;
     }
 
     @Override
@@ -58,10 +74,36 @@
      */
     static AndroidBidirectionalStreamWrapper createAndAddToCallback(
             android.net.http.BidirectionalStream backend,
-            AndroidBidirectionalStreamCallbackWrapper callback) {
+            AndroidBidirectionalStreamCallbackWrapper callback,
+            AndroidHttpEngineWrapper engine,
+            String url,
+            Collection<Object> annotations) {
         AndroidBidirectionalStreamWrapper wrappedStream =
-                new AndroidBidirectionalStreamWrapper(backend);
+                new AndroidBidirectionalStreamWrapper(backend, engine, url, annotations);
         callback.setStream(wrappedStream);
         return wrappedStream;
     }
+
+    /**
+     * HttpEngine does not support {@link CronetEngine#addRequestFinishedListener(Listener)}). To
+     * preserve compatibility with Cronet users who use that method, we reimplement the
+     * functionality with a placeholder CronetMetric object. This reimplementation allows for {@link
+     * RequestFinishedInfo.Listener#onRequestFinished(RequestFinishedInfo)} to be called so as not
+     * to unknowingly block users who might be depending on that API call.
+     */
+    void maybeReportMetrics(
+            @RequestFinishedInfoImpl.FinishedReason int finishedReason,
+            AndroidUrlResponseInfoWrapper responseInfo,
+            CronetException exception) {
+        final RequestFinishedInfo requestInfo =
+                new RequestFinishedInfoImpl(
+                        mInitialUrl,
+                        mAnnotations,
+                        new CronetMetrics(
+                                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, false, -1, -1),
+                        finishedReason,
+                        responseInfo,
+                        exception);
+        mEngine.reportRequestFinished(requestInfo, null);
+    }
 }
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/AndroidHttpEngineWrapper.java b/components/cronet/android/java/src/org/chromium/net/impl/AndroidHttpEngineWrapper.java
index 0e65ce2..6c70d26 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/AndroidHttpEngineWrapper.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/AndroidHttpEngineWrapper.java
@@ -10,6 +10,7 @@
 import android.net.Network;
 import android.net.http.HttpEngine;
 import android.os.Process;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresExtension;
@@ -28,18 +29,25 @@
 import java.net.URLStreamHandlerFactory;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 
 @RequiresExtension(extension = EXT_API_LEVEL, version = EXT_VERSION)
 class AndroidHttpEngineWrapper extends CronetEngineBase {
+    private static final String TAG = "HttpEngineWrapper";
     private final HttpEngine mBackend;
     private final int mThreadPriority;
     // The thread that priority has been set on.
     private Thread mPriorityThread;
+    private final Map<
+                    RequestFinishedInfo.Listener, VersionSafeCallbacks.RequestFinishedInfoListener>
+            mFinishedListenerMap = Collections.synchronizedMap(new HashMap<>());
 
     public AndroidHttpEngineWrapper(HttpEngine backend, int threadPriority) {
         mBackend = backend;
@@ -117,6 +125,45 @@
     }
 
     @Override
+    public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {
+        mFinishedListenerMap.put(
+                listener, new VersionSafeCallbacks.RequestFinishedInfoListener(listener));
+    }
+
+    @Override
+    public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {
+        mFinishedListenerMap.remove(listener);
+    }
+
+    void reportRequestFinished(
+            RequestFinishedInfo requestInfo,
+            VersionSafeCallbacks.RequestFinishedInfoListener extraRequestListener) {
+        ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners =
+                new ArrayList<>();
+        synchronized (mFinishedListenerMap) {
+            currentListeners.addAll(mFinishedListenerMap.values());
+        }
+        if (extraRequestListener != null) {
+            currentListeners.add(extraRequestListener);
+        }
+        for (final VersionSafeCallbacks.RequestFinishedInfoListener listener : currentListeners) {
+            try {
+                listener.getExecutor()
+                        .execute(
+                                () -> {
+                                    try {
+                                        listener.onRequestFinished(requestInfo);
+                                    } catch (Exception e) {
+                                        Log.e(TAG, "Exception thrown from observation task", e);
+                                    }
+                                });
+            } catch (RejectedExecutionException failException) {
+                Log.e(TAG, "Exception posting task to executor", failException);
+            }
+        }
+    }
+
+    @Override
     public org.chromium.net.ExperimentalBidirectionalStream createBidirectionalStream(
             String url,
             BidirectionalStream.Callback callback,
@@ -125,7 +172,7 @@
             List<Entry<String, String>> requestHeaders,
             @StreamPriority int priority,
             boolean delayRequestHeadersUntilFirstFlush,
-            Collection<Object> requestAnnotations /* not in HttpEngine */,
+            Collection<Object> requestAnnotations,
             boolean trafficStatsTagSet,
             int trafficStatsTag,
             boolean trafficStatsUidSet,
@@ -151,7 +198,7 @@
         }
 
         return AndroidBidirectionalStreamWrapper.createAndAddToCallback(
-                streamBuilder.build(), wrappedCallback);
+                streamBuilder.build(), wrappedCallback, this, url, requestAnnotations);
     }
 
     @Override
@@ -160,7 +207,7 @@
             UrlRequest.Callback callback,
             Executor executor,
             @RequestPriority int priority,
-            Collection<Object> requestAnnotations /* not in HttpEngine */,
+            Collection<Object> requestAnnotations,
             boolean disableCache,
             boolean disableConnectionMigration /* not in HttpEngine */,
             boolean allowDirectExecutor,
@@ -168,7 +215,7 @@
             int trafficStatsTag,
             boolean trafficStatsUidSet,
             int trafficStatsUid,
-            @Nullable RequestFinishedInfo.Listener requestFinishedListener /* not in HttpEngine */,
+            @Nullable RequestFinishedInfo.Listener requestFinishedListener,
             @Idempotency int idempotency /* not in HttpEngine */,
             long networkHandle,
             String method,
@@ -201,7 +248,12 @@
         }
 
         return AndroidUrlRequestWrapper.createAndAddToCallback(
-                requestBuilder.build(), wrappedCallback);
+                requestBuilder.build(),
+                wrappedCallback,
+                this,
+                url,
+                requestAnnotations,
+                requestFinishedListener);
     }
 
     private Network getNetwork(long networkHandle) {
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestCallbackWrapper.java b/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestCallbackWrapper.java
index 15051b97..10d699f 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestCallbackWrapper.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestCallbackWrapper.java
@@ -13,6 +13,9 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresExtension;
 
+import org.chromium.net.CronetException;
+import org.chromium.net.RequestFinishedInfo;
+
 import java.nio.ByteBuffer;
 import java.util.Objects;
 
@@ -83,7 +86,17 @@
             android.net.http.UrlRequest request, android.net.http.UrlResponseInfo info) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
-        mBackend.onSucceeded(mWrappedRequest, specializedResponseInfo);
+        try {
+            mBackend.onSucceeded(mWrappedRequest, specializedResponseInfo);
+        } finally {
+            // In a scenario where this throws, the side effect is that it will be propagated to
+            // CronetUrlRequest as an error in the callback and mess with the FinalUserCallbackThrew
+            // metrics. Because we catch most the exceptions, this side effect is negligible enough
+            // to
+            // not try to figure out a workaround.
+            mWrappedRequest.maybeReportMetrics(
+                    RequestFinishedInfo.SUCCEEDED, specializedResponseInfo, null);
+        }
     }
 
     @Override
@@ -93,10 +106,15 @@
             HttpException error) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
-        mBackend.onFailed(
-                mWrappedRequest,
-                specializedResponseInfo,
-                CronetExceptionTranslationUtils.translateCheckedAndroidCronetException(error));
+        CronetException translatedException =
+                CronetExceptionTranslationUtils.translateCheckedAndroidCronetException(error);
+        try {
+            mBackend.onFailed(mWrappedRequest, specializedResponseInfo, translatedException);
+        } finally {
+            // See comment in onSucceeded.
+            mWrappedRequest.maybeReportMetrics(
+                    RequestFinishedInfo.FAILED, specializedResponseInfo, translatedException);
+        }
     }
 
     @Override
@@ -105,7 +123,13 @@
             @Nullable android.net.http.UrlResponseInfo info) {
         AndroidUrlResponseInfoWrapper specializedResponseInfo =
                 AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
-        mBackend.onCanceled(mWrappedRequest, specializedResponseInfo);
+        try {
+            mBackend.onCanceled(mWrappedRequest, specializedResponseInfo);
+        } finally {
+            // See comment in onSucceeded.
+            mWrappedRequest.maybeReportMetrics(
+                    RequestFinishedInfo.CANCELED, specializedResponseInfo, null);
+        }
     }
 
     void setRequest(AndroidUrlRequestWrapper request) {
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestWrapper.java b/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestWrapper.java
index 2c67875..b2e11bb 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestWrapper.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestWrapper.java
@@ -9,14 +9,37 @@
 
 import androidx.annotation.RequiresExtension;
 
+import org.chromium.net.CronetEngine;
+import org.chromium.net.CronetException;
+import org.chromium.net.RequestFinishedInfo;
+import org.chromium.net.RequestFinishedInfo.Listener;
+
 import java.nio.ByteBuffer;
+import java.util.Collection;
 
 @RequiresExtension(extension = EXT_API_LEVEL, version = EXT_VERSION)
 class AndroidUrlRequestWrapper extends org.chromium.net.ExperimentalUrlRequest {
     private final android.net.http.UrlRequest mBackend;
+    private final AndroidHttpEngineWrapper mEngine;
+    private final String mInitialUrl;
+    private final Collection<Object> mAnnotations;
+    private final VersionSafeCallbacks.RequestFinishedInfoListener mRequestFinishedInfoListener;
 
-    AndroidUrlRequestWrapper(android.net.http.UrlRequest backend) {
+    AndroidUrlRequestWrapper(
+            android.net.http.UrlRequest backend,
+            AndroidHttpEngineWrapper engine,
+            String url,
+            Collection<Object> annotations,
+            RequestFinishedInfo.Listener requestFinishedInfoListener) {
         this.mBackend = backend;
+        this.mEngine = engine;
+        this.mInitialUrl = url;
+        this.mAnnotations = annotations;
+        this.mRequestFinishedInfoListener =
+                requestFinishedInfoListener == null
+                        ? null
+                        : new VersionSafeCallbacks.RequestFinishedInfoListener(
+                                requestFinishedInfoListener);
     }
 
     @Override
@@ -57,9 +80,39 @@
      * @return the wrapped request
      */
     static AndroidUrlRequestWrapper createAndAddToCallback(
-            android.net.http.UrlRequest backend, AndroidUrlRequestCallbackWrapper callback) {
-        AndroidUrlRequestWrapper wrappedRequest = new AndroidUrlRequestWrapper(backend);
+            android.net.http.UrlRequest backend,
+            AndroidUrlRequestCallbackWrapper callback,
+            AndroidHttpEngineWrapper engine,
+            String url,
+            Collection<Object> annotations,
+            RequestFinishedInfo.Listener requestFinishedInfoListener) {
+        AndroidUrlRequestWrapper wrappedRequest =
+                new AndroidUrlRequestWrapper(
+                        backend, engine, url, annotations, requestFinishedInfoListener);
         callback.setRequest(wrappedRequest);
         return wrappedRequest;
     }
+
+    /**
+     * HttpEngine does not support {@link CronetEngine#addRequestFinishedListener(Listener)}). To
+     * preserve compatibility with Cronet users who use that method, we reimplement the
+     * functionality with a placeholder CronetMetric object. This reimplementation allows for {@link
+     * RequestFinishedInfo.Listener#onRequestFinished(RequestFinishedInfo)} to be called so as not
+     * to unknowingly block users who might be depending on that API call.
+     */
+    void maybeReportMetrics(
+            @RequestFinishedInfoImpl.FinishedReason int finishedReason,
+            AndroidUrlResponseInfoWrapper responseInfo,
+            CronetException exception) {
+        final RequestFinishedInfo requestInfo =
+                new RequestFinishedInfoImpl(
+                        mInitialUrl,
+                        mAnnotations,
+                        new CronetMetrics(
+                                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, false, -1, -1),
+                        finishedReason,
+                        responseInfo,
+                        exception);
+        mEngine.reportRequestFinished(requestInfo, mRequestFinishedInfoListener);
+    }
 }
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
index 35d482d1..770f55f 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
@@ -971,10 +971,9 @@
             final RequestFinishedInfo requestInfo,
             RefCountDelegate inflightCallbackCount,
             VersionSafeCallbacks.RequestFinishedInfoListener extraRequestFinishedInfoListener) {
-        List<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
+        List<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners = new ArrayList<>();
         synchronized (mFinishedListenerLock) {
-            if (mFinishedListenerMap.isEmpty() && extraRequestFinishedInfoListener == null) return;
-            currentListeners = new ArrayList<>(mFinishedListenerMap.values());
+            currentListeners.addAll(mFinishedListenerMap.values());
         }
         if (extraRequestFinishedInfoListener != null) {
             currentListeners.add(extraRequestFinishedInfoListener);
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamQuicTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamQuicTest.java
index 706658c..22d7bdd 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamQuicTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamQuicTest.java
@@ -98,6 +98,7 @@
     @Test
     @SmallTest
     public void testSimplePost() throws Exception {
+        CronetImplementation implementationUnderTest = mTestRule.implementationUnderTest();
         String path = "/simple.txt";
         String quicURL = QuicTestServer.getServerURL() + path;
         TestBidirectionalStreamCallback callback = new TestBidirectionalStreamCallback();
@@ -124,9 +125,11 @@
         requestFinishedListener.blockUntilDone();
         Date endTime = new Date();
         RequestFinishedInfo finishedInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(finishedInfo, quicURL, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                implementationUnderTest, finishedInfo, quicURL, startTime, endTime);
         assertThat(finishedInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(finishedInfo.getMetrics(), startTime, endTime, true);
+        MetricsTestUtil.checkHasConnectTiming(
+                implementationUnderTest, finishedInfo.getMetrics(), startTime, endTime, true);
         assertThat(finishedInfo.getAnnotations()).containsExactly("request annotation", this);
         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
         assertThat(callback.mResponseAsString)
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
index a867020..1bd6ddd 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
@@ -327,10 +327,8 @@
 
     @Test
     @SmallTest
-    @IgnoreFor(
-            implementations = {CronetImplementation.AOSP_PLATFORM},
-            reason = "RequedFinishedListener is not available in AOSP")
     public void testPostWithFinishedListener() throws Exception {
+        CronetImplementation implementationUnderTest = mTestRule.implementationUnderTest();
         String url = Http2TestServer.getEchoStreamUrl();
         TestBidirectionalStreamCallback callback = new TestBidirectionalStreamCallback();
         callback.addWriteData("Test String".getBytes());
@@ -355,9 +353,11 @@
         requestFinishedListener.blockUntilDone();
         Date endTime = new Date();
         RequestFinishedInfo finishedInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(finishedInfo, url, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                implementationUnderTest, finishedInfo, url, startTime, endTime);
         assertThat(finishedInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(finishedInfo.getMetrics(), startTime, endTime, true);
+        MetricsTestUtil.checkHasConnectTiming(
+                implementationUnderTest, finishedInfo.getMetrics(), startTime, endTime, true);
         assertThat(finishedInfo.getAnnotations()).containsExactly("request annotation", this);
         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
         assertThat(callback.mResponseAsString).isEqualTo("Test String1234567890woot!");
@@ -1316,11 +1316,6 @@
     /** Checks that the buffer is updated correctly, when starting at an offset. */
     @Test
     @SmallTest
-    @IgnoreFor(
-            implementations = {CronetImplementation.AOSP_PLATFORM},
-            reason =
-                    "crbug.com/1494845: Relies on finished listener synchronization which isn't"
-                            + " available in AOSP")
     public void testSimpleGetBufferUpdates() throws Exception {
         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
         mCronetEngine.addRequestFinishedListener(requestFinishedListener);
@@ -1494,22 +1489,29 @@
         if (failureStep != ResponseStep.ON_STREAM_READY) {
             assertThat(callback.getResponseInfo()).isNotNull();
         }
-        // Check metrics information.
-        if (failureStep == ResponseStep.ON_RESPONSE_STARTED
-                || failureStep == ResponseStep.ON_READ_COMPLETED
-                || failureStep == ResponseStep.ON_TRAILERS) {
-            // For steps after response headers are received, there will be
-            // connect timing metrics.
-            MetricsTestUtil.checkTimingMetrics(metrics, startTime, endTime);
-            MetricsTestUtil.checkHasConnectTiming(metrics, startTime, endTime, true);
-            assertThat(metrics.getSentByteCount()).isGreaterThan(0L);
-            assertThat(metrics.getReceivedByteCount()).isGreaterThan(0L);
-        } else if (failureStep == ResponseStep.ON_STREAM_READY) {
-            assertThat(metrics.getRequestStart()).isNotNull();
-            MetricsTestUtil.assertAfter(metrics.getRequestStart(), startTime);
-            assertThat(metrics.getRequestEnd()).isNotNull();
-            MetricsTestUtil.assertAfter(endTime, metrics.getRequestEnd());
-            MetricsTestUtil.assertAfter(metrics.getRequestEnd(), metrics.getRequestStart());
+        CronetImplementation implementationUnderTest = mTestRule.implementationUnderTest();
+        // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+        // metrics. Don't bother checking timing metrics for AOSP whether it passes or not.
+        if (implementationUnderTest != CronetImplementation.AOSP_PLATFORM) {
+            // Check metrics information.
+            if (failureStep == ResponseStep.ON_RESPONSE_STARTED
+                    || failureStep == ResponseStep.ON_READ_COMPLETED
+                    || failureStep == ResponseStep.ON_TRAILERS) {
+                // For steps after response headers are received, there will be
+                // connect timing metrics.
+                MetricsTestUtil.checkTimingMetrics(
+                        implementationUnderTest, metrics, startTime, endTime);
+                MetricsTestUtil.checkHasConnectTiming(
+                        implementationUnderTest, metrics, startTime, endTime, true);
+                assertThat(metrics.getSentByteCount()).isGreaterThan(0L);
+                assertThat(metrics.getReceivedByteCount()).isGreaterThan(0L);
+            } else if (failureStep == ResponseStep.ON_STREAM_READY) {
+                assertThat(metrics.getRequestStart()).isNotNull();
+                MetricsTestUtil.assertAfter(metrics.getRequestStart(), startTime);
+                assertThat(metrics.getRequestEnd()).isNotNull();
+                MetricsTestUtil.assertAfter(endTime, metrics.getRequestEnd());
+                MetricsTestUtil.assertAfter(metrics.getRequestEnd(), metrics.getRequestStart());
+            }
         }
         assertThat(callback.mError != null).isEqualTo(expectError);
         assertThat(callback.mOnErrorCalled).isEqualTo(expectError);
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
index 219c309..92bd66a 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
@@ -1073,7 +1073,9 @@
     @SmallTest
     @IgnoreFor(
             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
-            reason = "Request finished listeners are only supported by native Cronet")
+            reason =
+                    "Request finished listeners not supported in Fallback, Active request count not"
+                            + " supported by AOSP.")
     public void testGetActiveRequestCountOnRequestFinishedListener() throws Exception {
         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
@@ -1098,7 +1100,9 @@
     @SmallTest
     @IgnoreFor(
             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
-            reason = "Request finished listeners are only supported by native Cronet")
+            reason =
+                    "Request finished listeners not supported in Fallback, Active request count not"
+                            + " supported by AOSP.")
     public void testGetActiveRequestCountOnThrowingRequestFinishedListener() throws Exception {
         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
@@ -1124,7 +1128,9 @@
     @SmallTest
     @IgnoreFor(
             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
-            reason = "Request finished listeners are only supported by native Cronet")
+            reason =
+                    "Request finished listeners not supported in Fallback, Active request count not"
+                            + " supported by AOSP.")
     public void testGetActiveRequestCountOnThrowingEngineRequestFinishedListener()
             throws Exception {
         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
@@ -1149,7 +1155,9 @@
     @SmallTest
     @IgnoreFor(
             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
-            reason = "Request finished listeners are only supported by native Cronet")
+            reason =
+                    "Request finished listeners not supported in Fallback, Active request count not"
+                            + " supported by AOSP.")
     public void testGetActiveRequestCountOnEngineRequestFinishedListener() throws Exception {
         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/MetricsTestUtil.java b/components/cronet/android/test/javatests/src/org/chromium/net/MetricsTestUtil.java
index 7e39d582..ac50ec0b 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/MetricsTestUtil.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/MetricsTestUtil.java
@@ -7,6 +7,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import org.chromium.net.CronetTestRule.CronetImplementation;
+
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.NoSuchElementException;
@@ -47,14 +49,21 @@
     }
 
     /**
-     * Check existence of all the timing metrics that apply to most test requests,
-     * except those that come from net::LoadTimingInfo::ConnectTiming.
-     * Also check some timing differences, focusing on things we can't check with asserts in the
-     * CronetMetrics constructor.
-     * Don't check push times here.
+     * Check existence of all the timing metrics that apply to most test requests, except those that
+     * come from net::LoadTimingInfo::ConnectTiming. Also check some timing differences, focusing on
+     * things we can't check with asserts in the CronetMetrics constructor. Don't check push times
+     * here.
      */
     public static void checkTimingMetrics(
-            RequestFinishedInfo.Metrics metrics, Date startTime, Date endTime) {
+            CronetImplementation implementationUnderTest,
+            RequestFinishedInfo.Metrics metrics,
+            Date startTime,
+            Date endTime) {
+        if (implementationUnderTest == CronetImplementation.AOSP_PLATFORM) {
+            // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+            // metrics. Don't bother checking timing metrics for AOSP whether it passes or not.
+            return;
+        }
         assertThat(metrics.getRequestStart()).isNotNull();
         assertAfter(metrics.getRequestStart(), startTime);
         assertThat(metrics.getSendingStart()).isNotNull();
@@ -73,7 +82,16 @@
      * except SSL times in the case of non-https requests.
      */
     public static void checkHasConnectTiming(
-            RequestFinishedInfo.Metrics metrics, Date startTime, Date endTime, boolean isSsl) {
+            CronetImplementation implementationUnderTest,
+            RequestFinishedInfo.Metrics metrics,
+            Date startTime,
+            Date endTime,
+            boolean isSsl) {
+        if (implementationUnderTest == CronetImplementation.AOSP_PLATFORM) {
+            // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+            // metrics. Don't bother checking timing metrics for AOSP whether it passes or not.
+            return;
+        }
         assertThat(metrics.getDnsStart()).isNotNull();
         assertAfter(metrics.getDnsStart(), startTime);
         assertThat(metrics.getDnsEnd()).isNotNull();
@@ -94,7 +112,14 @@
     }
 
     /** Check that the timing metrics from net::LoadTimingInfo::ConnectTiming don't exist. */
-    public static void checkNoConnectTiming(RequestFinishedInfo.Metrics metrics) {
+    public static void checkNoConnectTiming(
+            CronetImplementation implementationUnderTest, RequestFinishedInfo.Metrics metrics) {
+        if (implementationUnderTest == CronetImplementation.AOSP_PLATFORM) {
+            // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+            // metrics. Although the checks below would pass, generally, don't bother checking
+            // timing metrics for AOSP whether it passes or not.
+            return;
+        }
         assertThat(metrics.getDnsStart()).isNull();
         assertThat(metrics.getDnsEnd()).isNull();
         assertThat(metrics.getSslStart()).isNull();
@@ -103,9 +128,15 @@
         assertThat(metrics.getConnectEnd()).isNull();
     }
 
-    /** Check that RequestFinishedInfo looks the way it should look for a normal successful request. */
+    /**
+     * Check that RequestFinishedInfo looks the way it should look for a normal successful request.
+     */
     public static void checkRequestFinishedInfo(
-            RequestFinishedInfo info, String url, Date startTime, Date endTime) {
+            CronetImplementation implementationUnderTest,
+            RequestFinishedInfo info,
+            String url,
+            Date startTime,
+            Date endTime) {
         assertWithMessage("RequestFinishedInfo.Listener must be called").that(info).isNotNull();
         assertThat(info.getUrl()).isEqualTo(url);
         assertThat(info.getResponseInfo()).isNotNull();
@@ -114,15 +145,20 @@
         assertWithMessage("RequestFinishedInfo.getMetrics() must not be null")
                 .that(metrics)
                 .isNotNull();
-        // Check old (deprecated) timing metrics
-        assertThat(metrics.getTotalTimeMs()).isAtLeast(0L);
-        assertThat(metrics.getTotalTimeMs()).isAtLeast(metrics.getTtfbMs());
-        // Check new timing metrics
-        checkTimingMetrics(metrics, startTime, endTime);
-        assertThat(metrics.getPushStart()).isNull();
-        assertThat(metrics.getPushEnd()).isNull();
-        // Check data use metrics
-        assertThat(metrics.getSentByteCount()).isGreaterThan(0L);
-        assertThat(metrics.getReceivedByteCount()).isGreaterThan(0L);
+
+        // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+        // metrics. Don't bother checking timing metrics for AOSP whether it passes or not.
+        if (implementationUnderTest != CronetImplementation.AOSP_PLATFORM) {
+            // Check old (deprecated) timing metrics
+            assertThat(metrics.getTotalTimeMs()).isAtLeast(0L);
+            assertThat(metrics.getTotalTimeMs()).isAtLeast(metrics.getTtfbMs());
+            // Check new timing metrics
+            checkTimingMetrics(implementationUnderTest, metrics, startTime, endTime);
+            assertThat(metrics.getPushStart()).isNull();
+            assertThat(metrics.getPushEnd()).isNull();
+            // Check data use metrics
+            assertThat(metrics.getSentByteCount()).isGreaterThan(0L);
+            assertThat(metrics.getReceivedByteCount()).isGreaterThan(0L);
+        }
     }
 }
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
index 1aff795..b3f3ac9d9 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
@@ -249,6 +249,7 @@
     @SmallTest
     public void testMetricsWithQuic() throws Exception {
         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
+        CronetImplementation implementationUnderTest = mTestRule.implementationUnderTest();
         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
         cronetEngine.addRequestFinishedListener(requestFinishedListener);
 
@@ -267,9 +268,11 @@
         assertIsQuic(callback.getResponseInfoWithChecks());
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, quicURL, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                implementationUnderTest, requestInfo, quicURL, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, true);
+        MetricsTestUtil.checkHasConnectTiming(
+                implementationUnderTest, requestInfo.getMetrics(), startTime, endTime, true);
 
         // Second request should use the same connection and not have ConnectTiming numbers
         callback = new TestUrlRequestCallback();
@@ -286,9 +289,10 @@
         assertIsQuic(callback.getResponseInfoWithChecks());
 
         requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, quicURL, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                implementationUnderTest, requestInfo, quicURL, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkNoConnectTiming(requestInfo.getMetrics());
+        MetricsTestUtil.checkNoConnectTiming(implementationUnderTest, requestInfo.getMetrics());
 
         cronetEngine.shutdown();
     }
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
index c4963d4f..1cee9e6 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/RequestFinishedInfoTest.java
@@ -38,12 +38,13 @@
 @DoNotBatch(reason = "crbug/1459563")
 @RunWith(AndroidJUnit4.class)
 @IgnoreFor(
-        implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
-        reason = "Fallback and AOSP implementations do not support RequestFinishedListeners")
+        implementations = {CronetImplementation.FALLBACK},
+        reason = "Fallback implementation does not support RequestFinishedListener.")
 public class RequestFinishedInfoTest {
     @Rule public final CronetTestRule mTestRule = CronetTestRule.withAutomaticEngineStartup();
 
     private String mUrl;
+    private CronetImplementation mImplementationUnderTest;
 
     // A subclass of TestRequestFinishedListener to additionally assert that UrlRequest.Callback's
     // terminal callbacks have been invoked at the time of onRequestFinished().
@@ -64,12 +65,12 @@
             super.onRequestFinished(requestInfo);
         }
     }
-    ;
 
     @Before
     public void setUp() throws Exception {
         NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext());
         mUrl = NativeTestServer.getFileURL("/echo?status=200");
+        mImplementationUnderTest = mTestRule.implementationUnderTest();
     }
 
     @After
@@ -135,9 +136,11 @@
         Date endTime = new Date();
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
         assertThat(requestInfo.getAnnotations()).containsExactly("request annotation", this);
     }
 
@@ -172,9 +175,11 @@
         Date endTime = new Date();
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
         assertThat(requestInfo.getAnnotations()).containsExactly("request annotation", this);
     }
 
@@ -207,15 +212,21 @@
         RequestFinishedInfo firstRequestInfo = firstListener.getRequestInfo();
         RequestFinishedInfo secondRequestInfo = secondListener.getRequestInfo();
 
-        MetricsTestUtil.checkRequestFinishedInfo(firstRequestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, firstRequestInfo, mUrl, startTime, endTime);
         assertThat(firstRequestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
         MetricsTestUtil.checkHasConnectTiming(
-                firstRequestInfo.getMetrics(), startTime, endTime, false);
+                mImplementationUnderTest, firstRequestInfo.getMetrics(), startTime, endTime, false);
 
-        MetricsTestUtil.checkRequestFinishedInfo(secondRequestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, secondRequestInfo, mUrl, startTime, endTime);
         assertThat(secondRequestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
         MetricsTestUtil.checkHasConnectTiming(
-                secondRequestInfo.getMetrics(), startTime, endTime, false);
+                mImplementationUnderTest,
+                secondRequestInfo.getMetrics(),
+                startTime,
+                endTime,
+                false);
 
         assertThat(firstRequestInfo.getAnnotations()).containsExactly("request annotation", this);
         assertThat(secondRequestInfo.getAnnotations()).containsExactly("request annotation", this);
@@ -259,22 +270,28 @@
         assertWithMessage("RequestFinishedInfo.getMetrics() must not be null")
                 .that(metrics)
                 .isNotNull();
-        // The failure is occasionally fast enough that time reported is 0, so just check for null
-        assertThat(metrics.getTotalTimeMs()).isNotNull();
-        assertThat(metrics.getTtfbMs()).isNull();
 
-        // Check the timing metrics
-        assertThat(metrics.getRequestStart()).isNotNull();
-        MetricsTestUtil.assertAfter(metrics.getRequestStart(), startTime);
-        MetricsTestUtil.checkNoConnectTiming(metrics);
-        assertThat(metrics.getSendingStart()).isNull();
-        assertThat(metrics.getSendingEnd()).isNull();
-        assertThat(metrics.getResponseStart()).isNull();
-        assertThat(metrics.getRequestEnd()).isNotNull();
-        MetricsTestUtil.assertAfter(endTime, metrics.getRequestEnd());
-        MetricsTestUtil.assertAfter(metrics.getRequestEnd(), metrics.getRequestStart());
-        assertThat(metrics.getSentByteCount()).isEqualTo(0);
-        assertThat(metrics.getReceivedByteCount()).isEqualTo(0);
+        // RequestFinishedInfoListener HttpEngineWrapper implementation has placeholder ie null
+        // metrics. Don't bother checking timing metrics for AOSP whether it passes or not.
+        if (mImplementationUnderTest != CronetImplementation.AOSP_PLATFORM) {
+            // The failure is occasionally fast enough that time reported is 0, so just check for
+            // null
+            assertThat(metrics.getTotalTimeMs()).isNotNull();
+            assertThat(metrics.getTtfbMs()).isNull();
+
+            // Check the timing metrics
+            assertThat(metrics.getRequestStart()).isNotNull();
+            MetricsTestUtil.assertAfter(metrics.getRequestStart(), startTime);
+            MetricsTestUtil.checkNoConnectTiming(mImplementationUnderTest, metrics);
+            assertThat(metrics.getSendingStart()).isNull();
+            assertThat(metrics.getSendingEnd()).isNull();
+            assertThat(metrics.getResponseStart()).isNull();
+            assertThat(metrics.getRequestEnd()).isNotNull();
+            MetricsTestUtil.assertAfter(endTime, metrics.getRequestEnd());
+            MetricsTestUtil.assertAfter(metrics.getRequestEnd(), metrics.getRequestStart());
+            assertThat(metrics.getSentByteCount()).isEqualTo(0);
+            assertThat(metrics.getReceivedByteCount()).isEqualTo(0);
+        }
     }
 
     @Test
@@ -401,9 +418,11 @@
         Date endTime = new Date();
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.CANCELED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
 
         assertThat(requestInfo.getAnnotations()).containsExactly("request annotation", this);
     }
@@ -437,7 +456,13 @@
         // Empty headers are invalid and will cause start() to throw an exception.
         UrlRequest request = urlRequestBuilder.addHeader("", "").build();
         IllegalArgumentException e = assertThrows(IllegalArgumentException.class, request::start);
-        assertThat(e).hasMessageThat().isEqualTo("Invalid header with headername: ");
+        if (mImplementationUnderTest == CronetImplementation.AOSP_PLATFORM
+                && !mTestRule.isRunningInAOSP()) {
+            // TODO(b/307234565): Remove check once chromium Android 14 emulator has latest changes.
+            assertThat(e).hasMessageThat().isEqualTo("Invalid header =");
+        } else {
+            assertThat(e).hasMessageThat().isEqualTo("Invalid header with headername: ");
+        }
     }
 
     @Test
@@ -491,8 +516,8 @@
         assertThat(metrics.getResponseStart()).isEqualTo(new Date(responseStart));
         assertThat(metrics.getRequestEnd()).isEqualTo(new Date(requestEnd));
         assertThat(metrics.getSocketReused()).isEqualTo(socketReused);
-        assertThat((long) metrics.getSentByteCount()).isEqualTo(sentByteCount);
-        assertThat((long) metrics.getReceivedByteCount()).isEqualTo(receivedByteCount);
+        assertThat(metrics.getSentByteCount()).isEqualTo(sentByteCount);
+        assertThat(metrics.getReceivedByteCount()).isEqualTo(receivedByteCount);
     }
 
     @Test
@@ -523,9 +548,11 @@
         Date endTime = new Date();
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
         assertThat(requestInfo.getAnnotations()).containsExactly("request annotation", this);
     }
 
@@ -562,9 +589,11 @@
         requestFinishedListener.blockUntilDone();
         Date endTime = new Date();
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
         // Check that annotation got updated in onSucceeded() callback.
         assertThat(requestInfo.getAnnotations()).containsExactly(requestAnnotation);
         assertThat(requestAnnotation.get()).isTrue();
@@ -684,9 +713,11 @@
         Date endTime = new Date();
 
         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
-        MetricsTestUtil.checkRequestFinishedInfo(requestInfo, mUrl, startTime, endTime);
+        MetricsTestUtil.checkRequestFinishedInfo(
+                mImplementationUnderTest, requestInfo, mUrl, startTime, endTime);
         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.CANCELED);
-        MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, false);
+        MetricsTestUtil.checkHasConnectTiming(
+                mImplementationUnderTest, requestInfo.getMetrics(), startTime, endTime, false);
 
         assertThat(requestInfo.getAnnotations()).containsExactly("request annotation", this);
     }
diff --git a/components/data_sharing/BUILD.gn b/components/data_sharing/BUILD.gn
index 2e28c21..fe4d5c1 100644
--- a/components/data_sharing/BUILD.gn
+++ b/components/data_sharing/BUILD.gn
@@ -4,6 +4,11 @@
 
 import("//build/buildflag_header.gni")
 
+if (is_android) {
+  import("//build/config/android/config.gni")
+  import("//build/config/android/rules.gni")
+}
+
 group("unit_tests") {
   testonly = true
 
@@ -36,3 +41,16 @@
     "//url",
   ]
 }
+
+if (is_android) {
+  android_library("test_support_java") {
+    sources = [ "test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java" ]
+    deps = [
+      "//base:base_java",
+      "//components/data_sharing/public:public_java",
+      "//third_party/androidx:androidx_annotation_annotation_java",
+      "//third_party/jni_zero:jni_zero_java",
+      "//url:gurl_java",
+    ]
+  }
+}
diff --git a/components/data_sharing/internal/BUILD.gn b/components/data_sharing/internal/BUILD.gn
index ee9159fa..ab0711d 100644
--- a/components/data_sharing/internal/BUILD.gn
+++ b/components/data_sharing/internal/BUILD.gn
@@ -48,13 +48,18 @@
 
   if (is_android) {
     sources += [
+      "android/data_sharing_conversion_bridge.cc",
+      "android/data_sharing_conversion_bridge.h",
       "android/data_sharing_network_loader_android.cc",
       "android/data_sharing_network_loader_android.h",
       "android/data_sharing_service_android.cc",
       "android/data_sharing_service_android.h",
     ]
 
-    deps += [ ":jni_headers" ]
+    deps += [
+      ":jni_headers",
+      "//components/data_sharing/public:jni_headers",
+    ]
   }
 }
 
@@ -68,11 +73,16 @@
     "data_sharing_network_loader_impl_unittest.cc",
     "data_sharing_service_impl_unittest.cc",
     "empty_data_sharing_service_unittest.cc",
+    "fake_data_sharing_sdk_delegate.cc",
+    "fake_data_sharing_sdk_delegate.h",
     "group_data_proto_utils_unittest.cc",
   ]
 
   if (is_android) {
-    sources += [ "android/data_sharing_network_loader_android_unittest.cc" ]
+    sources += [
+      "android/data_sharing_network_loader_android_unittest.cc",
+      "android/data_sharing_service_android_unittest.cc",
+    ]
   }
 
   deps = [
@@ -90,6 +100,8 @@
 
   if (is_android) {
     deps += [
+      ":native_test_helper_java",
+      ":test_jni_headers",
       "//components/data_sharing:test_support",
       "//url",
     ]
@@ -102,10 +114,15 @@
       "//chrome/android:chrome_all_java",
       "//components:*",
     ]
-    srcjar_deps = [ ":jni_headers" ]
+    srcjar_deps = [
+      ":jni_headers",
+      "//components/data_sharing/public:jni_headers",
+    ]
     sources = [
+      "android/java/src/org/chromium/components/data_sharing/DataSharingConversionBridge.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingNetworkLoaderImpl.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java",
+      "android/java/src/org/chromium/components/data_sharing/ObserverBridge.java",
     ]
 
     deps = [
@@ -125,11 +142,29 @@
     ]
 
     sources = [
+      "android/java/src/org/chromium/components/data_sharing/DataSharingConversionBridge.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingNetworkLoaderImpl.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java",
+      "android/java/src/org/chromium/components/data_sharing/ObserverBridge.java",
     ]
   }
 
+  android_library("native_test_helper_java") {
+    srcjar_deps = [ ":test_jni_headers" ]
+    testonly = true
+    sources = [ "android/java/src/org/chromium/components/data_sharing/TestServiceObserver.java" ]
+    deps = [
+      "//base:base_java",
+      "//components/data_sharing/public:public_java",
+      "//third_party/jni_zero:jni_zero_java",
+    ]
+  }
+
+  generate_jni("test_jni_headers") {
+    testonly = true
+    sources = [ "android/java/src/org/chromium/components/data_sharing/TestServiceObserver.java" ]
+  }
+
   robolectric_library("data_sharing_junit_tests") {
     sources = [ "android/java/src/org/chromium/components/data_sharing/DataSharingNetworkLoaderImplTest.java" ]
 
diff --git a/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc b/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc
new file mode 100644
index 0000000..c47ae53
--- /dev/null
+++ b/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc
@@ -0,0 +1,102 @@
+// 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/data_sharing/internal/android/data_sharing_conversion_bridge.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "components/data_sharing/internal/jni_headers/DataSharingConversionBridge_jni.h"
+#include "components/data_sharing/public/group_data.h"
+#include "components/data_sharing/public/jni_headers/GroupData_jni.h"
+#include "components/data_sharing/public/jni_headers/GroupMember_jni.h"
+#include "url/android/gurl_android.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+using base::android::ToTypedJavaArrayOfObjects;
+
+namespace data_sharing {
+
+// static
+ScopedJavaLocalRef<jobject> DataSharingConversionBridge::CreateJavaGroupMember(
+    JNIEnv* env,
+    const GroupMember& member) {
+  return Java_GroupMember_createGroupMember(
+      env, ConvertUTF8ToJavaString(env, member.gaia_id),
+      ConvertUTF8ToJavaString(env, member.display_name),
+      ConvertUTF8ToJavaString(env, member.email), static_cast<int>(member.role),
+      url::GURLAndroid::FromNativeGURL(env, member.avatar_url));
+}
+
+// static
+ScopedJavaLocalRef<jobject> DataSharingConversionBridge::CreateJavaGroupData(
+    JNIEnv* env,
+    const GroupData& group_data) {
+  std::vector<ScopedJavaLocalRef<jobject>> j_members;
+  j_members.reserve(group_data.members.size());
+  for (const GroupMember& member : group_data.members) {
+    j_members.push_back(CreateJavaGroupMember(env, member));
+  }
+  return Java_GroupData_createGroupData(
+      env, ConvertUTF8ToJavaString(env, group_data.group_id),
+      ConvertUTF8ToJavaString(env, group_data.display_name),
+      ToTypedJavaArrayOfObjects(
+          env, base::make_span(j_members),
+          org_chromium_components_data_1sharing_GroupMember_clazz(env)));
+}
+
+// static
+ScopedJavaLocalRef<jobject>
+DataSharingConversionBridge::CreateGroupDataOrFailureOutcome(
+    JNIEnv* env,
+    const DataSharingService::GroupDataOrFailureOutcome& data) {
+  ScopedJavaLocalRef<jobject> j_group_data;
+  DataSharingService::PeopleGroupActionFailure failure =
+      DataSharingService::PeopleGroupActionFailure::kUnknown;
+  if (data.has_value()) {
+    j_group_data = CreateJavaGroupData(env, data.value());
+  } else {
+    failure = data.error();
+  }
+  return Java_DataSharingConversionBridge_createGroupDataOrFailureOutcome(
+      env, j_group_data, static_cast<int>(failure));
+}
+
+// static
+ScopedJavaLocalRef<jobject>
+DataSharingConversionBridge::CreateGroupDataSetOrFailureOutcome(
+    JNIEnv* env,
+    const DataSharingService::GroupsDataSetOrFailureOutcome& data) {
+  std::vector<ScopedJavaLocalRef<jobject>> j_groups_data;
+  DataSharingService::PeopleGroupActionFailure failure =
+      DataSharingService::PeopleGroupActionFailure::kUnknown;
+  if (data.has_value()) {
+    for (const GroupData& group : data.value()) {
+      j_groups_data.push_back(CreateJavaGroupData(env, group));
+    }
+  } else {
+    failure = data.error();
+  }
+  ScopedJavaLocalRef<jobjectArray> j_group_array;
+  if (!j_groups_data.empty()) {
+    j_group_array = ToTypedJavaArrayOfObjects(
+        env, base::make_span(j_groups_data),
+        org_chromium_components_data_1sharing_GroupData_clazz(env));
+  }
+  return Java_DataSharingConversionBridge_createGroupDataSetOrFailureOutcome(
+      env, j_group_array, static_cast<int>(failure));
+}
+
+// static
+ScopedJavaLocalRef<jobject>
+DataSharingConversionBridge::CreatePeopleGroupActionOutcome(JNIEnv* env,
+                                                            int value) {
+  return Java_DataSharingConversionBridge_createPeopleGroupActionOutcome(
+      AttachCurrentThread(), value);
+}
+
+}  // namespace data_sharing
diff --git a/components/data_sharing/internal/android/data_sharing_conversion_bridge.h b/components/data_sharing/internal/android/data_sharing_conversion_bridge.h
new file mode 100644
index 0000000..ca753b8
--- /dev/null
+++ b/components/data_sharing/internal/android/data_sharing_conversion_bridge.h
@@ -0,0 +1,55 @@
+// 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_DATA_SHARING_INTERNAL_ANDROID_DATA_SHARING_CONVERSION_BRIDGE_H_
+#define COMPONENTS_DATA_SHARING_INTERNAL_ANDROID_DATA_SHARING_CONVERSION_BRIDGE_H_
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/data_sharing/public/data_sharing_service.h"
+#include "components/data_sharing/public/group_data.h"
+
+using base::android::ScopedJavaLocalRef;
+
+namespace data_sharing {
+
+// Utility for JNI conversion of the data types used by the service.
+class DataSharingConversionBridge {
+ public:
+  // Creates an object of org.chromium.components.data_sharing.GroupMember.
+  static ScopedJavaLocalRef<jobject> CreateJavaGroupMember(
+      JNIEnv* env,
+      const GroupMember& member);
+
+  // Creates an object of org.chromium.components.data_sharing.GroupData.
+  static ScopedJavaLocalRef<jobject> CreateJavaGroupData(
+      JNIEnv* env,
+      const GroupData& result);
+
+  // Creates an object of
+  // org.chromium.components.data_sharing.DataSharingService.
+  //   GroupDataOrFailureOutcome.
+  static ScopedJavaLocalRef<jobject> CreateGroupDataOrFailureOutcome(
+      JNIEnv* env,
+      const DataSharingService::GroupDataOrFailureOutcome& data);
+
+  // Creates an object of
+  // org.chromium.components.data_sharing.DataSharingService.
+  //   GroupsDataSetOrFailureOutcome.
+  static ScopedJavaLocalRef<jobject> CreateGroupDataSetOrFailureOutcome(
+      JNIEnv* env,
+      const DataSharingService::GroupsDataSetOrFailureOutcome& data);
+
+  // Creates an Integer object that identifies the generated enum
+  // PeopleGroupActionOutcome. The object is useful since Java Callback does not
+  // support primitive type int.
+  static ScopedJavaLocalRef<jobject> CreatePeopleGroupActionOutcome(JNIEnv* env,
+                                                                    int value);
+};
+
+}  // namespace data_sharing
+
+#endif  // COMPONENTS_DATA_SHARING_INTERNAL_ANDROID_DATA_SHARING_CONVERSION_BRIDGE_H_
diff --git a/components/data_sharing/internal/android/data_sharing_service_android.cc b/components/data_sharing/internal/android/data_sharing_service_android.cc
index 9dcd1896..c0b0397 100644
--- a/components/data_sharing/internal/android/data_sharing_service_android.cc
+++ b/components/data_sharing/internal/android/data_sharing_service_android.cc
@@ -5,14 +5,24 @@
 #include "components/data_sharing/internal/android/data_sharing_service_android.h"
 
 #include <memory>
+#include <string>
 
+#include "base/android/callback_android.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/scoped_observation.h"
+#include "components/data_sharing/internal/android/data_sharing_conversion_bridge.h"
 #include "components/data_sharing/internal/android/data_sharing_network_loader_android.h"
 #include "components/data_sharing/internal/jni_headers/DataSharingServiceImpl_jni.h"
+#include "components/data_sharing/internal/jni_headers/ObserverBridge_jni.h"
 #include "components/data_sharing/public/data_sharing_service.h"
 
 using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
 using base::android::JavaParamRef;
+using base::android::RunObjectCallbackAndroid;
 using base::android::ScopedJavaGlobalRef;
 using base::android::ScopedJavaLocalRef;
 
@@ -21,8 +31,93 @@
 
 const char kDataSharingServiceBridgeKey[] = "data_sharing_service_bridge";
 
+void RunGroupsDataSetOrFailureOutcomeCallback(
+    const JavaRef<jobject>& j_callback,
+    const DataSharingService::GroupsDataSetOrFailureOutcome& result) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_result =
+      DataSharingConversionBridge::CreateGroupDataSetOrFailureOutcome(env,
+                                                                      result);
+  RunObjectCallbackAndroid(j_callback, j_result);
+}
+
+void RunGroupDataOrFailureOutcomeCallback(
+    const JavaRef<jobject>& j_callback,
+    const DataSharingService::GroupDataOrFailureOutcome& result) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_result =
+      DataSharingConversionBridge::CreateGroupDataOrFailureOutcome(env, result);
+  RunObjectCallbackAndroid(j_callback, j_result);
+}
+
+void RunPeopleGroupActionOutcomeCallback(
+    const JavaRef<jobject>& j_callback,
+    DataSharingService::PeopleGroupActionOutcome result) {
+  ScopedJavaLocalRef<jobject> j_result =
+      DataSharingConversionBridge::CreatePeopleGroupActionOutcome(
+          AttachCurrentThread(), static_cast<int>(result));
+  RunObjectCallbackAndroid(j_callback, j_result);
+}
+
 }  // namespace
 
+// Native counterpart of Java ObserverBridge. Observes the native service and
+// sends notifications to the Java bridge.
+class DataSharingServiceAndroid::GroupDataObserverBridge
+    : public DataSharingService::Observer {
+ public:
+  GroupDataObserverBridge(
+      DataSharingService* data_sharing_service,
+      DataSharingServiceAndroid* data_sharing_service_android);
+  ~GroupDataObserverBridge() override;
+
+  GroupDataObserverBridge(const GroupDataObserverBridge&) = delete;
+  GroupDataObserverBridge& operator=(const GroupDataObserverBridge&) = delete;
+
+  // DataSharingService::Observer impl:
+  void OnGroupChanged(const GroupData& group_data) override;
+  void OnGroupAdded(const GroupData& group_data) override;
+  void OnGroupRemoved(const std::string& group_id) override;
+
+ private:
+  ScopedJavaLocalRef<jobject> java_obj_;
+  base::ScopedObservation<DataSharingService, DataSharingService::Observer>
+      scoped_obs_{this};
+};
+
+DataSharingServiceAndroid::GroupDataObserverBridge::GroupDataObserverBridge(
+    DataSharingService* data_sharing_service,
+    DataSharingServiceAndroid* data_sharing_service_android) {
+  java_obj_ = data_sharing_service_android->GetJavaObserverBridge();
+  scoped_obs_.Observe(data_sharing_service);
+}
+
+DataSharingServiceAndroid::GroupDataObserverBridge::~GroupDataObserverBridge() =
+    default;
+
+void DataSharingServiceAndroid::GroupDataObserverBridge::OnGroupChanged(
+    const GroupData& group_data) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_group =
+      DataSharingConversionBridge::CreateJavaGroupData(env, group_data);
+  Java_ObserverBridge_onGroupChanged(env, java_obj_, j_group);
+}
+
+void DataSharingServiceAndroid::GroupDataObserverBridge::OnGroupAdded(
+    const GroupData& group_data) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_group =
+      DataSharingConversionBridge::CreateJavaGroupData(env, group_data);
+  Java_ObserverBridge_onGroupAdded(env, java_obj_, j_group);
+}
+
+void DataSharingServiceAndroid::GroupDataObserverBridge::OnGroupRemoved(
+    const std::string& group_id) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_ObserverBridge_onGroupRemoved(env, java_obj_,
+                                     ConvertUTF8ToJavaString(env, group_id));
+}
+
 // This function is declared in data_sharing_service.h and
 // should be linked in to any binary using
 // DataSharingService::GetJavaObject.
@@ -50,6 +145,8 @@
   java_obj_.Reset(env, Java_DataSharingServiceImpl_create(
                            env, reinterpret_cast<int64_t>(this))
                            .obj());
+  observer_bridge_ =
+      std::make_unique<GroupDataObserverBridge>(data_sharing_service, this);
 }
 
 DataSharingServiceAndroid::~DataSharingServiceAndroid() {
@@ -57,6 +154,68 @@
   Java_DataSharingServiceImpl_clearNativePtr(env, java_obj_);
 }
 
+void DataSharingServiceAndroid::ReadAllGroups(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->ReadAllGroups(
+      base::BindOnce(&RunGroupsDataSetOrFailureOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
+void DataSharingServiceAndroid::ReadGroup(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& group_id,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->ReadGroup(
+      ConvertJavaStringToUTF8(env, group_id),
+      base::BindOnce(&RunGroupDataOrFailureOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
+void DataSharingServiceAndroid::CreateGroup(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& group_name,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->CreateGroup(
+      ConvertJavaStringToUTF8(env, group_name),
+      base::BindOnce(&RunGroupDataOrFailureOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
+void DataSharingServiceAndroid::DeleteGroup(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& group_id,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->DeleteGroup(
+      ConvertJavaStringToUTF8(env, group_id),
+      base::BindOnce(&RunPeopleGroupActionOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
+void DataSharingServiceAndroid::InviteMember(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& group_id,
+    const JavaParamRef<jstring>& invitee_email,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->InviteMember(
+      ConvertJavaStringToUTF8(env, group_id),
+      ConvertJavaStringToUTF8(env, invitee_email),
+      base::BindOnce(&RunPeopleGroupActionOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
+void DataSharingServiceAndroid::RemoveMember(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& group_id,
+    const JavaParamRef<jstring>& member_email,
+    const JavaParamRef<jobject>& j_callback) {
+  data_sharing_service_->RemoveMember(
+      ConvertJavaStringToUTF8(env, group_id),
+      ConvertJavaStringToUTF8(env, member_email),
+      base::BindOnce(&RunPeopleGroupActionOutcomeCallback,
+                     ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
 bool DataSharingServiceAndroid::IsEmptyService(
     JNIEnv* env,
     const JavaParamRef<jobject>& jcaller) {
@@ -72,4 +231,9 @@
   return ScopedJavaLocalRef<jobject>(java_obj_);
 }
 
+ScopedJavaLocalRef<jobject> DataSharingServiceAndroid::GetJavaObserverBridge() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  return Java_DataSharingServiceImpl_getObserverBridge(env, GetJavaObject());
+}
+
 }  // namespace data_sharing
diff --git a/components/data_sharing/internal/android/data_sharing_service_android.h b/components/data_sharing/internal/android/data_sharing_service_android.h
index 721ecbe..a3b5a7a 100644
--- a/components/data_sharing/internal/android/data_sharing_service_android.h
+++ b/components/data_sharing/internal/android/data_sharing_service_android.h
@@ -26,12 +26,39 @@
   explicit DataSharingServiceAndroid(DataSharingService* service);
   ~DataSharingServiceAndroid() override;
 
+  // DataSharingService Java API methods, implemented by native service:
+  void ReadAllGroups(JNIEnv* env, const JavaParamRef<jobject>& j_callback);
+  void ReadGroup(JNIEnv* env,
+                 const JavaParamRef<jstring>& group_id,
+                 const JavaParamRef<jobject>& j_callback);
+  void CreateGroup(JNIEnv* env,
+                   const JavaParamRef<jstring>& group_name,
+                   const JavaParamRef<jobject>& j_callback);
+  void DeleteGroup(JNIEnv* env,
+                   const JavaParamRef<jstring>& group_id,
+                   const JavaParamRef<jobject>& j_callback);
+  void InviteMember(JNIEnv* env,
+                    const JavaParamRef<jstring>& group_id,
+                    const JavaParamRef<jstring>& invitee_email,
+                    const JavaParamRef<jobject>& j_callback);
+  void RemoveMember(JNIEnv* env,
+                    const JavaParamRef<jstring>& group_id,
+                    const JavaParamRef<jstring>& member_email,
+                    const JavaParamRef<jobject>& j_callback);
   bool IsEmptyService(JNIEnv* env, const JavaParamRef<jobject>& j_caller);
   ScopedJavaLocalRef<jobject> GetNetworkLoader(JNIEnv* env);
 
+  // Returns the DataSharingServiceImpl java object.
   ScopedJavaLocalRef<jobject> GetJavaObject();
 
+  // Returns the observer that routes the notifications to Java observers. The
+  // returned object is of type:
+  // org.chromium.components.data_sharing.ObserverBridge.
+  ScopedJavaLocalRef<jobject> GetJavaObserverBridge();
+
  private:
+  class GroupDataObserverBridge;
+
   // A reference to the Java counterpart of this class.  See
   // DataSharingServiceImpl.java.
   ScopedJavaGlobalRef<jobject> java_obj_;
@@ -40,6 +67,8 @@
   raw_ptr<DataSharingService> data_sharing_service_;
 
   std::unique_ptr<DataSharingNetworkLoaderAndroid> network_loader_;
+
+  std::unique_ptr<GroupDataObserverBridge> observer_bridge_;
 };
 
 }  // namespace data_sharing
diff --git a/components/data_sharing/internal/android/data_sharing_service_android_unittest.cc b/components/data_sharing/internal/android/data_sharing_service_android_unittest.cc
new file mode 100644
index 0000000..32cdf5be
--- /dev/null
+++ b/components/data_sharing/internal/android/data_sharing_service_android_unittest.cc
@@ -0,0 +1,273 @@
+// 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/data_sharing/internal/android/data_sharing_service_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/functional/callback_forward.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "components/data_sharing/internal/data_sharing_service_impl.h"
+#include "components/data_sharing/internal/fake_data_sharing_sdk_delegate.h"
+#include "components/data_sharing/internal/test_jni_headers/TestServiceObserver_jni.h"
+#include "components/data_sharing/public/data_sharing_service.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/sync/protocol/model_type_state.pb.h"
+#include "components/sync/test/model_type_store_test_util.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"
+#include "third_party/abseil-cpp/absl/status/status.h"
+
+namespace data_sharing {
+
+using ::base::android::AttachCurrentThread;
+using ::base::android::ScopedJavaLocalRef;
+
+// Java observer for testing, counter part of TestServiceObserver.
+// On each observation increments a counter and runs the callback.
+class TestJavaObserver {
+ public:
+  TestJavaObserver(DataSharingService* service, base::OnceClosure callback)
+      : service_(DataSharingService::GetJavaObject(service)),
+        java_obj_(Java_TestServiceObserver_createAndAdd(
+            AttachCurrentThread(),
+            service_,
+            reinterpret_cast<long>(this))),
+        callback_(std::move(callback)) {}
+  ~TestJavaObserver() {
+    Java_TestServiceObserver_destroy(AttachCurrentThread(), java_obj_,
+                                     service_);
+  }
+
+  int GetGroupChangeCount() {
+    return Java_TestServiceObserver_getOnGroupChangeCount(AttachCurrentThread(),
+                                                          java_obj_);
+  }
+  int GetGroupAddedCount() {
+    return Java_TestServiceObserver_getOnGroupAddedCount(AttachCurrentThread(),
+                                                         java_obj_);
+  }
+  int GetGroupRemovedCount() {
+    return Java_TestServiceObserver_getOnGroupRemovedCount(
+        AttachCurrentThread(), java_obj_);
+  }
+
+  void ResetCallback(base::OnceClosure callback) {
+    callback_ = std::move(callback);
+  }
+
+  void OnObserverNotify() { std::move(callback_).Run(); }
+
+ private:
+  ScopedJavaLocalRef<jobject> service_;
+  ScopedJavaLocalRef<jobject> java_obj_;
+
+  base::OnceClosure callback_;
+};
+
+// Implements TestServiceObserver.onObserverNotify static method.
+void JNI_TestServiceObserver_OnObserverNotify(JNIEnv* env, jlong observer_ptr) {
+  reinterpret_cast<TestJavaObserver*>(observer_ptr)->OnObserverNotify();
+}
+
+namespace {
+
+sync_pb::CollaborationGroupSpecifics MakeCollaborationGroupSpecifics(
+    const std::string& id) {
+  sync_pb::CollaborationGroupSpecifics result;
+  result.set_collaboration_id(id);
+  result.set_last_updated_timestamp_millis_since_unix_epoch(
+      base::Time::Now().InMillisecondsSinceUnixEpoch());
+  return result;
+}
+
+syncer::EntityData EntityDataFromSpecifics(
+    const sync_pb::CollaborationGroupSpecifics& specifics) {
+  syncer::EntityData entity_data;
+  *entity_data.specifics.mutable_collaboration_group() = specifics;
+  entity_data.name = specifics.collaboration_id();
+  return entity_data;
+}
+
+std::unique_ptr<syncer::EntityChange> EntityChangeAddFromSpecifics(
+    const sync_pb::CollaborationGroupSpecifics& specifics) {
+  return syncer::EntityChange::CreateAdd(specifics.collaboration_id(),
+                                         EntityDataFromSpecifics(specifics));
+}
+
+std::unique_ptr<syncer::EntityChange> EntityChangeUpdateFromSpecifics(
+    const sync_pb::CollaborationGroupSpecifics& specifics) {
+  return syncer::EntityChange::CreateUpdate(specifics.collaboration_id(),
+                                            EntityDataFromSpecifics(specifics));
+}
+
+std::unique_ptr<syncer::EntityChange> EntityChangeDeleteFromSpecifics(
+    const sync_pb::CollaborationGroupSpecifics& specifics) {
+  return syncer::EntityChange::CreateDelete(specifics.collaboration_id());
+}
+
+}  // namespace
+
+class DataSharingServiceAndroidTest : public testing::Test {
+ public:
+  DataSharingServiceAndroidTest() = default;
+
+  ~DataSharingServiceAndroidTest() override = default;
+
+  void SetUp() override {
+    Test::SetUp();
+    scoped_refptr<network::SharedURLLoaderFactory> test_url_loader_factory =
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            &test_url_loader_factory_);
+
+    std::unique_ptr<FakeDataSharingSDKDelegate> sdk_delegate =
+        std::make_unique<FakeDataSharingSDKDelegate>();
+    not_owned_sdk_delegate_ = sdk_delegate.get();
+
+    data_sharing_service_ = std::make_unique<DataSharingServiceImpl>(
+        std::move(test_url_loader_factory),
+        identity_test_env_.identity_manager(),
+        syncer::ModelTypeStoreTestUtil::FactoryForInMemoryStoreForTest(),
+        version_info::Channel::UNKNOWN, std::move(sdk_delegate),
+        /*ui_delegate=*/nullptr);
+    data_sharing_service_android_ = std::make_unique<DataSharingServiceAndroid>(
+        data_sharing_service_.get());
+  }
+
+  void TearDown() override {
+    data_sharing_service_android_.reset();
+    not_owned_sdk_delegate_ = nullptr;
+    data_sharing_service_.reset();
+  }
+
+  // Creates group and returns ID.
+  // Mimics initial sync for collaboration group datatype, this should trigger
+  // OnGroupAdded() notification.
+  std::string CreateGroup() {
+    const std::string display_name = "display_name";
+    const std::string group_id =
+        not_owned_sdk_delegate_->AddGroupAndReturnId(display_name);
+
+    auto* collaboration_group_bridge =
+        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();
+
+    syncer::EntityChangeList entity_changes;
+    entity_changes.push_back(EntityChangeAddFromSpecifics(
+        MakeCollaborationGroupSpecifics(group_id)));
+
+    collaboration_group_bridge->MergeFullSyncData(
+        collaboration_group_bridge->CreateMetadataChangeList(),
+        std::move(entity_changes));
+    return group_id;
+  }
+
+  // Removes the group with `group_id`, which would trigger the OnGroupRemoved()
+  // notification.
+  void RemoveGroup(const std::string& group_id) {
+    auto* collaboration_group_bridge =
+        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();
+    not_owned_sdk_delegate_->RemoveGroup(group_id);
+    syncer::EntityChangeList entity_changes;
+    entity_changes.push_back(EntityChangeDeleteFromSpecifics(
+        MakeCollaborationGroupSpecifics(group_id)));
+
+    collaboration_group_bridge->ApplyIncrementalSyncChanges(
+        collaboration_group_bridge->CreateMetadataChangeList(),
+        std::move(entity_changes));
+  }
+
+  // Updates the group with `group_id` with a different name, which wuld trigger
+  // the OnGroupUpdated() notification.
+  void UpdateGroup(const std::string& group_id) {
+    const std::string new_display_name = "new_display_name";
+    auto* collaboration_group_bridge =
+        data_sharing_service_->GetCollaborationGroupSyncBridgeForTesting();
+    not_owned_sdk_delegate_->UpdateGroup(group_id, new_display_name);
+    syncer::EntityChangeList entity_changes;
+    entity_changes.push_back(EntityChangeUpdateFromSpecifics(
+        MakeCollaborationGroupSpecifics(group_id)));
+
+    collaboration_group_bridge->ApplyIncrementalSyncChanges(
+        collaboration_group_bridge->CreateMetadataChangeList(),
+        std::move(entity_changes));
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  signin::IdentityTestEnvironment identity_test_env_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  std::unique_ptr<DataSharingServiceImpl> data_sharing_service_;
+  std::unique_ptr<DataSharingServiceAndroid> data_sharing_service_android_;
+  raw_ptr<FakeDataSharingSDKDelegate> not_owned_sdk_delegate_;
+};
+
+TEST_F(DataSharingServiceAndroidTest, GroupAddedObservation) {
+  base::RunLoop run_loop;
+  TestJavaObserver observer(data_sharing_service_.get(),
+                            run_loop.QuitClosure());
+
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+
+  CreateGroup();
+
+  run_loop.Run();
+
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+}
+
+TEST_F(DataSharingServiceAndroidTest, GroupRemovedObservation) {
+  base::RunLoop run_loop;
+  TestJavaObserver observer(data_sharing_service_.get(),
+                            run_loop.QuitClosure());
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+
+  std::string group_id = CreateGroup();
+
+  run_loop.Run();
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+
+  base::RunLoop wait_for_remove;
+  observer.ResetCallback(wait_for_remove.QuitClosure());
+
+  RemoveGroup(group_id);
+
+  wait_for_remove.Run();
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 1);
+}
+
+TEST_F(DataSharingServiceAndroidTest, GroupChangeObservation) {
+  base::RunLoop run_loop;
+  TestJavaObserver observer(data_sharing_service_.get(),
+                            run_loop.QuitClosure());
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 0);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+
+  std::string group_id = CreateGroup();
+
+  run_loop.Run();
+  EXPECT_EQ(observer.GetGroupChangeCount(), 0);
+  EXPECT_EQ(observer.GetGroupAddedCount(), 1);
+  EXPECT_EQ(observer.GetGroupRemovedCount(), 0);
+
+  base::RunLoop wait_for_update;
+  observer.ResetCallback(wait_for_update.QuitClosure());
+
+  UpdateGroup(group_id);
+
+  wait_for_update.Run();
+}
+
+}  // namespace data_sharing
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingConversionBridge.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingConversionBridge.java
new file mode 100644
index 0000000..c4322568
--- /dev/null
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingConversionBridge.java
@@ -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.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+
+import java.util.List;
+
+/** Conversion utility for misc types needed in the JNI layer for the service. */
+@JNINamespace("data_sharing")
+public class DataSharingConversionBridge {
+
+    @CalledByNative
+    private static DataSharingService.GroupDataOrFailureOutcome createGroupDataOrFailureOutcome(
+            GroupData groupData, int failureOutcome) {
+        return new DataSharingService.GroupDataOrFailureOutcome(groupData, failureOutcome);
+    }
+
+    @CalledByNative
+    private static DataSharingService.GroupsDataSetOrFailureOutcome
+            createGroupDataSetOrFailureOutcome(GroupData[] groupDataSet, int failureOutcome) {
+        List<GroupData> list = groupDataSet == null ? null : List.of(groupDataSet);
+        return new DataSharingService.GroupsDataSetOrFailureOutcome(list, failureOutcome);
+    }
+
+    @CalledByNative
+    public static Integer createPeopleGroupActionOutcome(int value) {
+        return value;
+    }
+}
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
index 5465e9f..70bfbb0 100644
--- a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
@@ -8,6 +8,7 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
+import org.chromium.base.Callback;
 import org.chromium.base.UserDataHost;
 
 /**
@@ -20,6 +21,8 @@
 
     private final UserDataHost mUserDataHost = new UserDataHost();
 
+    private final ObserverBridge mObserverBridge = new ObserverBridge();
+
     @CalledByNative
     private static DataSharingServiceImpl create(long nativePtr) {
         return new DataSharingServiceImpl(nativePtr);
@@ -29,6 +32,51 @@
         mNativePtr = nativePtr;
     }
 
+    @CalledByNative
+    private ObserverBridge getObserverBridge() {
+        return mObserverBridge;
+    }
+
+    @Override
+    public void addObserver(Observer observer) {
+        mObserverBridge.addObserver(observer);
+    }
+
+    @Override
+    public void removeObserver(Observer observer) {
+        mObserverBridge.removeObserver(observer);
+    }
+
+    @Override
+    public void readAllGroups(Callback<GroupsDataSetOrFailureOutcome> callback) {
+        DataSharingServiceImplJni.get().readAllGroups(mNativePtr, callback);
+    }
+
+    @Override
+    public void readGroup(String groupId, Callback<GroupDataOrFailureOutcome> callback) {
+        DataSharingServiceImplJni.get().readGroup(mNativePtr, groupId, callback);
+    }
+
+    @Override
+    public void createGroup(String groupName, Callback<GroupDataOrFailureOutcome> callback) {
+        DataSharingServiceImplJni.get().createGroup(mNativePtr, groupName, callback);
+    }
+
+    @Override
+    public void deleteGroup(String groupId, Callback<Integer> callback) {
+        DataSharingServiceImplJni.get().deleteGroup(mNativePtr, groupId, callback);
+    }
+
+    @Override
+    public void inviteMember(String groupId, String inviteeEmail, Callback<Integer> callback) {
+        DataSharingServiceImplJni.get().inviteMember(mNativePtr, groupId, inviteeEmail, callback);
+    }
+
+    @Override
+    public void removeMember(String groupId, String memberEmail, Callback<Integer> callback) {
+        DataSharingServiceImplJni.get().removeMember(mNativePtr, groupId, memberEmail, callback);
+    }
+
     @Override
     public boolean isEmptyService() {
         return DataSharingServiceImplJni.get().isEmptyService(mNativePtr, this);
@@ -52,6 +100,35 @@
 
     @NativeMethods
     interface Natives {
+        void readAllGroups(
+                long nativeDataSharingServiceAndroid,
+                Callback<GroupsDataSetOrFailureOutcome> callback);
+
+        void readGroup(
+                long nativeDataSharingServiceAndroid,
+                String groupId,
+                Callback<GroupDataOrFailureOutcome> callback);
+
+        void createGroup(
+                long nativeDataSharingServiceAndroid,
+                String groupName,
+                Callback<GroupDataOrFailureOutcome> callback);
+
+        void deleteGroup(
+                long nativeDataSharingServiceAndroid, String groupId, Callback<Integer> callback);
+
+        void inviteMember(
+                long nativeDataSharingServiceAndroid,
+                String groupId,
+                String inviteeEmail,
+                Callback<Integer> callback);
+
+        void removeMember(
+                long nativeDataSharingServiceAndroid,
+                String groupId,
+                String memberEmail,
+                Callback<Integer> callback);
+
         boolean isEmptyService(long nativeDataSharingServiceAndroid, DataSharingServiceImpl caller);
 
         DataSharingNetworkLoader getNetworkLoader(long nativeDataSharingServiceAndroid);
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/ObserverBridge.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/ObserverBridge.java
new file mode 100644
index 0000000..bd553f7
--- /dev/null
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/ObserverBridge.java
@@ -0,0 +1,57 @@
+// 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.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+
+import org.chromium.base.ObserverList;
+
+/**
+ * A wrapper for DataSharingService.Observer
+ *
+ * <p>Hosts all the Java observers of the service. Receives all the notifications from the native
+ * counterpart GroupDataObserverBridge and then notifies all the Java observers. NOTE: This observer
+ * is not registered to the Java DataSharingService, this implements the DataSharingService.Observer
+ * only for readability. The native observer is registered to the native service.
+ */
+public class ObserverBridge implements DataSharingService.Observer {
+    ObserverList<DataSharingService.Observer> mJavaObservers = new ObserverList<>();
+
+    public ObserverBridge() {}
+
+    /** Add a new observer. Each observer can be added only once */
+    public void addObserver(DataSharingService.Observer observer) {
+        mJavaObservers.addObserver(observer);
+    }
+
+    /** Remove an added observer. Ignores if an observer is not in the list. */
+    public void removeObserver(DataSharingService.Observer observer) {
+        mJavaObservers.removeObserver(observer);
+    }
+
+    @CalledByNative
+    @Override
+    public void onGroupChanged(GroupData groupData) {
+        for (DataSharingService.Observer javaObserver : mJavaObservers) {
+            javaObserver.onGroupChanged(groupData);
+        }
+    }
+
+    @CalledByNative
+    @Override
+    public void onGroupAdded(GroupData groupData) {
+        for (DataSharingService.Observer javaObserver : mJavaObservers) {
+            javaObserver.onGroupAdded(groupData);
+        }
+    }
+
+    @CalledByNative
+    @Override
+    public void onGroupRemoved(String groupId) {
+        for (DataSharingService.Observer javaObserver : mJavaObservers) {
+            javaObserver.onGroupRemoved(groupId);
+        }
+    }
+}
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/TestServiceObserver.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/TestServiceObserver.java
new file mode 100644
index 0000000..a16130f
--- /dev/null
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/TestServiceObserver.java
@@ -0,0 +1,80 @@
+// 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.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+import org.jni_zero.NativeMethods;
+
+/**
+ * Test observer that registers itself to the Java service, and sends notification to the native
+ * test.
+ *
+ * <p>See data_sharing_service_android_unittest.cc for usage.
+ */
+@JNINamespace("data_sharing")
+public class TestServiceObserver implements DataSharingService.Observer {
+    private final long mNativePtr;
+    private int mOnGroupChangeCount;
+    private int mOnGroupAddedCount;
+    private int mOnGroupRemovedCount;
+
+    public TestServiceObserver(long nativePtr) {
+        this.mNativePtr = nativePtr;
+        this.mOnGroupChangeCount = 0;
+        this.mOnGroupAddedCount = 0;
+        this.mOnGroupRemovedCount = 0;
+    }
+
+    @CalledByNative
+    public static TestServiceObserver createAndAdd(DataSharingService service, long nativePtr) {
+        TestServiceObserver obs = new TestServiceObserver(nativePtr);
+        service.addObserver(obs);
+        return obs;
+    }
+
+    @CalledByNative
+    public void destroy(DataSharingService service) {
+        service.removeObserver(this);
+    }
+
+    @CalledByNative
+    private int getOnGroupChangeCount() {
+        return mOnGroupChangeCount;
+    }
+
+    @CalledByNative
+    private int getOnGroupAddedCount() {
+        return mOnGroupAddedCount;
+    }
+
+    @CalledByNative
+    private int getOnGroupRemovedCount() {
+        return mOnGroupRemovedCount;
+    }
+
+    @Override
+    public void onGroupChanged(GroupData groupData) {
+        mOnGroupChangeCount++;
+        TestServiceObserverJni.get().onObserverNotify(mNativePtr);
+    }
+
+    @Override
+    public void onGroupAdded(GroupData groupData) {
+        mOnGroupAddedCount++;
+        TestServiceObserverJni.get().onObserverNotify(mNativePtr);
+    }
+
+    @Override
+    public void onGroupRemoved(String groupId) {
+        mOnGroupRemovedCount++;
+        TestServiceObserverJni.get().onObserverNotify(mNativePtr);
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onObserverNotify(long observerPtr);
+    }
+}
diff --git a/components/data_sharing/internal/data_sharing_service_impl.cc b/components/data_sharing/internal/data_sharing_service_impl.cc
index e30ed6c..15e06fe6 100644
--- a/components/data_sharing/internal/data_sharing_service_impl.cc
+++ b/components/data_sharing/internal/data_sharing_service_impl.cc
@@ -66,6 +66,8 @@
     return DataSharingService::PeopleGroupActionOutcome::kSuccess;
   }
   switch (StatusToPeopleGroupActionFailure(status)) {
+    case DataSharingService::PeopleGroupActionFailure::kUnknown:
+      return DataSharingService::PeopleGroupActionOutcome::kUnknown;
     case DataSharingService::PeopleGroupActionFailure::kPersistentFailure:
       return DataSharingService::PeopleGroupActionOutcome::kPersistentFailure;
     case DataSharingService::PeopleGroupActionFailure::kTransientFailure:
diff --git a/components/data_sharing/internal/data_sharing_service_impl_unittest.cc b/components/data_sharing/internal/data_sharing_service_impl_unittest.cc
index e8a249d..384e1a5a 100644
--- a/components/data_sharing/internal/data_sharing_service_impl_unittest.cc
+++ b/components/data_sharing/internal/data_sharing_service_impl_unittest.cc
@@ -7,12 +7,12 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
-#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "base/version_info/channel.h"
+#include "components/data_sharing/internal/fake_data_sharing_sdk_delegate.h"
 #include "components/data_sharing/public/data_sharing_sdk_delegate.h"
 #include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/data_sharing_ui_delegate.h"
@@ -85,179 +85,6 @@
   MOCK_METHOD(void, OnGroupRemoved, (const std::string&), (override));
 };
 
-class FakeDataSharingSDKDelegate : public DataSharingSDKDelegate {
- public:
-  FakeDataSharingSDKDelegate() = default;
-  ~FakeDataSharingSDKDelegate() override = default;
-
-  // Convenience methods for testing.
-  std::optional<data_sharing_pb::GroupData> GetGroup(
-      const std::string& group_id) {
-    auto it = groups_.find(group_id);
-    if (it != groups_.end()) {
-      return it->second;
-    }
-    return std::nullopt;
-  }
-
-  void RemoveGroup(const std::string& group_id) { groups_.erase(group_id); }
-
-  void UpdateGroup(const std::string& group_id,
-                   const std::string& new_display_name) {
-    auto it = groups_.find(group_id);
-    ASSERT_TRUE(it != groups_.end());
-
-    it->second.set_display_name(new_display_name);
-  }
-
-  std::string AddGroupAndReturnId(const std::string& display_name) {
-    data_sharing_pb::GroupData group_data;
-    group_data.set_group_id(base::NumberToString(next_group_id_++));
-    group_data.set_display_name(display_name);
-    groups_[group_data.group_id()] = group_data;
-    return group_data.group_id();
-  }
-
-  void AddMember(const std::string& group_id,
-                 const std::string& member_gaia_id) {
-    auto group_it = groups_.find(group_id);
-    ASSERT_TRUE(group_it != groups_.end());
-
-    data_sharing_pb::GroupMember member;
-    member.set_gaia_id(member_gaia_id);
-    *group_it->second.add_members() = member;
-  }
-
-  void AddAccount(const std::string& email, const std::string& gaia_id) {
-    email_to_gaia_id_[email] = gaia_id;
-  }
-
-  // DataSharingSDKDelegate implementation.
-  void CreateGroup(
-      const data_sharing_pb::CreateGroupParams& params,
-      base::OnceCallback<
-          void(const base::expected<data_sharing_pb::CreateGroupResult,
-                                    absl::Status>&)> callback) override {
-    std::string group_id = AddGroupAndReturnId(params.display_name());
-
-    data_sharing_pb::CreateGroupResult result;
-    *result.mutable_group_data() = groups_[group_id];
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), result));
-  }
-
-  void ReadGroups(const data_sharing_pb::ReadGroupsParams& params,
-                  base::OnceCallback<void(
-                      const base::expected<data_sharing_pb::ReadGroupsResult,
-                                           absl::Status>&)> callback) override {
-    data_sharing_pb::ReadGroupsResult result;
-    for (const auto& group_id : params.group_ids()) {
-      if (groups_.find(group_id) != groups_.end()) {
-        *result.add_group_data() = groups_[group_id];
-      }
-    }
-
-    if (result.group_data().empty()) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(callback), base::unexpected(absl::Status(
-                                                  absl::StatusCode::kNotFound,
-                                                  "Groups not found"))));
-      return;
-    }
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), result));
-  }
-
-  void AddMember(
-      const data_sharing_pb::AddMemberParams& params,
-      base::OnceCallback<void(const absl::Status&)> callback) override {
-    auto group_it = groups_.find(params.group_id());
-    absl::Status status = absl::OkStatus();
-    if (group_it != groups_.end()) {
-      data_sharing_pb::GroupMember member;
-      member.set_gaia_id(params.member_gaia_id());
-      *group_it->second.add_members() = member;
-    } else {
-      status = absl::Status(absl::StatusCode::kNotFound, "Group not found");
-    }
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), status));
-  }
-
-  void RemoveMember(
-      const data_sharing_pb::RemoveMemberParams& params,
-      base::OnceCallback<void(const absl::Status&)> callback) override {
-    auto group_it = groups_.find(params.group_id());
-    if (group_it == groups_.end()) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback),
-                                    absl::Status(absl::StatusCode::kNotFound,
-                                                 "Group not found")));
-      return;
-    }
-
-    absl::Status status = absl::OkStatus();
-    auto* group_members = group_it->second.mutable_members();
-    auto member_it =
-        std::find_if(group_members->begin(), group_members->end(),
-                     [&params](const data_sharing_pb::GroupMember& member) {
-                       return member.gaia_id() == params.member_gaia_id();
-                     });
-    if (member_it != group_members->end()) {
-      group_members->erase(member_it);
-    } else {
-      status = absl::Status(absl::StatusCode::kNotFound, "Member not found");
-    }
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), status));
-  }
-
-  void DeleteGroup(
-      const data_sharing_pb::DeleteGroupParams& params,
-      base::OnceCallback<void(const absl::Status&)> callback) override {
-    absl::Status status = absl::OkStatus();
-    if (groups_.find(params.group_id()) != groups_.end()) {
-      groups_.erase(params.group_id());
-    } else {
-      status = absl::Status(absl::StatusCode::kNotFound, "Group not found");
-    }
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), status));
-  }
-
-  void LookupGaiaIdByEmail(
-      const data_sharing_pb::LookupGaiaIdByEmailParams& params,
-      base::OnceCallback<
-          void(const base::expected<data_sharing_pb::LookupGaiaIdByEmailResult,
-                                    absl::Status>&)> callback) override {
-    auto it = email_to_gaia_id_.find(params.email());
-    if (it != email_to_gaia_id_.end()) {
-      data_sharing_pb::LookupGaiaIdByEmailResult result;
-      result.set_gaia_id(it->second);
-
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), result));
-      return;
-    }
-
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(callback),
-                       base::unexpected(absl::Status(
-                           absl::StatusCode::kNotFound, "Account not found"))));
-  }
-
- private:
-  std::map<std::string, data_sharing_pb::GroupData> groups_;
-  std::map<std::string, std::string> email_to_gaia_id_;
-  int next_group_id_ = 0;
-};
 
 }  // namespace
 
diff --git a/components/data_sharing/internal/fake_data_sharing_sdk_delegate.cc b/components/data_sharing/internal/fake_data_sharing_sdk_delegate.cc
new file mode 100644
index 0000000..dab75f9
--- /dev/null
+++ b/components/data_sharing/internal/fake_data_sharing_sdk_delegate.cc
@@ -0,0 +1,184 @@
+// 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/data_sharing/internal/fake_data_sharing_sdk_delegate.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/task/single_thread_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/status/status.h"
+
+namespace data_sharing {
+
+FakeDataSharingSDKDelegate::FakeDataSharingSDKDelegate() = default;
+FakeDataSharingSDKDelegate::~FakeDataSharingSDKDelegate() = default;
+
+std::optional<data_sharing_pb::GroupData> FakeDataSharingSDKDelegate::GetGroup(
+    const std::string& group_id) {
+  auto it = groups_.find(group_id);
+  if (it != groups_.end()) {
+    return it->second;
+  }
+  return std::nullopt;
+}
+
+void FakeDataSharingSDKDelegate::RemoveGroup(const std::string& group_id) {
+  groups_.erase(group_id);
+}
+
+void FakeDataSharingSDKDelegate::UpdateGroup(
+    const std::string& group_id,
+    const std::string& new_display_name) {
+  auto it = groups_.find(group_id);
+  ASSERT_TRUE(it != groups_.end());
+
+  it->second.set_display_name(new_display_name);
+}
+
+std::string FakeDataSharingSDKDelegate::AddGroupAndReturnId(
+    const std::string& display_name) {
+  data_sharing_pb::GroupData group_data;
+  group_data.set_group_id(base::NumberToString(next_group_id_++));
+  group_data.set_display_name(display_name);
+  groups_[group_data.group_id()] = group_data;
+  return group_data.group_id();
+}
+
+void FakeDataSharingSDKDelegate::AddMember(const std::string& group_id,
+                                           const std::string& member_gaia_id) {
+  auto group_it = groups_.find(group_id);
+  ASSERT_TRUE(group_it != groups_.end());
+
+  data_sharing_pb::GroupMember member;
+  member.set_gaia_id(member_gaia_id);
+  *group_it->second.add_members() = member;
+}
+
+void FakeDataSharingSDKDelegate::AddAccount(const std::string& email,
+                                            const std::string& gaia_id) {
+  email_to_gaia_id_[email] = gaia_id;
+}
+
+void FakeDataSharingSDKDelegate::CreateGroup(
+    const data_sharing_pb::CreateGroupParams& params,
+    base::OnceCallback<
+        void(const base::expected<data_sharing_pb::CreateGroupResult,
+                                  absl::Status>&)> callback) {
+  std::string group_id = AddGroupAndReturnId(params.display_name());
+
+  data_sharing_pb::CreateGroupResult result;
+  *result.mutable_group_data() = groups_[group_id];
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void FakeDataSharingSDKDelegate::ReadGroups(
+    const data_sharing_pb::ReadGroupsParams& params,
+    base::OnceCallback<void(
+        const base::expected<data_sharing_pb::ReadGroupsResult, absl::Status>&)>
+        callback) {
+  data_sharing_pb::ReadGroupsResult result;
+  for (const auto& group_id : params.group_ids()) {
+    if (groups_.find(group_id) != groups_.end()) {
+      *result.add_group_data() = groups_[group_id];
+    }
+  }
+
+  if (result.group_data().empty()) {
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback),
+                       base::unexpected(absl::Status(
+                           absl::StatusCode::kNotFound, "Groups not found"))));
+    return;
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void FakeDataSharingSDKDelegate::AddMember(
+    const data_sharing_pb::AddMemberParams& params,
+    base::OnceCallback<void(const absl::Status&)> callback) {
+  auto group_it = groups_.find(params.group_id());
+  absl::Status status = absl::OkStatus();
+  if (group_it != groups_.end()) {
+    data_sharing_pb::GroupMember member;
+    member.set_gaia_id(params.member_gaia_id());
+    *group_it->second.add_members() = member;
+  } else {
+    status = absl::Status(absl::StatusCode::kNotFound, "Group not found");
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), status));
+}
+
+void FakeDataSharingSDKDelegate::RemoveMember(
+    const data_sharing_pb::RemoveMemberParams& params,
+    base::OnceCallback<void(const absl::Status&)> callback) {
+  auto group_it = groups_.find(params.group_id());
+  if (group_it == groups_.end()) {
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback),
+                                  absl::Status(absl::StatusCode::kNotFound,
+                                               "Group not found")));
+    return;
+  }
+
+  absl::Status status = absl::OkStatus();
+  auto* group_members = group_it->second.mutable_members();
+  auto member_it =
+      std::find_if(group_members->begin(), group_members->end(),
+                   [&params](const data_sharing_pb::GroupMember& member) {
+                     return member.gaia_id() == params.member_gaia_id();
+                   });
+  if (member_it != group_members->end()) {
+    group_members->erase(member_it);
+  } else {
+    status = absl::Status(absl::StatusCode::kNotFound, "Member not found");
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), status));
+}
+
+void FakeDataSharingSDKDelegate::DeleteGroup(
+    const data_sharing_pb::DeleteGroupParams& params,
+    base::OnceCallback<void(const absl::Status&)> callback) {
+  absl::Status status = absl::OkStatus();
+  if (groups_.find(params.group_id()) != groups_.end()) {
+    groups_.erase(params.group_id());
+  } else {
+    status = absl::Status(absl::StatusCode::kNotFound, "Group not found");
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), status));
+}
+
+void FakeDataSharingSDKDelegate::LookupGaiaIdByEmail(
+    const data_sharing_pb::LookupGaiaIdByEmailParams& params,
+    base::OnceCallback<
+        void(const base::expected<data_sharing_pb::LookupGaiaIdByEmailResult,
+                                  absl::Status>&)> callback) {
+  auto it = email_to_gaia_id_.find(params.email());
+  if (it != email_to_gaia_id_.end()) {
+    data_sharing_pb::LookupGaiaIdByEmailResult result;
+    result.set_gaia_id(it->second);
+
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), result));
+    return;
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback),
+                     base::unexpected(absl::Status(absl::StatusCode::kNotFound,
+                                                   "Account not found"))));
+}
+
+}  // namespace data_sharing
diff --git a/components/data_sharing/internal/fake_data_sharing_sdk_delegate.h b/components/data_sharing/internal/fake_data_sharing_sdk_delegate.h
new file mode 100644
index 0000000..5b5d75e4
--- /dev/null
+++ b/components/data_sharing/internal/fake_data_sharing_sdk_delegate.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 COMPONENTS_DATA_SHARING_INTERNAL_FAKE_DATA_SHARING_SDK_DELEGATE_H_
+#define COMPONENTS_DATA_SHARING_INTERNAL_FAKE_DATA_SHARING_SDK_DELEGATE_H_
+
+#include <string>
+
+#include "components/data_sharing/public/data_sharing_sdk_delegate.h"
+
+namespace data_sharing {
+
+// Implementation of DataSharingSDKDelegate for unit tests that holds the group
+// info in-memory.
+class FakeDataSharingSDKDelegate : public DataSharingSDKDelegate {
+ public:
+  FakeDataSharingSDKDelegate();
+  ~FakeDataSharingSDKDelegate() override;
+
+  // Convenience methods for testing.
+  std::optional<data_sharing_pb::GroupData> GetGroup(
+      const std::string& group_id);
+
+  void RemoveGroup(const std::string& group_id);
+
+  void UpdateGroup(const std::string& group_id,
+                   const std::string& new_display_name);
+
+  std::string AddGroupAndReturnId(const std::string& display_name);
+
+  void AddMember(const std::string& group_id,
+                 const std::string& member_gaia_id);
+
+  void AddAccount(const std::string& email, const std::string& gaia_id);
+
+  // DataSharingSDKDelegate impl:
+  void CreateGroup(const data_sharing_pb::CreateGroupParams& params,
+                   base::OnceCallback<void(
+                       const base::expected<data_sharing_pb::CreateGroupResult,
+                                            absl::Status>&)> callback) override;
+  void ReadGroups(const data_sharing_pb::ReadGroupsParams& params,
+                  base::OnceCallback<void(
+                      const base::expected<data_sharing_pb::ReadGroupsResult,
+                                           absl::Status>&)> callback) override;
+  void AddMember(
+      const data_sharing_pb::AddMemberParams& params,
+      base::OnceCallback<void(const absl::Status&)> callback) override;
+  void RemoveMember(
+      const data_sharing_pb::RemoveMemberParams& params,
+      base::OnceCallback<void(const absl::Status&)> callback) override;
+  void DeleteGroup(
+      const data_sharing_pb::DeleteGroupParams& params,
+      base::OnceCallback<void(const absl::Status&)> callback) override;
+  void LookupGaiaIdByEmail(
+      const data_sharing_pb::LookupGaiaIdByEmailParams& params,
+      base::OnceCallback<
+          void(const base::expected<data_sharing_pb::LookupGaiaIdByEmailResult,
+                                    absl::Status>&)> callback) override;
+
+ private:
+  std::map<std::string, data_sharing_pb::GroupData> groups_;
+  std::map<std::string, std::string> email_to_gaia_id_;
+  int next_group_id_ = 0;
+};
+
+}  // namespace data_sharing
+
+#endif  // COMPONENTS_DATA_SHARING_INTERNAL_FAKE_DATA_SHARING_SDK_DELEGATE_H_
diff --git a/components/data_sharing/public/BUILD.gn b/components/data_sharing/public/BUILD.gn
index 4d3df068..065cf78 100644
--- a/components/data_sharing/public/BUILD.gn
+++ b/components/data_sharing/public/BUILD.gn
@@ -36,6 +36,8 @@
       "android/java/src/org/chromium/components/data_sharing/DataSharingNetworkLoader.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingService.java",
       "android/java/src/org/chromium/components/data_sharing/DataSharingUIDelegate.java",
+      "android/java/src/org/chromium/components/data_sharing/GroupData.java",
+      "android/java/src/org/chromium/components/data_sharing/GroupMember.java",
       "android/java/src/org/chromium/components/data_sharing/configs/AvatarConfig.java",
       "android/java/src/org/chromium/components/data_sharing/configs/GroupMemberConfig.java",
       "android/java/src/org/chromium/components/data_sharing/configs/MemberPickerConfig.java",
@@ -48,5 +50,21 @@
       "//third_party/jni_zero:jni_zero_java",
       "//url:gurl_java",
     ]
+
+    srcjar_deps = [ ":enums_java" ]
+  }
+
+  java_cpp_enum("enums_java") {
+    sources = [
+      "data_sharing_service.h",
+      "group_data.h",
+    ]
+  }
+
+  generate_jni("jni_headers") {
+    sources = [
+      "android/java/src/org/chromium/components/data_sharing/GroupData.java",
+      "android/java/src/org/chromium/components/data_sharing/GroupMember.java",
+    ]
   }
 }
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
index 80c5552e..67d9a20 100644
--- a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
@@ -4,13 +4,139 @@
 
 package org.chromium.components.data_sharing;
 
+import org.chromium.base.Callback;
 import org.chromium.base.UserDataHost;
 
+import java.util.List;
+
 /**
  * DataSharingService is the core class for managing data sharing. It represents a native
  * DataSharingService object in Java.
  */
 public interface DataSharingService {
+    /** Result that contains group data and an outcome of the action that was requested. */
+    public static class GroupDataOrFailureOutcome {
+        /**
+         * The group data requested.
+         *
+         * <p>Can be null if the action failed. Please check `actionFailure` for more info.
+         */
+        public final GroupData groupData;
+
+        /** Result of the action, UNKNOWN if the action was successful. */
+        public final @PeopleGroupActionFailure int actionFailure;
+
+        GroupDataOrFailureOutcome(GroupData groupData, int actionFailure) {
+            this.groupData = groupData;
+            this.actionFailure = actionFailure;
+        }
+    }
+
+    /** Result that contains a set of groups and an outcome of the action that was requested. */
+    public static class GroupsDataSetOrFailureOutcome {
+        /**
+         * The list of groups requested.
+         *
+         * <p>The list if null if the request failed. Group IDs cannot be repeated in the list.
+         */
+        public final List<GroupData> groupDataSet;
+
+        /** Result of the action */
+        public final @PeopleGroupActionFailure int actionFailure;
+
+        GroupsDataSetOrFailureOutcome(List<GroupData> groupDataSet, int actionFailure) {
+            this.groupDataSet = groupDataSet;
+            this.actionFailure = actionFailure;
+        }
+    }
+
+    /** Observer to listen to the updates on any of the groups. */
+    interface Observer {
+        /** A group was updated where the current user continues to be a member of. */
+        void onGroupChanged(GroupData groupData);
+
+        /** The user either created a new group or has been invited to the existing one. */
+        void onGroupAdded(GroupData groupData);
+
+        /** Either group has been deleted or user has been removed from the group. */
+        void onGroupRemoved(String groupId);
+    }
+
+    /**
+     * Add an observer to the service.
+     *
+     * <p>An observer should not be added to the same list more than once.
+     *
+     * @param observer The observer to add.
+     */
+    void addObserver(Observer observer);
+
+    /**
+     * Remove an observer from the service, if it is in the list.
+     *
+     * @param observer The observer to remove.
+     */
+    void removeObserver(Observer observer);
+
+    /**
+     * Refresh and read all the group data the user is part of.
+     *
+     * <p>Refresh data if necessary. The result is ordered by group ID.
+     *
+     * @param callback On success passes to the `callback` a set of all groups known to the client.
+     */
+    void readAllGroups(Callback<GroupsDataSetOrFailureOutcome> callback);
+
+    /**
+     * Refresh and read the requested group data.
+     *
+     * <p>Refresh data if necessary.
+     *
+     * @param groupId The group ID to read data from.
+     * @param callback The GroupData is returned by callback.
+     */
+    void readGroup(String groupId, Callback<GroupDataOrFailureOutcome> callback);
+
+    /**
+     * Attempt to create a new group.
+     *
+     * @param groupName The name of the group to be created.
+     * @param callback Return a created group data on success.
+     */
+    void createGroup(String groupName, Callback<GroupDataOrFailureOutcome> callback);
+
+    /**
+     * Attempt to delete a group.
+     *
+     * @param groupID The group ID to delete.
+     * @param callback The deletion result as PeopleGroupActionOutcome.
+     */
+    void deleteGroup(String groupId, Callback</*PeopleGroupActionOutcome*/ Integer> callback);
+
+    /**
+     * Attempt to invite a new user to the group.
+     *
+     * @param groupID The group ID to add to.
+     * @param inviteeEmail The email of the member to add.
+     * @param callback The invite result as PeopleGroupActionOutcome.
+     */
+    void inviteMember(
+            String groupId,
+            String inviteeEmail,
+            Callback</*PeopleGroupActionOutcome*/ Integer> callback);
+
+    /**
+     * Attempts to remove a user from the group.
+     *
+     * @param groupID The group ID to remove from.
+     * @param membeEmail The email of the member to remove.
+     * @param callback The removal result as PeopleGroupActionOutcome.
+     */
+    void removeMember(
+            String groupId,
+            String memberEmail,
+            Callback</*PeopleGroupActionOutcome*/ Integer> callback);
+
     /**
      * Whether the service is an empty implementation. This is here because the Chromium build
      * disables RTTI, and we need to be able to verify that we are using an empty service from the
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupData.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupData.java
new file mode 100644
index 0000000..df36132
--- /dev/null
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupData.java
@@ -0,0 +1,30 @@
+// 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.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+
+import java.util.List;
+
+/** Information about a group. */
+@JNINamespace("data_sharing")
+public class GroupData {
+    public final String groupId;
+    public final String displayName;
+    public final List<GroupMember> members;
+
+    GroupData(String groupId, String displayName, GroupMember[] members) {
+        this.groupId = groupId;
+        this.displayName = displayName;
+        this.members = members == null ? null : List.of(members);
+    }
+
+    @CalledByNative
+    private static GroupData createGroupData(
+            String groupId, String displayName, GroupMember[] members) {
+        return new GroupData(groupId, displayName, members);
+    }
+}
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupMember.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupMember.java
new file mode 100644
index 0000000..68f90c00
--- /dev/null
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/GroupMember.java
@@ -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.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+
+import org.chromium.components.data_sharing.member_role.MemberRole;
+import org.chromium.url.GURL;
+
+/** Information about a member of a group. */
+@JNINamespace("data_sharing")
+public class GroupMember {
+    public final String gaiaId;
+    public final String displayName;
+    public final String email;
+    public final @MemberRole int role;
+    public final GURL avatarUrl;
+
+    GroupMember(String gaiaId, String displayName, String email, int role, GURL avatarUrl) {
+        this.gaiaId = gaiaId;
+        this.displayName = displayName;
+        this.email = email;
+        this.role = role;
+        this.avatarUrl = avatarUrl;
+    }
+
+    @CalledByNative
+    private static GroupMember createGroupMember(
+            String gaiaId, String displayName, String email, int role, GURL avatarUrl) {
+        return new GroupMember(gaiaId, displayName, email, role, avatarUrl);
+    }
+}
diff --git a/components/data_sharing/public/data_sharing_service.h b/components/data_sharing/public/data_sharing_service.h
index fe80276..010238e 100644
--- a/components/data_sharing/public/data_sharing_service.h
+++ b/components/data_sharing/public/data_sharing_service.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_DATA_SHARING_PUBLIC_DATA_SHARING_SERVICE_H_
 #define COMPONENTS_DATA_SHARING_PUBLIC_DATA_SHARING_SERVICE_H_
 
+#include <string>
+
 #include "base/functional/callback_forward.h"
 #include "base/observer_list_types.h"
 #include "base/supports_user_data.h"
@@ -38,12 +40,21 @@
     virtual void OnGroupRemoved(const std::string& group_id) {}
   };
 
-  enum class PeopleGroupActionFailure { kTransientFailure, kPersistentFailure };
+  // GENERATED_JAVA_ENUM_PACKAGE: (
+  //   org.chromium.components.data_sharing)
+  enum class PeopleGroupActionFailure {
+    kUnknown = 0,
+    kTransientFailure = 1,
+    kPersistentFailure = 2
+  };
 
+  // GENERATED_JAVA_ENUM_PACKAGE: (
+  //   org.chromium.components.data_sharing)
   enum class PeopleGroupActionOutcome {
-    kSuccess,
-    kTransientFailure,
-    kPersistentFailure
+    kUnknown = 0,
+    kSuccess = 1,
+    kTransientFailure = 2,
+    kPersistentFailure = 3
   };
 
   using GroupDataOrFailureOutcome =
diff --git a/components/data_sharing/public/group_data.h b/components/data_sharing/public/group_data.h
index a62d4f7..57efeb3 100644
--- a/components/data_sharing/public/group_data.h
+++ b/components/data_sharing/public/group_data.h
@@ -11,7 +11,9 @@
 
 namespace data_sharing {
 
-enum class MemberRole { kUnknown, kOwner, kMember, kInvitee };
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.components.data_sharing.member_role)
+enum class MemberRole { kUnknown = 0, kOwner = 1, kMember = 2, kInvitee = 3 };
 
 struct GroupMember {
   GroupMember();
diff --git a/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java b/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java
new file mode 100644
index 0000000..1db6dc44
--- /dev/null
+++ b/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java
@@ -0,0 +1,81 @@
+// 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.
+
+package org.chromium.components.data_sharing;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ObserverList;
+import org.chromium.base.UserDataHost;
+
+/** Data sharing service impl for testing. */
+public class TestDataSharingService implements DataSharingService {
+
+    ObserverList<DataSharingService.Observer> mJavaObservers = new ObserverList<>();
+
+    public TestDataSharingService() {}
+
+    @Override
+    public void addObserver(Observer observer) {
+        mJavaObservers.addObserver(observer);
+    }
+
+    @Override
+    public void removeObserver(Observer observer) {
+        mJavaObservers.removeObserver(observer);
+    }
+
+    @Override
+    public void readAllGroups(Callback<GroupsDataSetOrFailureOutcome> callback) {
+        Callback.runNullSafe(
+                callback,
+                new DataSharingService.GroupsDataSetOrFailureOutcome(
+                        null, PeopleGroupActionFailure.PERSISTENT_FAILURE));
+    }
+
+    @Override
+    public void readGroup(String groupId, Callback<GroupDataOrFailureOutcome> callback) {
+        Callback.runNullSafe(
+                callback,
+                new DataSharingService.GroupDataOrFailureOutcome(
+                        null, PeopleGroupActionFailure.PERSISTENT_FAILURE));
+    }
+
+    @Override
+    public void createGroup(String groupName, Callback<GroupDataOrFailureOutcome> callback) {
+        Callback.runNullSafe(
+                callback,
+                new DataSharingService.GroupDataOrFailureOutcome(
+                        null, PeopleGroupActionFailure.PERSISTENT_FAILURE));
+    }
+
+    @Override
+    public void deleteGroup(String groupId, Callback<Integer> callback) {
+        Callback.runNullSafe(callback, PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+    }
+
+    @Override
+    public void inviteMember(String groupId, String inviteeEmail, Callback<Integer> callback) {
+        Callback.runNullSafe(callback, PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+    }
+
+    @Override
+    public void removeMember(String groupId, String memberEmail, Callback<Integer> callback) {
+        Callback.runNullSafe(callback, PeopleGroupActionOutcome.PERSISTENT_FAILURE);
+    }
+
+    @Override
+    public boolean isEmptyService() {
+        return true;
+    }
+
+    @Override
+    public DataSharingNetworkLoader getNetworkLoader() {
+        return null;
+    }
+
+    @Override
+    public UserDataHost getUserDataHost() {
+        return null;
+    }
+}
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index ac4d832..f61a9b6 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -79,12 +79,6 @@
 namespace exo {
 namespace {
 
-bool IsRadiiUniform(const gfx::RoundedCornersF& radii) {
-  return radii.upper_left() == radii.upper_right() &&
-         radii.lower_left() == radii.lower_right() &&
-         radii.upper_left() == radii.lower_left();
-}
-
 // The accelerator keys used to close ShellSurfaces.
 const struct {
   ui::KeyboardCode keycode;
@@ -182,7 +176,6 @@
           window_radii.value_or(shadow_radii.value_or(gfx::RoundedCornersF()));
 
       // TODO(crbug.com/40256581): Support variable window radii.
-      DCHECK(IsRadiiUniform(radii));
       corner_radius = radii.upper_left();
     }
 
@@ -2160,11 +2153,9 @@
     // TODO(crbug.com/40256581): Revisit once all the clients have migrated.
     shadow_radii = shadow_corners_radii_dp_.value_or(
         window_corners_radii_dp_.value_or(gfx::RoundedCornersF()));
-
-    // TODO(crbug.com/40256581): Support shadow with variable radius corners.
-    DCHECK(IsRadiiUniform(shadow_radii));
   }
 
+  // TODO(crbug.com/40256581): Support shadow with variable radius corners.
   shadow->SetRoundedCornerRadius(shadow_radii.upper_left());
 }
 
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index 7b823fa..37095bc8 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -32,6 +32,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/ui/base/chromeos_ui_constants.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
@@ -1287,7 +1288,9 @@
     if (wl_resource_get_version(aura_shell_resource_) >=
         ZAURA_SHELL_WINDOW_CORNERS_RADII_SINCE_VERSION) {
       const int window_corner_radius =
-          chromeos::features::RoundedWindowsRadius();
+          chromeos::features::IsRoundedWindowsEnabled()
+              ? chromeos::features::RoundedWindowsRadius()
+              : chromeos::kTopCornerRadiusWhenRestored;
 
       zaura_shell_send_window_corners_radii(
           aura_shell_resource_, window_corner_radius, window_corner_radius,
diff --git a/components/history_embeddings/passage_embeddings_service_controller.cc b/components/history_embeddings/passage_embeddings_service_controller.cc
index 83fc0d2..f47d50ed 100644
--- a/components/history_embeddings/passage_embeddings_service_controller.cc
+++ b/components/history_embeddings/passage_embeddings_service_controller.cc
@@ -4,6 +4,7 @@
 
 #include "components/history_embeddings/passage_embeddings_service_controller.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "base/task/thread_pool.h"
 #include "components/history_embeddings/vector_database.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
@@ -27,6 +28,24 @@
   return params;
 }
 
+class ScopedEmbeddingsModelInfoStatusLogger {
+ public:
+  ScopedEmbeddingsModelInfoStatusLogger() = default;
+  ~ScopedEmbeddingsModelInfoStatusLogger() {
+    CHECK_NE(history_embeddings::EmbeddingsModelInfoStatus::kUnknown, status_);
+    base::UmaHistogramEnumeration("History.Embeddings.Embedder.ModelInfoStatus",
+                                  status_);
+  }
+
+  void set_status(history_embeddings::EmbeddingsModelInfoStatus status) {
+    status_ = status;
+  }
+
+ private:
+  history_embeddings::EmbeddingsModelInfoStatus status_ =
+      history_embeddings::EmbeddingsModelInfoStatus::kUnknown;
+};
+
 }  // namespace
 
 namespace history_embeddings {
@@ -36,7 +55,6 @@
 PassageEmbeddingsServiceController::~PassageEmbeddingsServiceController() =
     default;
 
-// TODO(b/338650221): Add histograms for bad model info.
 bool PassageEmbeddingsServiceController::MaybeUpdateModelPaths(
     base::optional_ref<const optimization_guide::ModelInfo> model_info) {
   // Reset everything, so if the model info is invalid, the service controller
@@ -46,7 +64,9 @@
   model_metadata_ = std::nullopt;
   ResetRemotes();
 
-  if (!model_info.has_value() || !model_info->GetModelMetadata()) {
+  ScopedEmbeddingsModelInfoStatusLogger logger;
+  if (!model_info.has_value()) {
+    logger.set_status(EmbeddingsModelInfoStatus::kEmpty);
     return false;
   }
 
@@ -54,16 +74,22 @@
   base::flat_set<base::FilePath> additional_files =
       model_info->GetAdditionalFiles();
   if (additional_files.size() != 1u) {
+    logger.set_status(EmbeddingsModelInfoStatus::kInvalidAdditionalFiles);
     return false;
   }
 
   // Check validity of model metadata.
   const std::optional<optimization_guide::proto::Any>& metadata =
       model_info->GetModelMetadata();
+  if (!metadata) {
+    logger.set_status(EmbeddingsModelInfoStatus::kNoMetadata);
+    return false;
+  }
   std::optional<history_embeddings::proto::PassageEmbeddingsModelMetadata>
       embeddings_metadata = optimization_guide::ParsedAnyMetadata<
           history_embeddings::proto::PassageEmbeddingsModelMetadata>(*metadata);
   if (!embeddings_metadata) {
+    logger.set_status(EmbeddingsModelInfoStatus::kInvalidMetadata);
     return false;
   }
 
@@ -74,6 +100,7 @@
 
   CHECK(!embeddings_model_path_.empty());
   CHECK(!sp_model_path_.empty());
+  logger.set_status(EmbeddingsModelInfoStatus::kValid);
   return true;
 }
 
diff --git a/components/history_embeddings/passage_embeddings_service_controller.h b/components/history_embeddings/passage_embeddings_service_controller.h
index 3291288..13f634f4 100644
--- a/components/history_embeddings/passage_embeddings_service_controller.h
+++ b/components/history_embeddings/passage_embeddings_service_controller.h
@@ -13,6 +13,29 @@
 
 namespace history_embeddings {
 
+enum class EmbeddingsModelInfoStatus {
+  kUnknown = 0,
+
+  // Model info is valid.
+  kValid = 1,
+
+  // Model info is empty.
+  kEmpty = 2,
+
+  // Model info does not contain model metadata.
+  kNoMetadata = 3,
+
+  // Model info has invalid metadata.
+  kInvalidMetadata = 4,
+
+  // Model info has invalid additional files.
+  kInvalidAdditionalFiles = 5,
+
+  // This must be kept in sync with EmbeddingsModelInfoStatus in
+  // history/enums.xml
+  kMaxValue = kInvalidAdditionalFiles,
+};
+
 class PassageEmbeddingsServiceController {
  public:
   PassageEmbeddingsServiceController();
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 751be9a..a10e3bac 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -1942,11 +1942,9 @@
   // would be scored independently with their partial signals.
   internal_result_.DeduplicateMatches(input_, template_url_service_);
 
-  size_t eligible_matches_count =
-      base::ranges::count_if(internal_result_.matches_, [](const auto& match) {
-        return match.IsUrlScoringEligible() &&
-               !AutocompleteMatch::IsSearchType(match.type);
-      });
+  size_t eligible_matches_count = base::ranges::count_if(
+      internal_result_.matches_,
+      [](const auto& match) { return match.IsUrlScoringEligible(); });
 
   if (eligible_matches_count == 0)
     return;
@@ -1965,10 +1963,6 @@
     RecordScoringSignalCoverageForProvider(match_itr->scoring_signals.value(),
                                            match_itr->provider.get());
 
-    if (AutocompleteMatch::IsSearchType(match_itr->type)) {
-      continue;
-    }
-
     // Verify the eligible match or one of its duplicates has an expected type.
     DCHECK(match_itr->MatchOrDuplicateMeets([](const auto& match) {
       return AutocompleteScoringSignalsAnnotator::IsEligibleMatch(match);
@@ -2191,6 +2185,8 @@
   // This is needed in order to ensure that the relevance score assignment logic
   // can properly break ties when two (or more) URL suggestions have the same ML
   // score.
+  // TODO(crbug.com/40062540): Replace `Sort` with `DeduplicateMatches` for
+  //   better stability of matches.
   internal_result_.Sort(input_, template_url_service_,
                         old_result.default_match_to_preserve);
 
@@ -2211,10 +2207,6 @@
     RecordScoringSignalCoverageForProvider(match.scoring_signals.value(),
                                            match.provider.get());
 
-    if (AutocompleteMatch::IsSearchType(match.type)) {
-      continue;
-    }
-
     batch_scoring_signals.push_back(&match.scoring_signals.value());
     scored_positions.push_back(i);
   }
@@ -2276,8 +2268,7 @@
     const auto& match = internal_result_.matches_[i];
     if (!match.IsUrlScoringEligible() ||
         (match.type == AutocompleteMatchType::DOCUMENT_SUGGESTION &&
-         match.relevance == 0) ||
-        AutocompleteMatch::IsSearchType(match.type)) {
+         match.relevance == 0)) {
       continue;
     }
     scores_pool.push_back(match.relevance);
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 7018dc6..575f538 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -1453,7 +1453,8 @@
 
 bool AutocompleteMatch::IsUrlScoringEligible() const {
   return scoring_signals.has_value() &&
-         type != AutocompleteMatchType::URL_WHAT_YOU_TYPED;
+         type != AutocompleteMatchType::URL_WHAT_YOU_TYPED &&
+         !force_skip_ml_scoring && !AutocompleteMatch::IsSearchType(type);
 }
 
 bool AutocompleteMatch::IsTrendSuggestion() const {
@@ -1693,6 +1694,17 @@
         duplicate_match.rich_autocompletion_triggered;
   }
 
+  // If either one of the matches is ineligible for ML scoring, then ensure that
+  // the final match is marked as ineligible for ML scoring. Search suggestions
+  // are guaranteed to be excluded from ML scoring at this time, so there's no
+  // need to set `force_skip_ml_scoring` for those matches.
+  if (base::FeatureList::IsEnabled(omnibox::kEnableForceSkipMlScoring) &&
+      (!IsUrlScoringEligible() || !duplicate_match.IsUrlScoringEligible()) &&
+      !AutocompleteMatch::IsSearchType(type)) {
+    force_skip_ml_scoring = true;
+    RecordAdditionalInfo("force skip ml scoring", "true");
+  }
+
   // Merge scoring signals from duplicate match for ML model scoring and
   // training.
   if (OmniboxFieldTrial::IsPopulatingUrlScoringSignalsEnabled()) {
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index d7658d8..a516ff3b 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -916,6 +916,12 @@
   // Signals for ML scoring.
   std::optional<ScoringSignals> scoring_signals;
 
+  // A flag that's set during the de-duplication process in order to forcibly
+  // exclude this match from ML scoring (e.g. this match is ML-eligible, but one
+  // of the matches in `duplicate_matches` is not). Furthermore, when this flag
+  // is set, ML scoring signals will NOT be logged for this particular match.
+  bool force_skip_ml_scoring = false;
+
   // A flag to mark whether this would've been excluded from the "original" list
   // of matches. Traditionally, providers limit the number of suggestions they
   // provide to the top N most relevant matches. When ML scoring is enabled,
diff --git a/components/omnibox/browser/omnibox_metrics_provider.cc b/components/omnibox/browser/omnibox_metrics_provider.cc
index f935a5d..95cb4b02 100644
--- a/components/omnibox/browser/omnibox_metrics_provider.cc
+++ b/components/omnibox/browser/omnibox_metrics_provider.cc
@@ -226,10 +226,10 @@
     suggestion->set_is_keyword_suggestion(match.from_keyword);
 
     // Scoring signals are not logged for search suggestions or in incognito
-    // mode.
+    // mode or when the `force_skip_ml_scoring` flag as been set.
     if (OmniboxFieldTrial::IsReportingUrlScoringSignalsEnabled() &&
         !AutocompleteMatch::IsSearchType(match.type) && !log.is_incognito &&
-        match.scoring_signals) {
+        !match.force_skip_ml_scoring && match.scoring_signals) {
       suggestion->mutable_scoring_signals()->CopyFrom(*match.scoring_signals);
     }
   }
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index a18878f..12a4246 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -447,6 +447,12 @@
              "UrlScoringModel",
              enabled_by_default_desktop_only);
 
+// If enabled, skips ML scoring for those autocomplete matches which were
+// constructed by deduping an ML-eligible and ML-ineligible match.
+BASE_FEATURE(kEnableForceSkipMlScoring,
+             "EnableForceSkipMlScoring",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Actions in Suggest is a data-driven feature; it's considered enabled when the
 // data is available.
 // The feature flag below helps us tune feature behaviors.
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index d71a0e7..1d5acbd 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -128,6 +128,7 @@
 BASE_DECLARE_FEATURE(kMlUrlScoring);
 BASE_DECLARE_FEATURE(kMlUrlSearchBlending);
 BASE_DECLARE_FEATURE(kUrlScoringModel);
+BASE_DECLARE_FEATURE(kEnableForceSkipMlScoring);
 
 // Actions in Suggest - Action Chips for Entity Suggestions.
 // Data driven feature; flag helps tune behavior.
diff --git a/components/optimization_guide/core/model_execution/model_execution_features.cc b/components/optimization_guide/core/model_execution/model_execution_features.cc
index cb2da51..5900f9f 100644
--- a/components/optimization_guide/core/model_execution/model_execution_features.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_features.cc
@@ -47,7 +47,7 @@
 
 BASE_FEATURE(kModelAdaptationCompose,
              "ModelAdaptationCompose",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kOnDeviceModelTestFeature,
              "OnDeviceModelTestFeature",
diff --git a/components/optimization_guide/core/model_execution/model_execution_manager_unittest.cc b/components/optimization_guide/core/model_execution/model_execution_manager_unittest.cc
index a866313..b4d8c35 100644
--- a/components/optimization_guide/core/model_execution/model_execution_manager_unittest.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test.pb.h"
+#include "components/optimization_guide/core/model_execution/model_execution_features.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_access_controller.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
@@ -109,7 +110,9 @@
 class ModelExecutionManagerTest : public testing::Test {
  public:
   ModelExecutionManagerTest() {
-    scoped_feature_list_.InitAndDisableFeature(features::kTextSafetyClassifier);
+    scoped_feature_list_.InitWithFeatures(
+        {}, {features::kTextSafetyClassifier,
+             features::internal::kModelAdaptationCompose});
   }
   ~ModelExecutionManagerTest() override = default;
 
@@ -660,7 +663,9 @@
     : public ModelExecutionManagerTest {
  public:
   ModelExecutionManagerSafetyEnabledTest() {
-    scoped_feature_list_.InitAndEnableFeature(features::kTextSafetyClassifier);
+    scoped_feature_list_.InitWithFeatures(
+        {features::kTextSafetyClassifier},
+        {features::internal::kModelAdaptationCompose});
   }
 
  private:
diff --git a/components/optimization_guide/core/model_execution/model_execution_util.cc b/components/optimization_guide/core/model_execution/model_execution_util.cc
index e18502d..0f8fcf8 100644
--- a/components/optimization_guide/core/model_execution/model_execution_util.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_util.cc
@@ -14,8 +14,6 @@
 
 namespace optimization_guide {
 
-const char kModelOverrideSeparator[] = "|";
-
 // Helper method matches feature to corresponding FeatureTypeMap to set
 // LogAiDataRequest's request data.
 void SetExecutionRequest(
@@ -79,48 +77,6 @@
               kGenAILocalFoundationalModelEnterprisePolicySettings));
 }
 
-std::optional<on_device_model::AdaptationAssetPaths>
-GetOnDeviceModelAdaptationOverride(proto::ModelExecutionFeature feature) {
-  auto adaptations_override_switch =
-      switches::GetOnDeviceModelAdaptationsOverride();
-  if (!adaptations_override_switch) {
-    return std::nullopt;
-  }
-
-  for (const auto& adaptation_override :
-       base::SplitString(*adaptations_override_switch, ",",
-                         base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
-    std::vector<std::string> override_parts =
-        base::SplitString(adaptation_override, kModelOverrideSeparator,
-                          base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-    if (override_parts.size() != 2 && override_parts.size() != 3) {
-      // Input is malformed.
-      DLOG(ERROR) << "Invalid string format provided to the on-device model "
-                     "adaptations override";
-      return std::nullopt;
-    }
-    proto::ModelExecutionFeature this_feature;
-    if (!proto::ModelExecutionFeature_Parse(override_parts[0], &this_feature)) {
-      DLOG(ERROR) << "Invalid optimization target provided to the on-device "
-                     "model adaptations override";
-      return std::nullopt;
-    }
-    if (feature != this_feature) {
-      continue;
-    }
-    on_device_model::AdaptationAssetPaths adaptation_asset;
-    adaptation_asset.weights = *StringToFilePath(override_parts[1]);
-    if (!adaptation_asset.weights.IsAbsolute()) {
-      DLOG(ERROR)
-          << "Provided model adaptations weights file path must be absolute "
-          << adaptation_asset.weights;
-      return std::nullopt;
-    }
-    return adaptation_asset;
-  }
-  return std::nullopt;
-}
-
 OnDeviceModelLoadResult ConvertToOnDeviceModelLoadResult(
     on_device_model::mojom::LoadModelResult result) {
   switch (result) {
diff --git a/components/optimization_guide/core/model_execution/model_execution_util.h b/components/optimization_guide/core/model_execution/model_execution_util.h
index 286d8dd..c66ca9c3 100644
--- a/components/optimization_guide/core/model_execution/model_execution_util.h
+++ b/components/optimization_guide/core/model_execution/model_execution_util.h
@@ -13,7 +13,6 @@
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/proto/model_quality_service.pb.h"
 #include "components/optimization_guide/proto/on_device_model_execution_config.pb.h"
-#include "services/on_device_model/public/cpp/model_assets.h"
 #include "services/on_device_model/public/mojom/on_device_model_service.mojom.h"
 
 namespace optimization_guide {
@@ -73,12 +72,6 @@
 GetGenAILocalFoundationalModelEnterprisePolicySettings(
     PrefService* local_state);
 
-// Returns the model adaptation override from the command line. The override can
-// be specified as feature|weigths_path|model_pb_path, where the paths are all
-// absolute. model_pb_path is optional.
-std::optional<on_device_model::AdaptationAssetPaths>
-GetOnDeviceModelAdaptationOverride(proto::ModelExecutionFeature feature);
-
 OnDeviceModelLoadResult ConvertToOnDeviceModelLoadResult(
     on_device_model::mojom::LoadModelResult result);
 
diff --git a/components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.cc b/components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.cc
index 23843cc..3ccf44a 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.cc
@@ -104,14 +104,6 @@
       background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
   CHECK(features::internal::IsOnDeviceModelAdaptationEnabled(feature_));
-  if (const auto adaptations_override = GetOnDeviceModelAdaptationOverride(
-          ToModelExecutionFeatureProto(feature_))) {
-    // Do not register with model provider or component state manager, when
-    // override is provided.
-    model_provider_ = nullptr;
-    on_load_fn_.Run(nullptr);
-    return;
-  }
   component_state_manager_observation_.Observe(
       on_device_component_state_manager.get());
 }
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
index 3928580..20be0e3 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -98,7 +98,7 @@
            {"on_device_model_topk", "1"},
            {"on_device_model_temperature", "0"}}},
          {features::kTextSafetyClassifier, {}}},
-        {});
+        {features::internal::kModelAdaptationCompose});
     prefs::RegisterLocalStatePrefs(pref_service_.registry());
 
     // Fake the requirements to install the model.
@@ -2698,9 +2698,6 @@
                   .on_device_model_service_response()
                   .has_repeats());
   histogram_tester.ExpectUniqueSample(
-      "OptimizationGuide.ModelExecution.OnDeviceResponseHasRepeats.Compose",
-      true, 1);
-  histogram_tester.ExpectUniqueSample(
       "OptimizationGuide.ModelExecution.OnDeviceExecuteModelResult.Compose",
       ExecuteModelResult::kResponseHadRepeats, 1);
 }
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index eeacc5bc..4271623 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -524,8 +524,6 @@
     // If a repeat is detected, halt the response, and cancel/finish early.
     on_device_state_->receiver.reset();
     logged_response->set_has_repeats(true);
-    LogResponseHasRepeats(feature_, true);
-
     if (features::GetOnDeviceModelRetractRepeats()) {
       logged_response->set_status(
           proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
@@ -554,6 +552,10 @@
   // Stop timer, just in case we didn't already via OnResponse().
   on_device_state_->timer_for_first_response.Stop();
 
+  proto::OnDeviceModelServiceResponse* logged_response =
+      on_device_state_->MutableLoggedResponse();
+  LogResponseHasRepeats(feature_, logged_response->has_repeats());
+
   base::TimeDelta time_to_completion =
       base::TimeTicks::Now() - on_device_state_->start;
   base::UmaHistogramMediumTimes(
@@ -561,7 +563,7 @@
           {"OptimizationGuide.ModelExecution.OnDeviceResponseCompleteTime.",
            GetStringNameForModelExecutionFeature(feature_)}),
       time_to_completion);
-  on_device_state_->MutableLoggedResponse()->set_time_to_completion_millis(
+  logged_response->set_time_to_completion_millis(
       time_to_completion.InMilliseconds());
   on_device_state_->opts.model_client->OnResponseCompleted();
 
@@ -748,12 +750,6 @@
     return;
   }
 
-  if (!on_device_state_->MutableLoggedResponse()->has_repeats()) {
-    // Log completed responses with no repeats to calculate percentage of
-    // responses that have repeats.
-    LogResponseHasRepeats(feature_, false);
-  }
-
   if (features::ShouldUseTextSafetyRemoteFallbackForEligibleFeatures()) {
     RunTextSafetyRemoteFallbackAndCompletionCallback(std::move(*output));
     return;
diff --git a/components/optimization_guide/core/model_quality/OWNERS b/components/optimization_guide/core/model_quality/OWNERS
index 37e07883..d2b6e744 100644
--- a/components/optimization_guide/core/model_quality/OWNERS
+++ b/components/optimization_guide/core/model_quality/OWNERS
@@ -1,2 +1,3 @@
+curranmax@chromium.org
 spelchat@chromium.org
-sreejakshetty@chromium.org
\ No newline at end of file
+sreejakshetty@chromium.org
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 62b2a33..32d26d73 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -162,7 +162,7 @@
 // Whether to download the text safety classifier model.
 BASE_FEATURE(kTextSafetyClassifier,
              "TextSafetyClassifier",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Whether the text safety remote fallback should be used.
 BASE_FEATURE(kTextSafetyRemoteFallback,
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
index 48f79882..1a0b4b8 100644
--- a/components/printing/renderer/print_render_frame_helper.cc
+++ b/components/printing/renderer/print_render_frame_helper.cc
@@ -1255,15 +1255,34 @@
 }
 
 void PrintRenderFrameHelper::PrintRequestedPages() {
+  PrintRequestedPagesInternal(/*already_notified_frame=*/false);
+}
+
+void PrintRenderFrameHelper::PrintRequestedPagesInternal(
+    bool already_notified_frame) {
   ScopedIPC scoped_ipc(weak_ptr_factory_.GetWeakPtr());
-  if (ipc_nesting_level_ > kAllowedIpcDepthForPrint)
+  if (ipc_nesting_level_ > kAllowedIpcDepthForPrint) {
     return;
+  }
 
   blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
-  frame->DispatchBeforePrintEvent(/*print_client=*/nullptr);
-  // Don't print if the RenderFrame is gone.
-  if (render_frame_gone_)
-    return;
+
+  if (!already_notified_frame) {
+    frame->DispatchBeforePrintEvent(/*print_client=*/nullptr);
+    // Don't print if the RenderFrame is gone.
+    if (render_frame_gone_) {
+      return;
+    }
+
+    is_loading_ = frame->WillPrintSoon();
+    if (is_loading_) {
+      on_stop_loading_closure_ = base::BindOnce(
+          &PrintRenderFrameHelper::PrintRequestedPagesInternal,
+          weak_ptr_factory_.GetWeakPtr(), /*already_notified_frame=*/true);
+      SetupOnStopLoadingTimeout();
+      return;
+    }
+  }
 
   // If we are printing a frame with an internal PDF plugin element, find the
   // plugin node and print that instead.
@@ -2523,6 +2542,15 @@
   DCHECK(ret);
 }
 
+void PrintRenderFrameHelper::SetupOnStopLoadingTimeout() {
+  static constexpr base::TimeDelta kLoadEventTimeout = base::Seconds(2);
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&PrintRenderFrameHelper::DidFinishLoadForPrinting,
+                     weak_ptr_factory_.GetWeakPtr()),
+      kLoadEventTimeout);
+}
+
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 void PrintRenderFrameHelper::ShowScriptedPrintPreview() {
   if (is_scripted_preview_delayed_) {
@@ -2532,19 +2560,6 @@
   }
 }
 
-void PrintRenderFrameHelper::WaitForLoad(PrintPreviewRequestType type) {
-  static constexpr base::TimeDelta kLoadEventTimeout = base::Seconds(2);
-
-  on_stop_loading_closure_ =
-      base::BindOnce(&PrintRenderFrameHelper::RequestPrintPreview,
-                     weak_ptr_factory_.GetWeakPtr(), type, true);
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&PrintRenderFrameHelper::DidFinishLoadForPrinting,
-                     weak_ptr_factory_.GetWeakPtr()),
-      kLoadEventTimeout);
-}
-
 void PrintRenderFrameHelper::RequestPrintPreview(PrintPreviewRequestType type,
                                                  bool already_notified_frame) {
   auto weak_this = weak_ptr_factory_.GetWeakPtr();
@@ -2555,6 +2570,19 @@
     }
 
     is_loading_ = print_preview_context_.source_frame()->WillPrintSoon();
+    if (is_loading_) {
+      // Wait for DidStopLoading, for two reasons:
+      // * To give the document time to finish loading any pending resources
+      //   that are desired for printing.
+      // * Plugins may not know the correct `is_modifiable` value until they
+      //   are fully loaded, which occurs when DidStopLoading() is called.
+      //   Defer showing the preview until then.
+      on_stop_loading_closure_ =
+          base::BindOnce(&PrintRenderFrameHelper::RequestPrintPreview,
+                         weak_ptr_factory_.GetWeakPtr(), type, true);
+      SetupOnStopLoadingTimeout();
+      return;
+    }
   }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -2578,16 +2606,6 @@
       //    loaded.
       RecordDebugEvent(DebugEvent::kRequestPrintPreviewScripted);
       is_scripted_preview_delayed_ = true;
-      if (is_loading_) {
-        // Wait for DidStopLoading, for two reasons:
-        // * To give the document time to finish loading any pending resources
-        ///  that are desired for printing.
-        // * Plugins may not know the correct|is_modifiable| value until they
-        //   are fully loaded, which occurs when DidStopLoading() is called.
-        //   Defer showing the preview until then.
-        WaitForLoad(type);
-        return;
-      }
       base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE,
           base::BindOnce(&PrintRenderFrameHelper::ShowScriptedPrintPreview,
@@ -2618,12 +2636,6 @@
     case PrintPreviewRequestType::kUserInitiatedEntireFrame: {
       RecordDebugEvent(
           DebugEvent::kRequestPrintPreviewUserInitiatedEntireFrame);
-      // See comment under PRINT_PREVIEW_SCRIPTED.
-      if (is_loading_) {
-        WaitForLoad(type);
-        return;
-      }
-
       break;
     }
     case PrintPreviewRequestType::kUserInitiatedSelection: {
@@ -2636,12 +2648,6 @@
     case PrintPreviewRequestType::kUserInitiatedContextNode: {
       RecordDebugEvent(
           DebugEvent::kRequestPrintPreviewUserInitiatedContextNode);
-      // See comment under PRINT_PREVIEW_SCRIPTED.
-      if (is_loading_) {
-        WaitForLoad(type);
-        return;
-      }
-
       params->webnode_only = true;
       break;
     }
diff --git a/components/printing/renderer/print_render_frame_helper.h b/components/printing/renderer/print_render_frame_helper.h
index cc65c4ac5..14de0297 100644
--- a/components/printing/renderer/print_render_frame_helper.h
+++ b/components/printing/renderer/print_render_frame_helper.h
@@ -624,7 +624,8 @@
     int count_ = 0;
   };
 
-  void WaitForLoad(PrintPreviewRequestType type);
+  void SetupOnStopLoadingTimeout();
+  void PrintRequestedPagesInternal(bool already_notified_frame);
 
   ScriptingThrottler scripting_throttler_;
 
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
index e3a379f..7d39d0b 100644
--- a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
@@ -823,8 +823,8 @@
 
 std::string SerializeReportType(ClientPhishingRequest::ReportType report_type) {
   switch (report_type) {
-    case ClientPhishingRequest::UNKNOWN:
-      return "UNKNOWN";
+    case ClientPhishingRequest::REPORT_TYPE_UNSPECIFIED:
+      return "REPORT_TYPE_UNSPECIFIED";
     case ClientPhishingRequest::FULL_REPORT:
       return "FULL_REPORT";
     case ClientPhishingRequest::SAMPLE_REPORT:
diff --git a/components/safe_browsing/core/common/proto/csd.proto b/components/safe_browsing/core/common/proto/csd.proto
index 56cfc21..e74ca68a 100644
--- a/components/safe_browsing/core/common/proto/csd.proto
+++ b/components/safe_browsing/core/common/proto/csd.proto
@@ -256,7 +256,7 @@
   optional ImageFeatureEmbedding image_feature_embedding = 29;
 
   enum ReportType {
-    UNKNOWN = 0;
+    REPORT_TYPE_UNSPECIFIED = 0;
     // Normal/full ping to Safe Browsing containing all the fields in the
     // ClientPhishingRequest.
     FULL_REPORT = 1;
diff --git a/components/services/storage/public/cpp/filesystem/BUILD.gn b/components/services/storage/public/cpp/filesystem/BUILD.gn
index 84608110..36e1dbec 100644
--- a/components/services/storage/public/cpp/filesystem/BUILD.gn
+++ b/components/services/storage/public/cpp/filesystem/BUILD.gn
@@ -21,6 +21,8 @@
     "//mojo/public/cpp/bindings",
   ]
 
+  deps = [ "//components/crash/core/common:crash_key" ]
+
   defines = [ "IS_STORAGE_SERVICE_FILESYSTEM_SUPPORT_IMPL" ]
 }
 
diff --git a/components/services/storage/public/cpp/filesystem/DEPS b/components/services/storage/public/cpp/filesystem/DEPS
new file mode 100644
index 0000000..64556717
--- /dev/null
+++ b/components/services/storage/public/cpp/filesystem/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  # TODO(crbug.com/340398745): remove after fixing.
+  "+components/crash/core/common/crash_key.h",
+]
diff --git a/components/services/storage/public/cpp/filesystem/filesystem_impl.cc b/components/services/storage/public/cpp/filesystem/filesystem_impl.cc
index 7a311d7..1dc39c322 100644
--- a/components/services/storage/public/cpp/filesystem/filesystem_impl.cc
+++ b/components/services/storage/public/cpp/filesystem/filesystem_impl.cc
@@ -4,12 +4,13 @@
 
 #include "components/services/storage/public/cpp/filesystem/filesystem_impl.h"
 
-#include <set>
+#include <map>
 #include <vector>
 
 #include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/debug/dump_without_crashing.h"
+#include "base/debug/stack_trace.h"
 #include "base/files/file.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
@@ -19,6 +20,7 @@
 #include "base/synchronization/lock.h"
 #include "base/types/expected_macros.h"
 #include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -41,7 +43,20 @@
   bool AddLock(const base::FilePath& path) {
     DCHECK(path.IsAbsolute());
     base::AutoLock lock(lock_);
-    auto result = lock_paths_.insert(path.NormalizePathSeparators());
+    auto result = lock_paths_.insert(std::make_pair(
+        path.NormalizePathSeparators(), base::debug::StackTrace()));
+
+    // TODO(crbug.com/340398745): resolve this mystery and remove this
+    // block, or even replace it with a CHECK.
+    if (!result.second) {
+      static crash_reporter::CrashKeyString<1024> trace_key(
+          "crbug/340398745/existing_lock_stack");
+      crash_reporter::SetCrashKeyStringToStackTrace(&trace_key,
+                                                    result.first->second);
+      base::debug::DumpWithoutCrashing();
+      trace_key.Clear();
+    }
+
     return result.second;
   }
 
@@ -54,7 +69,8 @@
 
  private:
   base::Lock lock_;
-  std::set<base::FilePath> lock_paths_ GUARDED_BY(lock_);
+  std::map<base::FilePath, base::debug::StackTrace> lock_paths_
+      GUARDED_BY(lock_);
 };
 
 // Get the global singleton instance of LockTable. This returned object is
@@ -269,9 +285,6 @@
     return base::unexpected(file.error_details());
 
   if (!GetLockTable().AddLock(path)) {
-    // TODO(crbug.com/340398745): resolve this mystery and remove this line, or
-    // even replace it with a CHECK.
-    base::debug::DumpWithoutCrashing();
     return base::unexpected(base::File::FILE_ERROR_IN_USE);
   }
 
diff --git a/components/speech/BUILD.gn b/components/speech/BUILD.gn
index 2be48280..842b24f 100644
--- a/components/speech/BUILD.gn
+++ b/components/speech/BUILD.gn
@@ -18,6 +18,17 @@
     "upstream_loader_client.h",
   ]
 
+  if (!is_android) {
+    sources += [
+      "endpointer/endpointer.cc",
+      "endpointer/endpointer.h",
+      "endpointer/energy_endpointer.cc",
+      "endpointer/energy_endpointer.h",
+      "endpointer/energy_endpointer_params.cc",
+      "endpointer/energy_endpointer_params.h",
+    ]
+  }
+
   public_deps = [ "//base" ]
   deps = [
     "//mojo/public/cpp/bindings",
@@ -31,7 +42,16 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "chunked_byte_buffer_unittest.cc" ]
+  sources = [
+    "chunked_byte_buffer_unittest.cc",
+  ]
+
+  if (!is_android) {
+    sources += [
+      "endpointer/endpointer_unittest.cc",
+    ]
+  }
+
   deps = [
     ":speech",
     "//testing/gtest",
diff --git a/content/browser/speech/endpointer/endpointer.cc b/components/speech/endpointer/endpointer.cc
similarity index 89%
rename from content/browser/speech/endpointer/endpointer.cc
rename to components/speech/endpointer/endpointer.cc
index 32d767fe..7d9518ad 100644
--- a/content/browser/speech/endpointer/endpointer.cc
+++ b/components/speech/endpointer/endpointer.cc
@@ -1,8 +1,8 @@
-// Copyright 2012 The Chromium Authors
+// 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 "content/browser/speech/endpointer/endpointer.h"
+#include "components/speech/endpointer/endpointer.h"
 
 #include "base/time/time.h"
 #include "components/speech/audio_buffer.h"
@@ -10,9 +10,9 @@
 namespace {
 const int64_t kMicrosecondsPerSecond = base::Time::kMicrosecondsPerSecond;
 const int kFrameRate = 50;  // 1 frame = 20ms of audio.
-}
+}  // namespace
 
-namespace content {
+namespace speech {
 
 Endpointer::Endpointer(int sample_rate)
     : speech_input_possibly_complete_silence_length_us_(-1),
@@ -61,7 +61,7 @@
   waiting_for_speech_complete_timeout_ = false;
   speech_previously_detected_ = false;
   speech_input_complete_ = false;
-  audio_frame_time_us_ = 0; // Reset time for packets sent to endpointer.
+  audio_frame_time_us_ = 0;  // Reset time for packets sent to endpointer.
   speech_end_time_us_ = -1;
   speech_start_time_us_ = -1;
 }
@@ -99,10 +99,8 @@
   int sample_index = 0;
   while (sample_index + frame_size_ <= num_samples) {
     // Have the endpointer process the frame.
-    energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_,
-                                         audio_data + sample_index,
-                                         frame_size_,
-                                         rms_out);
+    energy_endpointer_.ProcessAudioFrame(
+        audio_frame_time_us_, audio_data + sample_index, frame_size_, rms_out);
     sample_index += frame_size_;
     audio_frame_time_us_ +=
         (frame_size_ * kMicrosecondsPerSecond) / sample_rate_;
@@ -133,7 +131,7 @@
       // Speech possibly complete timeout.
       if ((waiting_for_speech_possibly_complete_timeout_) &&
           (ep_time - speech_end_time_us_ >
-              speech_input_possibly_complete_silence_length_us_)) {
+           speech_input_possibly_complete_silence_length_us_)) {
         waiting_for_speech_possibly_complete_timeout_ = false;
       }
       if (waiting_for_speech_complete_timeout_) {
@@ -149,8 +147,7 @@
           requested_silence_length =
               long_speech_input_complete_silence_length_us_;
         } else {
-          requested_silence_length =
-              speech_input_complete_silence_length_us_;
+          requested_silence_length = speech_input_complete_silence_length_us_;
         }
 
         // Speech complete timeout.
@@ -165,4 +162,4 @@
   return ep_status;
 }
 
-}  // namespace content
+}  // namespace speech
diff --git a/content/browser/speech/endpointer/endpointer.h b/components/speech/endpointer/endpointer.h
similarity index 89%
rename from content/browser/speech/endpointer/endpointer.h
rename to components/speech/endpointer/endpointer.h
index 7695d5ec..db54ea0 100644
--- a/content/browser/speech/endpointer/endpointer.h
+++ b/components/speech/endpointer/endpointer.h
@@ -1,19 +1,18 @@
-// Copyright 2012 The Chromium Authors
+// 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 CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
-#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#define COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
 
 #include <stdint.h>
 
-#include "content/browser/speech/endpointer/energy_endpointer.h"
-#include "content/common/content_export.h"
+#include "components/speech/endpointer/energy_endpointer.h"
 
 class EpStatus;
 class AudioChunk;
 
-namespace content {
+namespace speech {
 
 // A simple interface to the underlying energy-endpointer implementation, this
 // class lets callers provide audio as being recorded and let them poll to find
@@ -43,7 +42,7 @@
 // The timeout length is speech_input_complete_silence_length until
 // long_speech_length, when it changes to
 // long_speech_input_complete_silence_length.
-class CONTENT_EXPORT Endpointer {
+class Endpointer {
  public:
   explicit Endpointer(int sample_rate);
 
@@ -70,9 +69,7 @@
 
   // Returns true if the endpointer detected reasonable audio levels above
   // background noise which could be user speech, false if not.
-  bool DidStartReceivingSpeech() const {
-    return speech_previously_detected_;
-  }
+  bool DidStartReceivingSpeech() const { return speech_previously_detected_; }
 
   bool IsEstimatingEnvironment() const {
     return energy_endpointer_.estimating_environment();
@@ -94,9 +91,7 @@
     long_speech_length_us_ = time_us;
   }
 
-  bool speech_input_complete() const {
-    return speech_input_complete_;
-  }
+  bool speech_input_complete() const { return speech_input_complete_; }
 
   // RMS background noise level in dB.
   float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); }
@@ -148,6 +143,6 @@
   int32_t frame_size_;
 };
 
-}  // namespace content
+}  // namespace speech
 
-#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENDPOINTER_H_
diff --git a/content/browser/speech/endpointer/endpointer_unittest.cc b/components/speech/endpointer/endpointer_unittest.cc
similarity index 92%
rename from content/browser/speech/endpointer/endpointer_unittest.cc
rename to components/speech/endpointer/endpointer_unittest.cc
index c00a161e..0f7c2cd 100644
--- a/content/browser/speech/endpointer/endpointer_unittest.cc
+++ b/components/speech/endpointer/endpointer_unittest.cc
@@ -1,25 +1,26 @@
-// Copyright 2012 The Chromium Authors
+// 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/speech/endpointer/endpointer.h"
+
 #include <stdint.h>
 
 #include "base/memory/raw_ptr.h"
 #include "components/speech/audio_buffer.h"
-#include "content/browser/speech/endpointer/endpointer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
-const int kFrameRate = 50;  // 20 ms long frames for AMR encoding.
+const int kFrameRate = 50;     // 20 ms long frames for AMR encoding.
 const int kSampleRate = 8000;  // 8 k samples per second for AMR encoding.
 
 // At 8 sample per second a 20 ms frame is 160 samples, which corrsponds
 // to the AMR codec.
 const int kFrameSize = kSampleRate / kFrameRate;  // 160 samples.
 static_assert(kFrameSize == 160, "invalid frame size");
-}
+}  // namespace
 
-namespace content {
+namespace speech {
 
 class FrameProcessor {
  public:
@@ -51,7 +52,7 @@
     // Create random samples.
     for (int i = 0; i < kFrameSize; ++i) {
       float randNum = static_cast<float>(rand() - (RAND_MAX / 2)) /
-          static_cast<float>(RAND_MAX);
+                      static_cast<float>(RAND_MAX);
       samples[i] = static_cast<int16_t>(gain * randNum);
     }
 
@@ -59,12 +60,15 @@
     time += static_cast<int64_t>(kFrameSize * (1e6 / kSampleRate));
 
     // Log the status.
-    if (20 == frame_count)
+    if (20 == frame_count) {
       EXPECT_EQ(EP_PRE_SPEECH, ep_status);
-    if (70 == frame_count)
+    }
+    if (70 == frame_count) {
       EXPECT_EQ(EP_SPEECH_PRESENT, ep_status);
-    if (120 == frame_count)
+    }
+    if (120 == frame_count) {
       EXPECT_EQ(EP_PRE_SPEECH, ep_status);
+    }
   }
 }
 
@@ -154,4 +158,4 @@
   endpointer.EndSession();
 }
 
-}  // namespace content
+}  // namespace speech
diff --git a/content/browser/speech/endpointer/energy_endpointer.cc b/components/speech/endpointer/energy_endpointer.cc
similarity index 87%
rename from content/browser/speech/endpointer/energy_endpointer.cc
rename to components/speech/endpointer/energy_endpointer.cc
index ec72e2b7..f8dba53 100644
--- a/content/browser/speech/endpointer/energy_endpointer.cc
+++ b/components/speech/endpointer/energy_endpointer.cc
@@ -1,4 +1,4 @@
-// Copyright 2012 The Chromium Authors
+// 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.
 //
@@ -6,7 +6,7 @@
 // based of, see
 // https://wiki.corp.google.com/twiki/bin/view/Main/ChromeGoogleCodeXRef
 
-#include "content/browser/speech/endpointer/energy_endpointer.h"
+#include "components/speech/endpointer/energy_endpointer.h"
 
 #include <math.h>
 #include <stddef.h>
@@ -35,14 +35,15 @@
 }
 
 float GetDecibel(float value) {
-  if (value > 1.0e-100)
+  if (value > 1.0e-100) {
     return 20 * log10(value);
+  }
   return -2000.0;
 }
 
 }  // namespace
 
-namespace content {
+namespace speech {
 
 // Stores threshold-crossing histories for making decisions about the speech
 // state.
@@ -80,7 +81,7 @@
 void EnergyEndpointer::HistoryRing::SetRing(int size, bool initial_state) {
   insertion_index_ = 0;
   decision_points_.clear();
-  DecisionPoint init = { -1, initial_state };
+  DecisionPoint init = {-1, initial_state};
   decision_points_.resize(size, init);
 }
 
@@ -92,33 +93,39 @@
 
 int64_t EnergyEndpointer::HistoryRing::EndTime() const {
   int ind = insertion_index_ - 1;
-  if (ind < 0)
+  if (ind < 0) {
     ind = decision_points_.size() - 1;
+  }
   return decision_points_[ind].time_us;
 }
 
 float EnergyEndpointer::HistoryRing::RingSum(float duration_sec) {
-  if (decision_points_.empty())
+  if (decision_points_.empty()) {
     return 0.0;
+  }
 
   int64_t sum_us = 0;
   int ind = insertion_index_ - 1;
-  if (ind < 0)
+  if (ind < 0) {
     ind = decision_points_.size() - 1;
+  }
   int64_t end_us = decision_points_[ind].time_us;
   bool is_on = decision_points_[ind].decision;
   int64_t start_us =
       end_us - static_cast<int64_t>(0.5 + (1.0e6 * duration_sec));
-  if (start_us < 0)
+  if (start_us < 0) {
     start_us = 0;
+  }
   size_t n_summed = 1;  // n points ==> (n-1) intervals
   while ((decision_points_[ind].time_us > start_us) &&
          (n_summed < decision_points_.size())) {
     --ind;
-    if (ind < 0)
+    if (ind < 0) {
       ind = decision_points_.size() - 1;
-    if (is_on)
+    }
+    if (is_on) {
       sum_us += end_us - decision_points_[ind].time_us;
+    }
     is_on = decision_points_[ind].decision;
     end_us = decision_points_[ind].time_us;
     n_summed++;
@@ -142,11 +149,9 @@
       rms_adapt_(0),
       start_lag_(0),
       end_lag_(0),
-      user_input_start_time_us_(0) {
-}
+      user_input_start_time_us_(0) {}
 
-EnergyEndpointer::~EnergyEndpointer() {
-}
+EnergyEndpointer::~EnergyEndpointer() {}
 
 int EnergyEndpointer::TimeToFrame(float time) const {
   return static_cast<int32_t>(0.5 + (time / params_.frame_period()));
@@ -181,16 +186,19 @@
   // depends upon ep_frame_period being set correctly in the factory
   // that did this instantiation.
   max_window_dur_ = params_.onset_window();
-  if (params_.speech_on_window() > max_window_dur_)
+  if (params_.speech_on_window() > max_window_dur_) {
     max_window_dur_ = params_.speech_on_window();
-  if (params_.offset_window() > max_window_dur_)
+  }
+  if (params_.offset_window() > max_window_dur_) {
     max_window_dur_ = params_.offset_window();
+  }
   Restart(true);
 
-  offset_confirm_dur_sec_ = params_.offset_window() -
-                            params_.offset_confirm_dur();
-  if (offset_confirm_dur_sec_ < 0.0)
+  offset_confirm_dur_sec_ =
+      params_.offset_window() - params_.offset_confirm_dur();
+  if (offset_confirm_dur_sec_ < 0.0) {
     offset_confirm_dur_sec_ = 0.0;
+  }
 
   user_input_start_time_us_ = 0;
 
@@ -208,10 +216,10 @@
   frame_counter_ = 0;  // Used for rapid initial update of levels.
 
   sample_rate_ = params_.sample_rate();
-  start_lag_ = static_cast<int>(sample_rate_ /
-                                params_.max_fundamental_frequency());
-  end_lag_ = static_cast<int>(sample_rate_ /
-                              params_.min_fundamental_frequency());
+  start_lag_ =
+      static_cast<int>(sample_rate_ / params_.max_fundamental_frequency());
+  end_lag_ =
+      static_cast<int>(sample_rate_ / params_.min_fundamental_frequency());
 }
 
 void EnergyEndpointer::StartSession() {
@@ -268,8 +276,9 @@
         if (tsum > params_.onset_confirm_dur()) {
           status_ = EP_SPEECH_PRESENT;
         } else {  // If signal is not maintained, drop back to pre-speech.
-          if (tsum <= params_.onset_detect_dur())
+          if (tsum <= params_.onset_detect_dur()) {
             status_ = EP_PRE_SPEECH;
+          }
         }
         break;
       }
@@ -279,8 +288,9 @@
         // smaller residency time in the on_ring, than was required to
         // enter the SPEECH_PERSENT state.
         float on_time = history_->RingSum(params_.speech_on_window());
-        if (on_time < params_.on_maintain_dur())
+        if (on_time < params_.on_maintain_dur()) {
           status_ = EP_POSSIBLE_OFFSET;
+        }
         break;
       }
 
@@ -293,8 +303,9 @@
           status_ = EP_PRE_SPEECH;  // Automatically reset for next utterance.
         } else {  // If speech picks up again we allow return to SPEECH_PRESENT.
           if (history_->RingSum(params_.speech_on_window()) >=
-              params_.on_maintain_dur())
+              params_.on_maintain_dur()) {
             status_ = EP_SPEECH_PRESENT;
+          }
         }
         break;
 
@@ -320,23 +331,25 @@
         } else {
           rms_adapt_ = (0.95f * rms_adapt_) + (0.05f * rms);
         }
-        float target_threshold = 0.3f * rms_adapt_ +  noise_level_;
-        decision_threshold_ = (.90f * decision_threshold_) +
-                              (0.10f * target_threshold);
+        float target_threshold = 0.3f * rms_adapt_ + noise_level_;
+        decision_threshold_ =
+            (.90f * decision_threshold_) + (0.10f * target_threshold);
       }
     }
 
     // Set a floor
-    if (decision_threshold_ < params_.min_decision_threshold())
+    if (decision_threshold_ < params_.min_decision_threshold()) {
       decision_threshold_ = params_.min_decision_threshold();
+    }
   }
 
   // Update speech and noise levels.
   UpdateLevels(rms);
   ++frame_counter_;
 
-  if (rms_out)
+  if (rms_out) {
     *rms_out = GetDecibel(rms);
+  }
 }
 
 float EnergyEndpointer::GetNoiseLevelDb() const {
@@ -350,7 +363,7 @@
     // Alpha increases from 0 to (k-1)/k where k is the number of time
     // steps in the initial adaptation period.
     float alpha = static_cast<float>(frame_counter_) /
-        static_cast<float>(fast_update_frames_);
+                  static_cast<float>(fast_update_frames_);
     noise_level_ = (alpha * noise_level_) + ((1 - alpha) * rms);
     DVLOG(1) << "FAST UPDATE, frame_counter_ " << frame_counter_
              << ", fast_update_frames_ " << fast_update_frames_;
@@ -358,16 +371,18 @@
     // Update Noise level. The noise level adapts quickly downward, but
     // slowly upward. The noise_level_ parameter is not currently used
     // for threshold adaptation. It is used for UI feedback.
-    if (noise_level_ < rms)
+    if (noise_level_ < rms) {
       noise_level_ = (0.999f * noise_level_) + (0.001f * rms);
-    else
+    } else {
       noise_level_ = (0.95f * noise_level_) + (0.05f * rms);
+    }
   }
   if (estimating_environment_ || (frame_counter_ < fast_update_frames_)) {
-    decision_threshold_ = noise_level_ * 2; // 6dB above noise level.
+    decision_threshold_ = noise_level_ * 2;  // 6dB above noise level.
     // Set a floor
-    if (decision_threshold_ < params_.min_decision_threshold())
+    if (decision_threshold_ < params_.min_decision_threshold()) {
       decision_threshold_ = params_.min_decision_threshold();
+    }
   }
 }
 
@@ -376,4 +391,4 @@
   return status_;
 }
 
-}  // namespace content
+}  // namespace speech
diff --git a/content/browser/speech/endpointer/energy_endpointer.h b/components/speech/endpointer/energy_endpointer.h
similarity index 89%
rename from content/browser/speech/endpointer/energy_endpointer.h
rename to components/speech/endpointer/energy_endpointer.h
index 9f869a3..bbea6a9 100644
--- a/content/browser/speech/endpointer/energy_endpointer.h
+++ b/components/speech/endpointer/energy_endpointer.h
@@ -1,4 +1,4 @@
-// Copyright 2012 The Chromium Authors
+// 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.
 
@@ -34,18 +34,17 @@
 // accept. The false accepts can be ignored by setting
 // ep_contamination_rejection_period.
 
-#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
-#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#define COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
 
 #include <stdint.h>
 
 #include <memory>
 #include <vector>
 
-#include "content/browser/speech/endpointer/energy_endpointer_params.h"
-#include "content/common/content_export.h"
+#include "components/speech/endpointer/energy_endpointer_params.h"
 
-namespace content {
+namespace speech {
 
 // Endpointer status codes
 enum EpStatus {
@@ -56,7 +55,7 @@
   EP_POST_SPEECH,
 };
 
-class CONTENT_EXPORT EnergyEndpointer {
+class EnergyEndpointer {
  public:
   // The default construction MUST be followed by Init(), before any
   // other use can be made of the instance.
@@ -94,9 +93,7 @@
   // corresponding to the most recently computed frame.
   EpStatus Status(int64_t* status_time_us) const;
 
-  bool estimating_environment() const {
-    return estimating_environment_;
-  }
+  bool estimating_environment() const { return estimating_environment_; }
 
   // Returns estimated noise level in dB.
   float GetNoiseLevelDb() const;
@@ -116,7 +113,7 @@
   // the 'time' (in seconds).
   int TimeToFrame(float time) const;
 
-  EpStatus status_;  // The current state of this instance.
+  EpStatus status_;               // The current state of this instance.
   float offset_confirm_dur_sec_;  // max on time allowed to confirm POST_SPEECH
   int64_t
       endpointer_time_us_;  // Time of the most recently received audio frame.
@@ -125,7 +122,7 @@
   int64_t
       frame_counter_;     // Number of frames seen. Used for initial adaptation.
   float max_window_dur_;  // Largest search window size (seconds)
-  float sample_rate_;  // Sampling rate.
+  float sample_rate_;     // Sampling rate.
 
   // Ring buffers to hold the speech activity history.
   std::unique_ptr<HistoryRing> history_;
@@ -157,6 +154,6 @@
   int64_t user_input_start_time_us_;
 };
 
-}  // namespace content
+}  // namespace speech
 
-#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
diff --git a/content/browser/speech/endpointer/energy_endpointer_params.cc b/components/speech/endpointer/energy_endpointer_params.cc
similarity index 91%
rename from content/browser/speech/endpointer/energy_endpointer_params.cc
rename to components/speech/endpointer/energy_endpointer_params.cc
index d9b70f29..21194c41 100644
--- a/content/browser/speech/endpointer/energy_endpointer_params.cc
+++ b/components/speech/endpointer/energy_endpointer_params.cc
@@ -1,10 +1,10 @@
-// Copyright 2012 The Chromium Authors
+// 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 "content/browser/speech/endpointer/energy_endpointer_params.h"
+#include "components/speech/endpointer/energy_endpointer_params.h"
 
-namespace content {
+namespace speech {
 
 EnergyEndpointerParams::EnergyEndpointerParams() {
   SetDefaults();
@@ -50,4 +50,4 @@
   contamination_rejection_period_ = source.contamination_rejection_period();
 }
 
-}  //  namespace content
+}  //  namespace speech
diff --git a/content/browser/speech/endpointer/energy_endpointer_params.h b/components/speech/endpointer/energy_endpointer_params.h
similarity index 79%
rename from content/browser/speech/endpointer/energy_endpointer_params.h
rename to components/speech/endpointer/energy_endpointer_params.h
index bf13dd2..b5341c3 100644
--- a/content/browser/speech/endpointer/energy_endpointer_params.h
+++ b/components/speech/endpointer/energy_endpointer_params.h
@@ -1,16 +1,14 @@
-// Copyright 2012 The Chromium Authors
+// 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 CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
-#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#ifndef COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#define COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
 
-#include "content/common/content_export.h"
-
-namespace content {
+namespace speech {
 
 // Input parameters for the EnergyEndpointer class.
-class CONTENT_EXPORT EnergyEndpointerParams {
+class EnergyEndpointerParams {
  public:
   EnergyEndpointerParams();
 
@@ -20,9 +18,7 @@
 
   // Accessors and mutators
   float frame_period() const { return frame_period_; }
-  void set_frame_period(float frame_period) {
-    frame_period_ = frame_period;
-  }
+  void set_frame_period(float frame_period) { frame_period_ = frame_period; }
 
   float frame_duration() const { return frame_duration_; }
   void set_frame_duration(float frame_duration) {
@@ -104,16 +100,16 @@
   }
 
  private:
-  float frame_period_;  // Frame period
-  float frame_duration_;  // Window size
-  float onset_window_;  // Interval scanned for onset activity
-  float speech_on_window_;  // Inverval scanned for ongoing speech
-  float offset_window_;  // Interval scanned for offset evidence
-  float offset_confirm_dur_;  // Silence duration required to confirm offset
-  float decision_threshold_;  // Initial rms detection threshold
+  float frame_period_;            // Frame period
+  float frame_duration_;          // Window size
+  float onset_window_;            // Interval scanned for onset activity
+  float speech_on_window_;        // Inverval scanned for ongoing speech
+  float offset_window_;           // Interval scanned for offset evidence
+  float offset_confirm_dur_;      // Silence duration required to confirm offset
+  float decision_threshold_;      // Initial rms detection threshold
   float min_decision_threshold_;  // Minimum rms detection threshold
-  float fast_update_dur_;  // Period for initial estimation of levels.
-  float sample_rate_;  // Expected sample rate.
+  float fast_update_dur_;         // Period for initial estimation of levels.
+  float sample_rate_;             // Expected sample rate.
 
   // Time to add on either side of endpoint threshold crossings
   float endpoint_margin_;
@@ -132,6 +128,6 @@
   float contamination_rejection_period_;
 };
 
-}  //  namespace content
+}  //  namespace speech
 
-#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#endif  // COMPONENTS_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
diff --git a/components/user_education/views/help_bubble_view.cc b/components/user_education/views/help_bubble_view.cc
index 53ae95e..f3ee834 100644
--- a/components/user_education/views/help_bubble_view.cc
+++ b/components/user_education/views/help_bubble_view.cc
@@ -1000,21 +1000,22 @@
 #if BUILDFLAG(IS_LINUX)
   // Help bubbles anchored to menus may be clipped to their anchors' bounds,
   // resulting in visual errors, unless they use accelerated rendering. See
-  // crbug.com/1445770 for details.
+  // crbug.com/1445770 for details. This also applies to bubbles anchored to
+  // all accelerated windows below a certain size, especially those which are
+  // not top-level application windows (see crbug.com/340523110).
   //
-  // In Views, [nearly] all menus have a scroll container as their root view.
-  // Key off of this in order to minimize the number of widgets that are forced
-  // to be accelerated. Accelerated widgets are "desktop native" widgets and
-  // interact with the OS window activation system; this is, in turn, a problem
-  // for Linux because of known technical limitations around window activation.
+  // Because it is not possible to know exactly if a bubble will correctly fit
+  // in the bounds of its ancestor accelerator widget, due to things like
+  // anchor positioning and the possibility that a window size could change,
+  // make all Linux help bubbles accelerated.
   //
-  // See the following bug for more information regarding window activation
-  // issues in Weston, the windowing environment used on chrome's Wayland
-  // testbots:
+  // Note: accelerated widgets are "desktop native" widgets and interact with
+  // the OS window activation system; this is, in turn, a problem for Linux
+  // because of known technical limitations around window activation. See the
+  // following bug for more information regarding window activation issues in
+  // Weston, the windowing environment used on chrome's Wayland test-bots:
   // https://gitlab.freedesktop.org/wayland/weston/-/issues/669
-  if (GetAnchorAsMenuItem(this) != nullptr) {
-    params->use_accelerated_widget_override = true;
-  }
+  params->use_accelerated_widget_override = true;
 #endif
 }
 
diff --git a/components/viz/service/display_embedder/compositor_gpu_thread.cc b/components/viz/service/display_embedder/compositor_gpu_thread.cc
index 318ea52..a38c889 100644
--- a/components/viz/service/display_embedder/compositor_gpu_thread.cc
+++ b/components/viz/service/display_embedder/compositor_gpu_thread.cc
@@ -22,7 +22,6 @@
 #include "gpu/vulkan/buildflags.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
-#include "ui/gl/gl_features.h"
 #include "ui/gl/gl_surface_egl.h"
 #include "ui/gl/init/gl_factory.h"
 
@@ -143,7 +142,13 @@
   attribs.angle_context_virtualization_group_number =
       gl::AngleContextVirtualizationGroup::kDrDc;
 
-  attribs.can_skip_validation = !features::IsANGLEValidationEnabled();
+  bool enable_angle_validation = features::IsANGLEValidationEnabled();
+#if DCHECK_IS_ON()
+  // Force validation on for all debug builds and testing
+  enable_angle_validation = true;
+#endif
+
+  attribs.can_skip_validation = !enable_angle_validation;
 
   // Compositor thread context doesn't need access textures and semaphores
   // created with other contexts.
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 42ad9b4..cd93a04 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -456,11 +456,13 @@
     "attribution_reporting/attribution_report_network_sender.cc",
     "attribution_reporting/attribution_report_network_sender.h",
     "attribution_reporting/attribution_report_sender.h",
-    "attribution_reporting/attribution_storage.h",
-    "attribution_reporting/attribution_storage_delegate.cc",
-    "attribution_reporting/attribution_storage_delegate.h",
-    "attribution_reporting/attribution_storage_delegate_impl.cc",
-    "attribution_reporting/attribution_storage_delegate_impl.h",
+    "attribution_reporting/attribution_resolver.h",
+    "attribution_reporting/attribution_resolver_delegate.cc",
+    "attribution_reporting/attribution_resolver_delegate.h",
+    "attribution_reporting/attribution_resolver_delegate_impl.cc",
+    "attribution_reporting/attribution_resolver_delegate_impl.h",
+    "attribution_reporting/attribution_resolver_impl.cc",
+    "attribution_reporting/attribution_resolver_impl.h",
     "attribution_reporting/attribution_storage_sql.cc",
     "attribution_reporting/attribution_storage_sql.h",
     "attribution_reporting/attribution_storage_sql_migrations.cc",
@@ -2058,6 +2060,8 @@
     "service_worker/url_loader_client_checker.h",
     "shared_storage/shared_storage_budget_charger.cc",
     "shared_storage/shared_storage_budget_charger.h",
+    "shared_storage/shared_storage_code_cache_host_proxy.cc",
+    "shared_storage/shared_storage_code_cache_host_proxy.h",
     "shared_storage/shared_storage_document_service_impl.cc",
     "shared_storage/shared_storage_document_service_impl.h",
     "shared_storage/shared_storage_event_params.cc",
@@ -2068,7 +2072,6 @@
     "shared_storage/shared_storage_render_thread_worklet_driver.h",
     "shared_storage/shared_storage_url_loader_factory_proxy.cc",
     "shared_storage/shared_storage_url_loader_factory_proxy.h",
-    "shared_storage/shared_storage_worklet_driver.h",
     "shared_storage/shared_storage_worklet_host.cc",
     "shared_storage/shared_storage_worklet_host.h",
     "shared_storage/shared_storage_worklet_host_manager.cc",
@@ -3278,12 +3281,6 @@
       "service_worker/service_worker_usb_delegate_observer.h",
 
       # Most speech code is non-Android.
-      "speech/endpointer/endpointer.cc",
-      "speech/endpointer/endpointer.h",
-      "speech/endpointer/energy_endpointer.cc",
-      "speech/endpointer/energy_endpointer.h",
-      "speech/endpointer/energy_endpointer_params.cc",
-      "speech/endpointer/energy_endpointer_params.h",
       "speech/network_speech_recognition_engine_impl.cc",
       "speech/network_speech_recognition_engine_impl.h",
       "speech/speech_recognition_engine.cc",
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 49ea5e6..ce900674 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1277,7 +1277,6 @@
     case ax::mojom::Action::kStitchChildTree:
       CHECK_NE(data.target_tree_id, ui::AXTreeIDUnknown());
       CHECK_EQ(data.target_tree_id, manager()->GetTreeID());
-      CHECK_NE(data.target_node_id, ui::kInvalidAXNodeID);
       CHECK_EQ(data.target_node_id, node()->id());
       CHECK_NE(data.child_tree_id, ui::AXTreeIDUnknown());
       CHECK_NE(data.child_tree_id, manager()->GetTreeID())
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 36dce71..09bef9ac 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -61,10 +61,10 @@
 #include "content/browser/attribution_reporting/attribution_report_network_sender.h"
 #include "content/browser/attribution_reporting/attribution_report_sender.h"
 #include "content/browser/attribution_reporting/attribution_reporting.mojom.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h"
-#include "content/browser/attribution_reporting/attribution_storage_sql.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h"
+#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/create_report_result.h"
 #include "content/browser/attribution_reporting/os_registration.h"
@@ -127,21 +127,21 @@
 const base::TimeDelta kPrivacySandboxAttestationsTimeout = base::Minutes(5);
 
 // This class consolidates logic regarding when to schedule the browser to send
-// attribution reports. It talks directly to the `AttributionStorage` to help
+// attribution reports. It talks directly to the `AttributionResolver` to help
 // make these decisions.
 //
 // While the class does not make large changes to the underlying database, it
-// is responsible for notifying the `AttributionStorage` when the browser comes
+// is responsible for notifying the `AttributionResolver` when the browser comes
 // back online, which mutates report times for some scheduled reports.
 class AttributionReportScheduler : public ReportSchedulerTimer::Delegate {
  public:
   AttributionReportScheduler(
       base::RepeatingClosure send_reports,
       base::RepeatingClosure on_reporting_paused_cb,
-      base::SequenceBound<AttributionStorage>& attribution_storage)
+      base::SequenceBound<AttributionResolver>& attribution_resolver)
       : send_reports_(std::move(send_reports)),
         on_reporting_paused_cb_(std::move(on_reporting_paused_cb)),
-        attribution_storage_(attribution_storage) {}
+        attribution_resolver_(attribution_resolver) {}
   ~AttributionReportScheduler() override = default;
 
   AttributionReportScheduler(const AttributionReportScheduler&) = delete;
@@ -155,7 +155,7 @@
   void GetNextReportTime(
       base::OnceCallback<void(std::optional<base::Time>)> callback,
       base::Time now) override {
-    attribution_storage_->AsyncCall(&AttributionStorage::GetNextReportTime)
+    attribution_resolver_->AsyncCall(&AttributionResolver::GetNextReportTime)
         .WithArgs(now)
         .Then(std::move(callback));
   }
@@ -170,8 +170,8 @@
     // offline so they are not temporally joinable. We do this in storage to
     // avoid pulling an unbounded number of reports into memory, only to
     // immediately issue async storage calls to modify their report times.
-    attribution_storage_
-        ->AsyncCall(&AttributionStorage::AdjustOfflineReportTimes)
+    attribution_resolver_
+        ->AsyncCall(&AttributionResolver::AdjustOfflineReportTimes)
         .Then(std::move(maybe_set_timer_cb));
   }
 
@@ -179,7 +179,7 @@
 
   base::RepeatingClosure send_reports_;
   base::RepeatingClosure on_reporting_paused_cb_;
-  const raw_ref<base::SequenceBound<AttributionStorage>> attribution_storage_;
+  const raw_ref<base::SequenceBound<AttributionResolver>> attribution_resolver_;
 };
 
 bool IsStorageKeySessionOnly(
@@ -417,14 +417,14 @@
   }
 }
 
-std::unique_ptr<AttributionStorageDelegate> MakeStorageDelegate(
+std::unique_ptr<AttributionResolverDelegate> MakeResolverDelegate(
     bool debug_mode) {
   if (debug_mode) {
-    return std::make_unique<AttributionStorageDelegateImpl>(
+    return std::make_unique<AttributionResolverDelegateImpl>(
         AttributionNoiseMode::kNone, AttributionDelayMode::kNone);
   }
 
-  return std::make_unique<AttributionStorageDelegateImpl>(
+  return std::make_unique<AttributionResolverDelegateImpl>(
       AttributionNoiseMode::kDefault, AttributionDelayMode::kDefault);
 }
 
@@ -513,17 +513,17 @@
     const base::FilePath& user_data_directory,
     size_t max_pending_events,
     scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-    std::unique_ptr<AttributionStorageDelegate> storage_delegate,
+    std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
     std::unique_ptr<AttributionCookieChecker> cookie_checker,
     std::unique_ptr<AttributionReportSender> report_sender,
     std::unique_ptr<AttributionOsLevelManager> os_level_manager,
     StoragePartitionImpl* storage_partition,
-    scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner) {
+    scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner) {
   return base::WrapUnique(new AttributionManagerImpl(
       storage_partition, user_data_directory, max_pending_events,
-      std::move(special_storage_policy), std::move(storage_delegate),
+      std::move(special_storage_policy), std::move(resolver_delegate),
       std::move(cookie_checker), std::move(report_sender),
-      std::move(os_level_manager), std::move(storage_task_runner)));
+      std::move(os_level_manager), std::move(resolver_task_runner)));
 }
 
 AttributionManagerImpl::AttributionManagerImpl(
@@ -537,8 +537,9 @@
           // os registrations will include multiple items.
           /*max_pending_events=*/1000,
           std::move(special_storage_policy),
-          MakeStorageDelegate(base::CommandLine::ForCurrentProcess()->HasSwitch(
-              switches::kAttributionReportingDebugMode)),
+          MakeResolverDelegate(
+              base::CommandLine::ForCurrentProcess()->HasSwitch(
+                  switches::kAttributionReportingDebugMode)),
           std::make_unique<AttributionCookieCheckerImpl>(storage_partition),
           std::make_unique<AttributionReportNetworkSender>(
               storage_partition->GetURLLoaderFactoryForBrowserProcess()),
@@ -559,19 +560,19 @@
     const base::FilePath& user_data_directory,
     size_t max_pending_events,
     scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-    std::unique_ptr<AttributionStorageDelegate> storage_delegate,
+    std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
     std::unique_ptr<AttributionCookieChecker> cookie_checker,
     std::unique_ptr<AttributionReportSender> report_sender,
     std::unique_ptr<AttributionOsLevelManager> os_level_manager,
-    scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner)
+    scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner)
     : storage_partition_(
           raw_ref<StoragePartitionImpl>::from_ptr(storage_partition)),
       max_pending_events_(max_pending_events),
-      storage_task_runner_(std::move(storage_task_runner)),
-      attribution_storage_(base::SequenceBound<AttributionStorageSql>(
-          storage_task_runner_,
+      resolver_task_runner_(std::move(resolver_task_runner)),
+      attribution_resolver_(base::SequenceBound<AttributionResolverImpl>(
+          resolver_task_runner_,
           g_run_in_memory ? base::FilePath() : user_data_directory,
-          std::move(storage_delegate))),
+          std::move(resolver_delegate))),
       data_host_manager_(
           std::make_unique<AttributionDataHostManagerImpl>(this)),
       special_storage_policy_(std::move(special_storage_policy)),
@@ -579,7 +580,7 @@
       report_sender_(std::move(report_sender)),
       os_level_manager_(std::move(os_level_manager)) {
   DCHECK_GT(max_pending_events_, 0u);
-  DCHECK(storage_task_runner_);
+  DCHECK(resolver_task_runner_);
   DCHECK(cookie_checker_);
   DCHECK(report_sender_);
   DCHECK(os_level_manager_);
@@ -694,7 +695,8 @@
         std::exchange(trigger.registration().debug_key, std::nullopt);
   }
 
-  attribution_storage_.AsyncCall(&AttributionStorage::MaybeCreateAndStoreReport)
+  attribution_resolver_
+      .AsyncCall(&AttributionResolver::MaybeCreateAndStoreReport)
       .WithArgs(std::move(trigger))
       .Then(base::BindOnce(&AttributionManagerImpl::OnReportStored,
                            weak_factory_.GetWeakPtr(), cleared_debug_key,
@@ -842,7 +844,7 @@
         std::exchange(source.registration().debug_key, std::nullopt);
   }
 
-  attribution_storage_.AsyncCall(&AttributionStorage::StoreSource)
+  attribution_resolver_.AsyncCall(&AttributionResolver::StoreSource)
       .WithArgs(std::move(source))
       .Then(base::BindOnce(&AttributionManagerImpl::OnSourceStored,
                            weak_factory_.GetWeakPtr(), cleared_debug_key));
@@ -944,7 +946,7 @@
   OnUserVisibleTaskStarted();
 
   const int kMaxSources = 1000;
-  attribution_storage_.AsyncCall(&AttributionStorage::GetActiveSources)
+  attribution_resolver_.AsyncCall(&AttributionResolver::GetActiveSources)
       .WithArgs(kMaxSources)
       .Then(std::move(callback).Then(
           base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
@@ -956,7 +958,7 @@
     base::OnceCallback<void(std::vector<AttributionReport>)> callback) {
   OnUserVisibleTaskStarted();
 
-  attribution_storage_.AsyncCall(&AttributionStorage::GetAttributionReports)
+  attribution_resolver_.AsyncCall(&AttributionResolver::GetAttributionReports)
       .WithArgs(/*max_report_time=*/base::Time::Max(), limit)
       .Then(std::move(callback).Then(
           base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
@@ -978,7 +980,7 @@
       base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
                      weak_factory_.GetWeakPtr()));
 
-  attribution_storage_.AsyncCall(&AttributionStorage::GetReport)
+  attribution_resolver_.AsyncCall(&AttributionResolver::GetReport)
       .WithArgs(id)
       .Then(base::BindOnce(&AttributionManagerImpl::OnGetReportToSendFromWebUI,
                            weak_factory_.GetWeakPtr(), std::move(done)));
@@ -1017,7 +1019,7 @@
     OnUserVisibleTaskStarted();
   }
 
-  attribution_storage_.AsyncCall(&AttributionStorage::ClearData)
+  attribution_resolver_.AsyncCall(&AttributionResolver::ClearData)
       .WithArgs(delete_begin, delete_end, std::move(filter),
                 delete_rate_limit_data)
       .Then(std::move(done).Then(
@@ -1029,7 +1031,7 @@
 void AttributionManagerImpl::OnUserVisibleTaskStarted() {
   // When a user-visible task is queued or running, we use a higher priority.
   ++num_pending_user_visible_tasks_;
-  storage_task_runner_->UpdatePriority(base::TaskPriority::USER_VISIBLE);
+  resolver_task_runner_->UpdatePriority(base::TaskPriority::USER_VISIBLE);
 }
 
 void AttributionManagerImpl::OnUserVisibleTaskComplete() {
@@ -1038,7 +1040,7 @@
 
   // No more user-visible tasks, so we can reset the priority.
   if (num_pending_user_visible_tasks_ == 0) {
-    storage_task_runner_->UpdatePriority(base::TaskPriority::BEST_EFFORT);
+    resolver_task_runner_->UpdatePriority(base::TaskPriority::BEST_EFFORT);
   }
 }
 
@@ -1053,7 +1055,7 @@
 void AttributionManagerImpl::GetAllDataKeys(
     base::OnceCallback<void(std::set<DataKey>)> callback) {
   OnUserVisibleTaskStarted();
-  attribution_storage_.AsyncCall(&AttributionStorage::GetAllDataKeys)
+  attribution_resolver_.AsyncCall(&AttributionResolver::GetAllDataKeys)
       .Then(std::move(callback).Then(
           base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
                          weak_factory_.GetWeakPtr())));
@@ -1073,7 +1075,7 @@
 
   OnUserVisibleTaskStarted();
 
-  attribution_storage_.AsyncCall(&AttributionStorage::DeleteByDataKey)
+  attribution_resolver_.AsyncCall(&AttributionResolver::DeleteByDataKey)
       .WithArgs(data_key)
       .Then(std::move(callback).Then(base::BindOnce(
           &AttributionManagerImpl::OnClearDataComplete,
@@ -1089,7 +1091,7 @@
   //
   // TODO(apaseltiner): Consider limiting the number of reports being sent at
   // once, to avoid pulling an arbitrary number of reports into memory.
-  attribution_storage_.AsyncCall(&AttributionStorage::GetAttributionReports)
+  attribution_resolver_.AsyncCall(&AttributionResolver::GetAttributionReports)
       .WithArgs(/*max_report_time=*/base::Time::Now(), /*limit=*/-1)
       .Then(base::BindOnce(&AttributionManagerImpl::SendReports,
                            weak_factory_.GetWeakPtr()));
@@ -1236,8 +1238,8 @@
       new_report_time);
 
   if (new_report_time) {
-    attribution_storage_
-        .AsyncCall(&AttributionStorage::UpdateReportForSendFailure)
+    attribution_resolver_
+        .AsyncCall(&AttributionResolver::UpdateReportForSendFailure)
         .WithArgs(report.id(), *new_report_time)
         .Then(std::move(then));
 
@@ -1248,7 +1250,7 @@
 
   NotifyReportSent(/*is_debug_report=*/false, report, info);
 
-  attribution_storage_.AsyncCall(&AttributionStorage::DeleteReport)
+  attribution_resolver_.AsyncCall(&AttributionResolver::DeleteReport)
       .WithArgs(report.id())
       .Then(std::move(then));
 
@@ -1632,8 +1634,8 @@
 
   // TODO(apaseltiner): Observers should be notified when the debug mode changes
   // so they can re-query its value.
-  attribution_storage_.AsyncCall(&AttributionStorage::SetDelegate)
-      .WithArgs(MakeStorageDelegate(debug_mode))
+  attribution_resolver_.AsyncCall(&AttributionResolver::SetDelegate)
+      .WithArgs(MakeResolverDelegate(debug_mode))
       .Then(std::move(done));
 }
 
@@ -1698,7 +1700,7 @@
           base::BindRepeating(
               &AttributionManagerImpl::RecordPendingAggregatableReportsTimings,
               base::Unretained(this)),
-          attribution_storage_));
+          attribution_resolver_));
 
   PrepareNextEvent();
   PrepareNextOsEvent();
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 9320cf4e..9f41f236 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -61,8 +61,8 @@
 class AttributionDataHostManager;
 class AttributionDebugReport;
 class AttributionOsLevelManager;
-class AttributionStorage;
-class AttributionStorageDelegate;
+class AttributionResolver;
+class AttributionResolverDelegate;
 class CreateReportResult;
 class StoragePartitionImpl;
 class StoreSourceResult;
@@ -104,12 +104,12 @@
       const base::FilePath& user_data_directory,
       size_t max_pending_events,
       scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-      std::unique_ptr<AttributionStorageDelegate> storage_delegate,
+      std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
       std::unique_ptr<AttributionCookieChecker> cookie_checker,
       std::unique_ptr<AttributionReportSender> report_sender,
       std::unique_ptr<AttributionOsLevelManager> os_level_manager,
       StoragePartitionImpl* storage_partition,
-      scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner);
+      scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner);
 
   AttributionManagerImpl(
       StoragePartitionImpl* storage_partition,
@@ -175,11 +175,11 @@
       const base::FilePath& user_data_directory,
       size_t max_pending_events,
       scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
-      std::unique_ptr<AttributionStorageDelegate> storage_delegate,
+      std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
       std::unique_ptr<AttributionCookieChecker> cookie_checker,
       std::unique_ptr<AttributionReportSender> report_sender,
       std::unique_ptr<AttributionOsLevelManager> os_level_manager,
-      scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner);
+      scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner);
 
   void MaybeEnqueueEvent(SourceOrTriggerRFH);
   void PrepareNextEvent();
@@ -282,17 +282,17 @@
   // growth with adversarial input.
   size_t max_pending_events_;
 
-  // The task runner for all attribution reporting storage operations.
+  // The task runner for all operations on the resolver.
   // Updateable to allow for priority to be temporarily increased to
   // `USER_VISIBLE` when a user-visible storage task is queued or running.
   // Otherwise `BEST_EFFORT` is used.
-  scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner_;
+  scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner_;
 
   // How many user-visible storage tasks are queued or running currently,
   // i.e. have been posted but the reply has not been run.
   int num_pending_user_visible_tasks_ = 0;
 
-  base::SequenceBound<AttributionStorage> attribution_storage_;
+  base::SequenceBound<AttributionResolver> attribution_resolver_;
 
   std::unique_ptr<ReportSchedulerTimer> scheduler_timer_;
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
index 33e5bc5..551327d 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -57,8 +57,8 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_sender.h"
 #include "content/browser/attribution_reporting/attribution_reporting.mojom.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
@@ -135,7 +135,7 @@
 constexpr attribution_reporting::Registrar kRegistrar =
     attribution_reporting::Registrar::kWeb;
 
-constexpr AttributionStorageDelegate::OfflineReportDelayConfig
+constexpr AttributionResolverDelegate::OfflineReportDelayConfig
     kDefaultOfflineReportDelay{
         .min = base::Minutes(0),
         .max = base::Minutes(1),
@@ -191,7 +191,7 @@
 }
 
 // Time after impression that a conversion can first be sent. See
-// AttributionStorageDelegateImpl::GetReportTimeForConversion().
+// AttributionResolverDelegateImpl::GetReportTimeForConversion().
 constexpr base::TimeDelta kFirstReportingWindow = base::Days(2);
 
 // Give impressions a sufficiently long expiry.
@@ -817,7 +817,7 @@
 
   // Simulate startup and ensure the report is sent before being expired.
   // Advance by the max offline report delay, per
-  // `AttributionStorageDelegate::GetOfflineReportDelayConfig()`.
+  // `AttributionResolverDelegate::GetOfflineReportDelayConfig()`.
   CreateManager();
 
   EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _));
@@ -996,7 +996,7 @@
   }
 }
 
-// This functionality is tested more thoroughly in the AttributionStorageSql
+// This functionality is tested more thoroughly in the AttributionResolverImpl
 // unit tests. Here, just test to make sure the basic control flow is working.
 TEST_F(AttributionManagerImplTest, ClearDataFromBrowserOnly) {
   for (bool match_url : {true, false}) {
@@ -1378,7 +1378,7 @@
 
   // Ensure that the expired report is delayed based on the time the browser
   // started and the min and max offline report delays, per
-  // `AttributionStorageDelegate::GetOfflineReportDelayConfig()`.
+  // `AttributionResolverDelegate::GetOfflineReportDelayConfig()`.
   base::Time min_new_time = base::Time::Now();
   EXPECT_THAT(StoredReports(),
               ElementsAre(ReportTimeIs(
@@ -1834,7 +1834,7 @@
   void ConfigureStorageDelegate(
       ConfigurableStorageDelegate& delegate) const override {
     delegate.set_offline_report_delay_config(
-        AttributionStorageDelegate::OfflineReportDelayConfig{
+        AttributionResolverDelegate::OfflineReportDelayConfig{
             .min = base::Minutes(1),
             .max = base::Minutes(1),
         });
@@ -1850,7 +1850,7 @@
 
   // Deliberately avoid running tasks so that the connection change and time
   // advance can be "atomic", which is necessary because
-  // `AttributionStorage::AdjustOfflineReportTimes()` only adjusts times for
+  // `AttributionResolver::AdjustOfflineReportTimes()` only adjusts times for
   // reports that should have been sent before now. In other words, the call to
   // `AdjustOfflineReportTimes()` would have no effect if we used
   // `FastForwardBy()` here, and we wouldn't be able to detect it below.
diff --git a/content/browser/attribution_reporting/attribution_storage.h b/content/browser/attribution_reporting/attribution_resolver.h
similarity index 80%
rename from content/browser/attribution_reporting/attribution_storage.h
rename to content/browser/attribution_reporting/attribution_resolver.h
index 0429848..8266420c 100644
--- a/content/browser/attribution_reporting/attribution_storage.h
+++ b/content/browser/attribution_reporting/attribution_resolver.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_H_
+#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_H_
 
 #include <memory>
 #include <optional>
@@ -21,7 +21,7 @@
 
 namespace content {
 
-class AttributionStorageDelegate;
+class AttributionResolverDelegate;
 class AttributionTrigger;
 class CreateReportResult;
 class StorableSource;
@@ -29,15 +29,15 @@
 class StoredSource;
 
 // This class provides an interface for persisting attribution data to
-// disk, and performing queries on it. AttributionStorage should initialize
-// itself. Calls to a AttributionStorage instance that failed to initialize
+// disk, and performing queries on it. AttributionResolver should initialize
+// itself. Calls to a AttributionResolver instance that failed to initialize
 // properly should result in no-ops.
-class AttributionStorage {
+class AttributionResolver {
  public:
-  virtual ~AttributionStorage() = default;
+  virtual ~AttributionResolver() = default;
 
   // When adding a new method, also add it to
-  // AttributionStorageTest.StorageUsedAfterFailedInitilization_FailsSilently.
+  // AttributionResolverTest.StorageUsedAfterFailedInitilization_FailsSilently.
 
   // Add |source| to storage. Two sources are considered
   // matching when they share a <reporting origin, attribution destination>
@@ -46,15 +46,14 @@
   // Unconverted matching sources are not modified.
   virtual StoreSourceResult StoreSource(StorableSource source) = 0;
 
-  // Finds all stored sources matching a given `trigger`, and stores the
+  // Finds all stored sources matching a given `trigger`, and creates a
   // new associated report. Only active sources will receive new attributions.
   // Returns whether a new report has been scheduled/added to storage.
   virtual CreateReportResult MaybeCreateAndStoreReport(AttributionTrigger) = 0;
 
   // Returns all of the reports that should be sent before
-  // |max_report_time|. This call is logically const, and does not modify the
-  // underlying storage. |limit| limits the number of reports to return; use
-  // a negative number for no limit. Reports are shuffled before being returned.
+  // |max_report_time|. |limit| limits the number of reports to return; use
+  // a negative number for no limit. Reports are shuffled before being returned. This call is logically const and does not mutate reports.
   virtual std::vector<AttributionReport> GetAttributionReports(
       base::Time max_report_time,
       int limit = -1) = 0;
@@ -63,7 +62,7 @@
   virtual std::optional<base::Time> GetNextReportTime(base::Time time) = 0;
 
   // Returns the report with the given ID. This call is logically const, and
-  // does not modify the underlying storage.
+  // does not mutate reports.
   virtual std::optional<AttributionReport> GetReport(AttributionReport::Id) = 0;
 
   // Returns all active sources in storage. Active sources are all
@@ -79,7 +78,7 @@
   // in the event of an error.
   virtual std::set<AttributionDataModel::DataKey> GetAllDataKeys() = 0;
 
-  // Deletes all data in storage for storage keys matching the provided
+  // Deletes all data for storage keys matching the provided
   // reporting origin in the data key.
   virtual void DeleteByDataKey(const AttributionDataModel::DataKey&) = 0;
 
@@ -96,7 +95,7 @@
 
   // Adjusts the report time of all reports that should have been sent while the
   // browser was offline, according to
-  // `AttributionStorageDelegate::GetOfflineReportDelayConfig()`. If that
+  // `AttributionResolverDelegate::GetOfflineReportDelayConfig()`. If that
   // method returns null, no delay is applied. Otherwise, applies a random value
   // between `min_delay` and `max_delay`, both inclusive. Returns the new first
   // report time in storage, if any.
@@ -118,9 +117,9 @@
                          StoragePartition::StorageKeyMatcherFunction filter,
                          bool delete_rate_limit_data = true) = 0;
 
-  virtual void SetDelegate(std::unique_ptr<AttributionStorageDelegate>) = 0;
+  virtual void SetDelegate(std::unique_ptr<AttributionResolverDelegate>) = 0;
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_H_
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_H_
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate.cc b/content/browser/attribution_reporting/attribution_resolver_delegate.cc
similarity index 74%
rename from content/browser/attribution_reporting/attribution_storage_delegate.cc
rename to content/browser/attribution_reporting/attribution_resolver_delegate.cc
index 804397b..b657ab3 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate.cc
+++ b/content/browser/attribution_reporting/attribution_resolver_delegate.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 
 #include "base/check.h"
 #include "base/notreached.h"
@@ -17,20 +17,20 @@
 
 }  // namespace
 
-AttributionStorageDelegate::AttributionStorageDelegate(
+AttributionResolverDelegate::AttributionResolverDelegate(
     const AttributionConfig& config)
     : config_(config) {
   DCHECK(config_.Validate());
 }
 
-AttributionStorageDelegate::~AttributionStorageDelegate() = default;
+AttributionResolverDelegate::~AttributionResolverDelegate() = default;
 
-int AttributionStorageDelegate::GetMaxSourcesPerOrigin() const {
+int AttributionResolverDelegate::GetMaxSourcesPerOrigin() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.max_sources_per_origin;
 }
 
-int AttributionStorageDelegate::GetMaxReportsPerDestination(
+int AttributionResolverDelegate::GetMaxReportsPerDestination(
     attribution_reporting::mojom::ReportType report_type) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   switch (report_type) {
@@ -43,19 +43,19 @@
   }
 }
 
-int AttributionStorageDelegate::GetMaxDestinationsPerSourceSiteReportingSite()
+int AttributionResolverDelegate::GetMaxDestinationsPerSourceSiteReportingSite()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.max_destinations_per_source_site_reporting_site;
 }
 
 const AttributionConfig::RateLimitConfig&
-AttributionStorageDelegate::GetRateLimits() const {
+AttributionResolverDelegate::GetRateLimits() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.rate_limit;
 }
 
-double AttributionStorageDelegate::GetMaxChannelCapacity(
+double AttributionResolverDelegate::GetMaxChannelCapacity(
     SourceType source_type) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   switch (source_type) {
@@ -66,19 +66,19 @@
   }
 }
 
-absl::uint128 AttributionStorageDelegate::GetMaxTriggerStateCardinality()
+absl::uint128 AttributionResolverDelegate::GetMaxTriggerStateCardinality()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.event_level_limit.max_trigger_state_cardinality;
 }
 
-int AttributionStorageDelegate::GetMaxAggregatableReportsPerSource() const {
+int AttributionResolverDelegate::GetMaxAggregatableReportsPerSource() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.aggregate_limit.max_aggregatable_reports_per_source;
 }
 
 AttributionConfig::DestinationRateLimit
-AttributionStorageDelegate::GetDestinationRateLimit() const {
+AttributionResolverDelegate::GetDestinationRateLimit() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return config_.destination_rate_limit;
 }
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate.h b/content/browser/attribution_reporting/attribution_resolver_delegate.h
similarity index 88%
rename from content/browser/attribution_reporting/attribution_storage_delegate.h
rename to content/browser/attribution_reporting/attribution_resolver_delegate.h
index f2df3ea..450435ff 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate.h
+++ b/content/browser/attribution_reporting/attribution_resolver_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_H_
+#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_H_
 
 #include <stdint.h>
 
@@ -40,11 +40,11 @@
 
 class AttributionReport;
 
-// Storage delegate that can supplied to extend basic attribution storage
+// Resolver delegate that can supplied to extend basic attribution storage
 // functionality like annotating reports. Users and subclasses must NOT assume
 // that the delegate has the same lifetime as the `AttributionManager` or
-// `AttributionStorage` classes.
-class CONTENT_EXPORT AttributionStorageDelegate {
+// `AttributionResolver` classes.
+class CONTENT_EXPORT AttributionResolverDelegate {
  public:
   // Both bounds are inclusive.
   struct OfflineReportDelayConfig {
@@ -52,16 +52,16 @@
     base::TimeDelta max;
   };
 
-  explicit AttributionStorageDelegate(const AttributionConfig& config);
+  explicit AttributionResolverDelegate(const AttributionConfig& config);
 
-  virtual ~AttributionStorageDelegate();
+  virtual ~AttributionResolverDelegate();
 
-  AttributionStorageDelegate(const AttributionStorageDelegate&) = delete;
-  AttributionStorageDelegate& operator=(const AttributionStorageDelegate&) =
+  AttributionResolverDelegate(const AttributionResolverDelegate&) = delete;
+  AttributionResolverDelegate& operator=(const AttributionResolverDelegate&) =
       delete;
 
-  AttributionStorageDelegate(AttributionStorageDelegate&&) = delete;
-  AttributionStorageDelegate& operator=(AttributionStorageDelegate&&) = delete;
+  AttributionResolverDelegate(AttributionResolverDelegate&&) = delete;
+  AttributionResolverDelegate& operator=(AttributionResolverDelegate&&) = delete;
 
   // Returns the time an event-level report should be sent for a given trigger
   // time and its corresponding source.
@@ -174,4 +174,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_H_
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_H_
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc b/content/browser/attribution_reporting/attribution_resolver_delegate_impl.cc
similarity index 82%
rename from content/browser/attribution_reporting/attribution_storage_delegate_impl.cc
rename to content/browser/attribution_reporting/attribution_resolver_delegate_impl.cc
index 53924d5..706e73d 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc
+++ b/content/browser/attribution_reporting/attribution_resolver_delegate_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -42,47 +42,47 @@
 }  // namespace
 
 // static
-std::unique_ptr<AttributionStorageDelegate>
-AttributionStorageDelegateImpl::CreateForTesting(
+std::unique_ptr<AttributionResolverDelegate>
+AttributionResolverDelegateImpl::CreateForTesting(
     AttributionNoiseMode noise_mode,
     AttributionDelayMode delay_mode,
     const AttributionConfig& config) {
   return base::WrapUnique(
-      new AttributionStorageDelegateImpl(noise_mode, delay_mode, config));
+      new AttributionResolverDelegateImpl(noise_mode, delay_mode, config));
 }
 
-AttributionStorageDelegateImpl::AttributionStorageDelegateImpl(
+AttributionResolverDelegateImpl::AttributionResolverDelegateImpl(
     AttributionNoiseMode noise_mode,
     AttributionDelayMode delay_mode)
-    : AttributionStorageDelegateImpl(noise_mode,
+    : AttributionResolverDelegateImpl(noise_mode,
                                      delay_mode,
                                      AttributionConfig()) {}
 
-AttributionStorageDelegateImpl::AttributionStorageDelegateImpl(
+AttributionResolverDelegateImpl::AttributionResolverDelegateImpl(
     AttributionNoiseMode noise_mode,
     AttributionDelayMode delay_mode,
     const AttributionConfig& config)
-    : AttributionStorageDelegate(config),
+    : AttributionResolverDelegate(config),
       noise_mode_(noise_mode),
       delay_mode_(delay_mode) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-AttributionStorageDelegateImpl::~AttributionStorageDelegateImpl() = default;
+AttributionResolverDelegateImpl::~AttributionResolverDelegateImpl() = default;
 
 base::TimeDelta
-AttributionStorageDelegateImpl::GetDeleteExpiredSourcesFrequency() const {
+AttributionResolverDelegateImpl::GetDeleteExpiredSourcesFrequency() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return base::Minutes(5);
 }
 
 base::TimeDelta
-AttributionStorageDelegateImpl::GetDeleteExpiredRateLimitsFrequency() const {
+AttributionResolverDelegateImpl::GetDeleteExpiredRateLimitsFrequency() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return base::Minutes(5);
 }
 
-base::Time AttributionStorageDelegateImpl::GetEventLevelReportTime(
+base::Time AttributionResolverDelegateImpl::GetEventLevelReportTime(
     const attribution_reporting::EventReportWindows& event_report_windows,
     base::Time source_time,
     base::Time trigger_time) const {
@@ -96,7 +96,7 @@
   }
 }
 
-base::Time AttributionStorageDelegateImpl::GetAggregatableReportTime(
+base::Time AttributionResolverDelegateImpl::GetAggregatableReportTime(
     base::Time trigger_time) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -116,13 +116,13 @@
   }
 }
 
-base::Uuid AttributionStorageDelegateImpl::NewReportID() const {
+base::Uuid AttributionResolverDelegateImpl::NewReportID() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return base::Uuid::GenerateRandomV4();
 }
 
-std::optional<AttributionStorageDelegate::OfflineReportDelayConfig>
-AttributionStorageDelegateImpl::GetOfflineReportDelayConfig() const {
+std::optional<AttributionResolverDelegate::OfflineReportDelayConfig>
+AttributionResolverDelegateImpl::GetOfflineReportDelayConfig() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (noise_mode_ == AttributionNoiseMode::kDefault &&
@@ -142,7 +142,7 @@
   return std::nullopt;
 }
 
-void AttributionStorageDelegateImpl::ShuffleReports(
+void AttributionResolverDelegateImpl::ShuffleReports(
     std::vector<AttributionReport>& reports) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -155,7 +155,7 @@
   }
 }
 
-void AttributionStorageDelegateImpl::ShuffleTriggerVerifications(
+void AttributionResolverDelegateImpl::ShuffleTriggerVerifications(
     std::vector<network::TriggerVerification>& verifications) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -168,7 +168,7 @@
   }
 }
 
-double AttributionStorageDelegateImpl::GetRandomizedResponseRate(
+double AttributionResolverDelegateImpl::GetRandomizedResponseRate(
     const attribution_reporting::TriggerSpecs& trigger_specs,
     attribution_reporting::MaxEventLevelReports max_event_level_reports,
     attribution_reporting::EventLevelEpsilon epsilon) const {
@@ -177,8 +177,8 @@
       GetNumStates(trigger_specs, max_event_level_reports), epsilon);
 }
 
-AttributionStorageDelegate::GetRandomizedResponseResult
-AttributionStorageDelegateImpl::GetRandomizedResponse(
+AttributionResolverDelegate::GetRandomizedResponseResult
+AttributionResolverDelegateImpl::GetRandomizedResponse(
     SourceType source_type,
     const attribution_reporting::TriggerSpecs& trigger_specs,
     attribution_reporting::MaxEventLevelReports max_event_level_reports,
@@ -204,7 +204,7 @@
   return response;
 }
 
-bool AttributionStorageDelegateImpl::
+bool AttributionResolverDelegateImpl::
     GenerateNullAggregatableReportForLookbackDay(
         int lookback_day,
         attribution_reporting::mojom::SourceRegistrationTimeConfig
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate_impl.h b/content/browser/attribution_reporting/attribution_resolver_delegate_impl.h
similarity index 74%
rename from content/browser/attribution_reporting/attribution_storage_delegate_impl.h
rename to content/browser/attribution_reporting/attribution_resolver_delegate_impl.h
index 582e8fa..3f3ba33 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate_impl.h
+++ b/content/browser/attribution_reporting/attribution_resolver_delegate_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_IMPL_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_IMPL_H_
+#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_IMPL_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_IMPL_H_
 
 #include <stdint.h>
 
@@ -12,7 +12,7 @@
 
 #include "base/thread_annotations.h"
 #include "components/attribution_reporting/source_type.mojom-forward.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 #include "content/common/content_export.h"
 
 namespace base {
@@ -48,30 +48,30 @@
 
 // Implementation of the storage delegate. This class handles assigning
 // report times to newly created reports. It
-// also controls constants for AttributionStorage. This is owned by
+// also controls constants for AttributionResolver. This is owned by
 // AttributionStorageSql, and should only be accessed on the attribution storage
 // task runner.
-class CONTENT_EXPORT AttributionStorageDelegateImpl
-    : public AttributionStorageDelegate {
+class CONTENT_EXPORT AttributionResolverDelegateImpl
+    : public AttributionResolverDelegate {
  public:
-  static std::unique_ptr<AttributionStorageDelegate> CreateForTesting(
+  static std::unique_ptr<AttributionResolverDelegate> CreateForTesting(
       AttributionNoiseMode noise_mode,
       AttributionDelayMode delay_mode,
       const AttributionConfig& config);
 
-  explicit AttributionStorageDelegateImpl(
+  explicit AttributionResolverDelegateImpl(
       AttributionNoiseMode noise_mode = AttributionNoiseMode::kDefault,
       AttributionDelayMode delay_mode = AttributionDelayMode::kDefault);
-  AttributionStorageDelegateImpl(const AttributionStorageDelegateImpl&) =
+  AttributionResolverDelegateImpl(const AttributionResolverDelegateImpl&) =
       delete;
-  AttributionStorageDelegateImpl& operator=(
-      const AttributionStorageDelegateImpl&) = delete;
-  AttributionStorageDelegateImpl(AttributionStorageDelegateImpl&&) = delete;
-  AttributionStorageDelegateImpl& operator=(AttributionStorageDelegateImpl&&) =
+  AttributionResolverDelegateImpl& operator=(
+      const AttributionResolverDelegateImpl&) = delete;
+  AttributionResolverDelegateImpl(AttributionResolverDelegateImpl&&) = delete;
+  AttributionResolverDelegateImpl& operator=(AttributionResolverDelegateImpl&&) =
       delete;
-  ~AttributionStorageDelegateImpl() override;
+  ~AttributionResolverDelegateImpl() override;
 
-  // AttributionStorageDelegate:
+  // AttributionResolverDelegate:
   base::Time GetEventLevelReportTime(
       const attribution_reporting::EventReportWindows& event_report_windows,
       base::Time source_time,
@@ -100,7 +100,7 @@
       const override;
 
  protected:
-  AttributionStorageDelegateImpl(AttributionNoiseMode noise_mode,
+  AttributionResolverDelegateImpl(AttributionNoiseMode noise_mode,
                                  AttributionDelayMode delay_mode,
                                  const AttributionConfig& config);
 
@@ -111,4 +111,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_STORAGE_DELEGATE_IMPL_H_
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_DELEGATE_IMPL_H_
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc b/content/browser/attribution_reporting/attribution_resolver_delegate_impl_unittest.cc
similarity index 84%
rename from content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc
rename to content/browser/attribution_reporting/attribution_resolver_delegate_impl_unittest.cc
index f030460..9bed2c6 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_resolver_delegate_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h"
 
 #include <stdint.h>
 
@@ -36,30 +36,30 @@
 
 // This is more comprehensively tested in
 // //components/attribution_reporting/event_report_windows_unittest.cc.
-TEST(AttributionStorageDelegateImplTest, GetEventLevelReportTime) {
+TEST(AttributionResolverDelegateImplTest, GetEventLevelReportTime) {
   constexpr base::Time kSourceTime;
   constexpr base::Time kTriggerTime = kSourceTime + base::Seconds(1);
   constexpr base::TimeDelta kEnd = base::Days(3);
 
   EXPECT_EQ(kSourceTime + kEnd,
-            AttributionStorageDelegateImpl().GetEventLevelReportTime(
+            AttributionResolverDelegateImpl().GetEventLevelReportTime(
                 *EventReportWindows::Create(/*start_time=*/base::Seconds(0),
                                             /*end_times=*/{kEnd}),
                 kSourceTime, kTriggerTime));
 }
 
-TEST(AttributionStorageDelegateImplTest, GetAggregatableReportTime) {
+TEST(AttributionResolverDelegateImplTest, GetAggregatableReportTime) {
   base::Time trigger_time = base::Time::Now();
   EXPECT_THAT(
-      AttributionStorageDelegateImpl().GetAggregatableReportTime(trigger_time),
+      AttributionResolverDelegateImpl().GetAggregatableReportTime(trigger_time),
       AllOf(Ge(trigger_time), Lt(trigger_time + base::Minutes(10))));
 }
 
-TEST(AttributionStorageDelegateImplTest, NewReportID_IsValidGUID) {
-  EXPECT_TRUE(AttributionStorageDelegateImpl().NewReportID().is_valid());
+TEST(AttributionResolverDelegateImplTest, NewReportID_IsValidGUID) {
+  EXPECT_TRUE(AttributionResolverDelegateImpl().NewReportID().is_valid());
 }
 
-TEST(AttributionStorageDelegateImplTest,
+TEST(AttributionResolverDelegateImplTest,
      RandomizedResponse_NoNoiseModeReturnsRealRateAndNullResponse) {
   for (auto source_type : {SourceType::kNavigation, SourceType::kEvent}) {
     const auto source =
@@ -72,7 +72,7 @@
                     : 1)
             .BuildStored();
 
-    auto result = AttributionStorageDelegateImpl(AttributionNoiseMode::kNone)
+    auto result = AttributionResolverDelegateImpl(AttributionNoiseMode::kNone)
                       .GetRandomizedResponse(source.common_info().source_type(),
                                              source.trigger_specs(),
                                              source.max_event_level_reports(),
@@ -83,7 +83,7 @@
   }
 }
 
-TEST(AttributionStorageDelegateImplTest,
+TEST(AttributionResolverDelegateImplTest,
      RandomizedResponse_ExceedsLimit_ReturnsError) {
   const struct {
     SourceType source_type;
@@ -120,7 +120,7 @@
     config.event_level_limit.max_event_info_gain =
         test_case.max_event_info_gain;
 
-    auto delegate = AttributionStorageDelegateImpl::CreateForTesting(
+    auto delegate = AttributionResolverDelegateImpl::CreateForTesting(
         AttributionNoiseMode::kDefault, AttributionDelayMode::kDefault, config);
 
     const auto source =
diff --git a/content/browser/attribution_reporting/attribution_resolver_impl.cc b/content/browser/attribution_reporting/attribution_resolver_impl.cc
new file mode 100644
index 0000000..83f29c0e
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_resolver_impl.cc
@@ -0,0 +1,112 @@
+// 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 "content/browser/attribution_reporting/attribution_resolver_impl.h"
+
+#include <memory>
+
+#include "base/check.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
+#include "content/browser/attribution_reporting/attribution_storage_sql.h"
+#include "content/browser/attribution_reporting/create_report_result.h"
+#include "content/browser/attribution_reporting/storable_source.h"
+#include "content/browser/attribution_reporting/store_source_result.h"
+
+namespace content {
+
+AttributionResolverImpl::AttributionResolverImpl(
+    const base::FilePath& user_data_directory,
+    std::unique_ptr<AttributionResolverDelegate> delegate)
+    : delegate_(std::move(delegate)),
+      storage_(user_data_directory, delegate_.get()) {
+  DCHECK(delegate_);
+}
+
+AttributionResolverImpl::~AttributionResolverImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+StoreSourceResult AttributionResolverImpl::StoreSource(StorableSource source) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.StoreSource(std::move(source));
+}
+
+CreateReportResult AttributionResolverImpl::MaybeCreateAndStoreReport(
+    AttributionTrigger trigger) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.MaybeCreateAndStoreReport(std::move(trigger));
+}
+
+std::vector<AttributionReport> AttributionResolverImpl::GetAttributionReports(
+    base::Time max_report_time,
+    int limit) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.GetAttributionReports(max_report_time, limit);
+}
+
+std::optional<base::Time> AttributionResolverImpl::GetNextReportTime(
+    base::Time time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.GetNextReportTime(time);
+}
+
+std::optional<AttributionReport> AttributionResolverImpl::GetReport(
+    AttributionReport::Id id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.GetReport(id);
+}
+
+std::vector<StoredSource> AttributionResolverImpl::GetActiveSources(int limit) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.GetActiveSources(limit);
+}
+
+std::set<AttributionDataModel::DataKey>
+AttributionResolverImpl::GetAllDataKeys() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.GetAllDataKeys();
+}
+
+void AttributionResolverImpl::DeleteByDataKey(
+    const AttributionDataModel::DataKey& datakey) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  storage_.DeleteByDataKey(datakey);
+}
+
+bool AttributionResolverImpl::DeleteReport(AttributionReport::Id report_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.DeleteReport(report_id);
+}
+
+bool AttributionResolverImpl::UpdateReportForSendFailure(
+    AttributionReport::Id report_id,
+    base::Time new_report_time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.UpdateReportForSendFailure(report_id, new_report_time);
+}
+
+std::optional<base::Time> AttributionResolverImpl::AdjustOfflineReportTimes() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return storage_.AdjustOfflineReportTimes();
+}
+
+void AttributionResolverImpl::ClearData(
+    base::Time delete_begin,
+    base::Time delete_end,
+    StoragePartition::StorageKeyMatcherFunction filter,
+    bool delete_rate_limit_data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  storage_.ClearData(delete_begin, delete_end, std::move(filter),
+                     delete_rate_limit_data);
+}
+
+void AttributionResolverImpl::SetDelegate(
+    std::unique_ptr<AttributionResolverDelegate> delegate) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(delegate);
+  storage_.SetDelegate(delegate.get());
+  delegate_ = std::move(delegate);
+}
+
+}  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_resolver_impl.h b/content/browser/attribution_reporting/attribution_resolver_impl.h
new file mode 100644
index 0000000..955af17
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_resolver_impl.h
@@ -0,0 +1,88 @@
+// 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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_IMPL_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_IMPL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/containers/enum_set.h"
+#include "base/files/file_path.h"
+#include "base/sequence_checker.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+#include "base/types/expected.h"
+#include "content/browser/attribution_reporting/attribution_report.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
+#include "content/browser/attribution_reporting/attribution_storage_sql.h"
+#include "content/browser/attribution_reporting/attribution_trigger.h"
+#include "content/browser/attribution_reporting/rate_limit_result.h"
+#include "content/browser/attribution_reporting/stored_source.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/attribution_data_model.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace content {
+
+class AttributionResolverDelegate;
+class StorableSource;
+struct AttributionInfo;
+
+// This class may be constructed on any sequence but must be accessed and
+// destroyed on the same sequence. The sequence must outlive |this|.
+class CONTENT_EXPORT AttributionResolverImpl : public AttributionResolver {
+ public:
+  AttributionResolverImpl(
+      const base::FilePath& user_data_directory,
+      std::unique_ptr<AttributionResolverDelegate> delegate);
+  AttributionResolverImpl(const AttributionResolverImpl&) = delete;
+  AttributionResolverImpl& operator=(const AttributionResolverImpl&) = delete;
+  AttributionResolverImpl(AttributionResolverImpl&&) = delete;
+  AttributionResolverImpl& operator=(AttributionResolverImpl&&) = delete;
+  ~AttributionResolverImpl() override;
+
+  void set_ignore_errors_for_testing(bool ignore_for_testing) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    storage_.set_ignore_errors_for_testing(ignore_for_testing);
+  }
+
+ private:
+  // AttributionResolver:
+  StoreSourceResult StoreSource(StorableSource source) override;
+  CreateReportResult MaybeCreateAndStoreReport(
+      AttributionTrigger trigger) override;
+  std::vector<AttributionReport> GetAttributionReports(
+      base::Time max_report_time,
+      int limit = -1) override;
+  std::optional<base::Time> GetNextReportTime(base::Time time) override;
+  std::optional<AttributionReport> GetReport(AttributionReport::Id) override;
+  std::vector<StoredSource> GetActiveSources(int limit = -1) override;
+  std::set<AttributionDataModel::DataKey> GetAllDataKeys() override;
+  void DeleteByDataKey(const AttributionDataModel::DataKey& datakey) override;
+  bool DeleteReport(AttributionReport::Id report_id) override;
+  bool UpdateReportForSendFailure(AttributionReport::Id report_id,
+                                  base::Time new_report_time) override;
+  std::optional<base::Time> AdjustOfflineReportTimes() override;
+  void ClearData(base::Time delete_begin,
+                 base::Time delete_end,
+                 StoragePartition::StorageKeyMatcherFunction filter,
+                 bool delete_rate_limit_data) override;
+  void SetDelegate(std::unique_ptr<AttributionResolverDelegate>) override;
+
+  std::unique_ptr<AttributionResolverDelegate> delegate_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  AttributionStorageSql storage_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_RESOLVER_IMPL_H_
diff --git a/content/browser/attribution_reporting/attribution_storage_unittest.cc b/content/browser/attribution_reporting/attribution_resolver_unittest.cc
similarity index 95%
rename from content/browser/attribution_reporting/attribution_storage_unittest.cc
rename to content/browser/attribution_reporting/attribution_resolver_unittest.cc
index b3a645b8..f1648ac0 100644
--- a/content/browser/attribution_reporting/attribution_storage_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_resolver_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/attribution_reporting/attribution_storage.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
 
 #include <stdint.h>
 
@@ -45,6 +45,7 @@
 #include "components/attribution_reporting/trigger_registration.h"
 #include "content/browser/attribution_reporting/attribution_features.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
+#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
 #include "content/browser/attribution_reporting/attribution_storage_sql.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
@@ -135,16 +136,16 @@
 
 }  // namespace
 
-// Unit test suite for the AttributionStorage interface. All AttributionStorage
+// Unit test suite for the AttributionResolver interface. All AttributionResolver
 // implementations (including fakes) should be able to re-use this test suite.
-class AttributionStorageTest : public testing::Test {
+class AttributionResolverTest : public testing::Test {
  public:
-  AttributionStorageTest() {
+  AttributionResolverTest() {
     EXPECT_TRUE(dir_.CreateUniqueTempDir());
     auto delegate = std::make_unique<ConfigurableStorageDelegate>();
     delegate->set_report_delay(kReportDelay);
     delegate_ = delegate.get();
-    storage_ = std::make_unique<AttributionStorageSql>(dir_.GetPath(),
+    storage_ = std::make_unique<AttributionResolverImpl>(dir_.GetPath(),
                                                        std::move(delegate));
   }
 
@@ -179,7 +180,7 @@
     }
   }
 
-  AttributionStorage* storage() { return storage_.get(); }
+  AttributionResolver* storage() { return storage_.get(); }
 
   ConfigurableStorageDelegate* delegate() { return delegate_; }
 
@@ -189,23 +190,23 @@
   base::ScopedTempDir dir_;
 
  private:
-  std::unique_ptr<AttributionStorage> storage_;
+  std::unique_ptr<AttributionResolver> storage_;
   raw_ptr<ConfigurableStorageDelegate> delegate_;
 };
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        StorageUsedAfterFailedInitilization_FailsSilently) {
   // We create a failed initialization by writing a dir to the database file
   // path.
   base::CreateDirectoryAndGetError(
       dir_.GetPath().Append(FILE_PATH_LITERAL("Conversions")), nullptr);
-  std::unique_ptr<AttributionStorage> storage =
-      std::make_unique<AttributionStorageSql>(
+  std::unique_ptr<AttributionResolver> storage =
+      std::make_unique<AttributionResolverImpl>(
           dir_.GetPath(), std::make_unique<ConfigurableStorageDelegate>());
-  static_cast<AttributionStorageSql*>(storage.get())
+  static_cast<AttributionResolverImpl*>(storage.get())
       ->set_ignore_errors_for_testing(true);
 
-  // Test all public methods on AttributionStorage.
+  // Test all public methods on AttributionResolver.
   EXPECT_NO_FATAL_FAILURE(storage->StoreSource(SourceBuilder().Build()));
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kInternalError,
             storage->MaybeCreateAndStoreReport(DefaultTrigger())
@@ -218,7 +219,7 @@
   EXPECT_EQ(storage->AdjustOfflineReportTimes(), std::nullopt);
 }
 
-TEST_F(AttributionStorageTest, ImpressionStoredAndRetrieved_ValuesIdentical) {
+TEST_F(AttributionResolverTest, ImpressionStoredAndRetrieved_ValuesIdentical) {
   base::HistogramTester histograms;
   storage()->StoreSource(SourceBuilder().Build());
   histograms.ExpectBucketCount("Conversions.DbVersionOnSourceStored",
@@ -227,7 +228,7 @@
               ElementsAre(SourceBuilder().BuildStored()));
 }
 
-TEST_F(AttributionStorageTest, UniqueReportWindowsStored_ValuesIdentical) {
+TEST_F(AttributionResolverTest, UniqueReportWindowsStored_ValuesIdentical) {
   base::Time source_time = base::Time::Now();
 
   const auto kTriggerSpecs =
@@ -250,7 +251,7 @@
                    source_time + base::Days(5)))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        GetWithNoMatchingImpressions_NoImpressionsReturned) {
   EXPECT_THAT(
       storage()->MaybeCreateAndStoreReport(DefaultTrigger()),
@@ -262,20 +263,20 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, GetWithMatchingImpression_ImpressionReturned) {
+TEST_F(AttributionResolverTest, GetWithMatchingImpression_ImpressionReturned) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest, MultipleImpressionsForConversion_OneConverts) {
+TEST_F(AttributionResolverTest, MultipleImpressionsForConversion_OneConverts) {
   storage()->StoreSource(SourceBuilder().Build());
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        CrossOriginSameDomainConversion_ImpressionConverted) {
   auto impression =
       SourceBuilder()
@@ -293,7 +294,7 @@
               .Build()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionWithMultipleDestinations_ImpressionConverted) {
   auto impression = SourceBuilder()
                         .SetDestinationSites(
@@ -312,7 +313,7 @@
               .Build()));
 }
 
-TEST_F(AttributionStorageTest, EventSourceImpressionsForConversion_Converts) {
+TEST_F(AttributionResolverTest, EventSourceImpressionsForConversion_Converts) {
   storage()->StoreSource(
       SourceBuilder().SetSourceType(SourceType::kEvent).Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
@@ -322,7 +323,7 @@
               ElementsAre(EventLevelDataIs(_)));
 }
 
-TEST_F(AttributionStorageTest, ImpressionExpired_NoConversionsStored) {
+TEST_F(AttributionResolverTest, ImpressionExpired_NoConversionsStored) {
   storage()->StoreSource(
       SourceBuilder().SetExpiry(base::Milliseconds(2)).Build());
   task_environment_.FastForwardBy(base::Milliseconds(2));
@@ -331,7 +332,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableReportWindowPassed_NoReportGenerated) {
   storage()->StoreSource(TestAggregatableSourceProvider()
                              .GetBuilder()
@@ -348,7 +349,7 @@
                 AttributionTrigger::AggregatableResult::kReportWindowPassed)));
 }
 
-TEST_F(AttributionStorageTest, ImpressionExpired_ConversionsStoredPrior) {
+TEST_F(AttributionResolverTest, ImpressionExpired_ConversionsStoredPrior) {
   storage()->StoreSource(
       SourceBuilder().SetExpiry(base::Milliseconds(4)).Build());
 
@@ -363,7 +364,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionWithSetMaxConversions_ConversionReportStored) {
   storage()->StoreSource(
       SourceBuilder().SetMaxEventLevelReports(kMaxConversions + 1).Build());
@@ -386,7 +387,7 @@
             DroppedEventLevelReportIs(Optional(TriggerDebugKeyIs(20u)))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionWithMaxConversionsSetToZero_NoReportGenerated) {
   storage()->StoreSource(SourceBuilder().SetMaxEventLevelReports(0).Build());
 
@@ -394,7 +395,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionReportWindowNotStarted_NoReportGenerated) {
   storage()->StoreSource(
       SourceBuilder()
@@ -408,7 +409,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionReportWindowsPassed_NoReportGenerated) {
   storage()->StoreSource(
       SourceBuilder()
@@ -424,7 +425,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest, OneConversion_OneReportScheduled) {
+TEST_F(AttributionResolverTest, OneConversion_OneReportScheduled) {
   auto conversion = DefaultTrigger();
 
   storage()->StoreSource(SourceBuilder().Build());
@@ -440,7 +441,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1u));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ConversionWithDifferentReportingOrigin_NoReportScheduled) {
   auto impression = SourceBuilder()
                         .SetReportingOrigin(*SuitableOrigin::Deserialize(
@@ -453,7 +454,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ConversionWithDifferentConversionOrigin_NoReportScheduled) {
   auto impression =
       SourceBuilder()
@@ -467,7 +468,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, ConversionReportDeleted_RemovedFromStorage) {
+TEST_F(AttributionResolverTest, ConversionReportDeleted_RemovedFromStorage) {
   base::HistogramTester histograms;
 
   storage()->StoreSource(SourceBuilder().Build());
@@ -483,7 +484,7 @@
   histograms.ExpectTotalCount("Conversions.DbVersionOnReportSentAndDeleted", 1);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ManyImpressionsWithManyConversions_OneImpressionAttributed) {
   const int kNumMultiTouchImpressions = 20;
 
@@ -504,7 +505,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MultipleImpressionsForConversion_UnattributedImpressionsInactive) {
   storage()->StoreSource(SourceBuilder().Build());
 
@@ -526,7 +527,7 @@
 // <reporting_origin, destination_origin> pair, all existing impressions for
 // that origin that have converted are marked ineligible for new conversions per
 // the multi-touch model.
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        NewImpressionForConvertedImpression_MarkedInactive) {
   storage()->StoreSource(SourceBuilder().SetSourceEventId(0).Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
@@ -553,7 +554,7 @@
       ElementsAre(ReportSourceIs(SourceEventIdIs(1000u))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        NonMatchingImpressionForConvertedImpression_FirstRemainsActive) {
   auto reporting_origin =
       SuitableOrigin::Deserialize("https://reporter.test").value();
@@ -580,7 +581,7 @@
                           ReportOriginIs(reporting_origin)));
 }
 
-TEST_F(AttributionStorageTest, ImpressionWithDeletedReport_RemainsActive) {
+TEST_F(AttributionResolverTest, ImpressionWithDeletedReport_RemainsActive) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
@@ -594,7 +595,7 @@
 }
 
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     MultipleImpressionsForConversionAtDifferentTimes_OneImpressionAttributed) {
   storage()->StoreSource(SourceBuilder().Build());
   storage()->StoreSource(SourceBuilder().Build());
@@ -614,7 +615,7 @@
               ElementsAre(ReportSourceIs(SourceEventIdIs(10))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ImpressionsAtDifferentTimes_AttributedImpressionHasCorrectReportTime) {
   auto first_impression = SourceBuilder().Build();
   storage()->StoreSource(first_impression);
@@ -638,7 +639,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
 }
 
-TEST_F(AttributionStorageTest, GetAttributionReportsMultipleTimes_SameResult) {
+TEST_F(AttributionResolverTest, GetAttributionReportsMultipleTimes_SameResult) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
@@ -653,7 +654,7 @@
   EXPECT_EQ(first_call_reports, second_call_reports);
 }
 
-TEST_F(AttributionStorageTest, ExceedsChannelCapacity_SupersedesRateLimits) {
+TEST_F(AttributionResolverTest, ExceedsChannelCapacity_SupersedesRateLimits) {
   delegate()->set_max_sources_per_origin(1);
   EXPECT_EQ(storage()->StoreSource(SourceBuilder().Build()).status(),
             StorableSource::Result::kSuccess);
@@ -663,7 +664,7 @@
             StorableSource::Result::kExceedsMaxChannelCapacity);
 }
 
-TEST_F(AttributionStorageTest, MaxImpressionsPerOrigin_LimitsStorage) {
+TEST_F(AttributionResolverTest, MaxImpressionsPerOrigin_LimitsStorage) {
   delegate()->set_max_sources_per_origin(2);
 
   base::HistogramTester histograms;
@@ -723,7 +724,7 @@
               ElementsAre(SourceEventIdIs(5u), SourceEventIdIs(6u)));
 }
 
-TEST_F(AttributionStorageTest, MaxImpressionsPerOrigin_PerOriginNotSite) {
+TEST_F(AttributionResolverTest, MaxImpressionsPerOrigin_PerOriginNotSite) {
   delegate()->set_max_sources_per_origin(2);
   storage()->StoreSource(SourceBuilder()
                              .SetSourceOrigin(*SuitableOrigin::Deserialize(
@@ -772,7 +773,7 @@
 // Regression test for https://crbug.com/1510433 in which expired sources
 // were erroneously counted during calculation of the sources-per-source-origin
 // limit check.
-TEST_F(AttributionStorageTest, MaxImpressionsPerOrigin_ExpiredSourcesIgnored) {
+TEST_F(AttributionResolverTest, MaxImpressionsPerOrigin_ExpiredSourcesIgnored) {
   delegate()->set_max_sources_per_origin(1);
 
   // Effectively prevent expired sources from being deleted/deactivated.
@@ -809,7 +810,7 @@
               ElementsAre(SourceEventIdIs(222u)));
 }
 
-TEST_F(AttributionStorageTest, MaxEventLevelReportsPerDestination) {
+TEST_F(AttributionResolverTest, MaxEventLevelReportsPerDestination) {
   SourceBuilder source_builder = TestAggregatableSourceProvider().GetBuilder();
 
   delegate()->set_max_reports_per_destination(
@@ -840,7 +841,7 @@
                     CreateReportMaxAggregatableReportsLimitIs(std::nullopt)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxEventLevelReportsPerDestination_MultipleDestinations) {
   SourceBuilder source_builder = TestAggregatableSourceProvider().GetBuilder();
 
@@ -888,7 +889,7 @@
                     CreateReportMaxAggregatableReportsLimitIs(std::nullopt)));
 }
 
-TEST_F(AttributionStorageTest, MaxAggregatableReportsPerDestination) {
+TEST_F(AttributionResolverTest, MaxAggregatableReportsPerDestination) {
   SourceBuilder source_builder = TestAggregatableSourceProvider().GetBuilder();
 
   delegate()->set_max_reports_per_destination(
@@ -919,7 +920,7 @@
                     CreateReportMaxAggregatableReportsLimitIs(1)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAggregatableReportsPerDestination_MultipleDestinations) {
   SourceBuilder source_builder = TestAggregatableSourceProvider().GetBuilder();
 
@@ -967,7 +968,7 @@
                     CreateReportMaxAggregatableReportsLimitIs(1)));
 }
 
-TEST_F(AttributionStorageTest, ClearDataWithNoMatch_NoDelete) {
+TEST_F(AttributionResolverTest, ClearDataWithNoMatch_NoDelete) {
   base::Time now = base::Time::Now();
   storage()->StoreSource(SourceBuilder(now).Build());
   storage()->ClearData(
@@ -976,7 +977,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        ClearData_SourceAndDestinationOriginsIrrelevant) {
   const auto origin = *SuitableOrigin::Deserialize("https://a.test");
 
@@ -997,7 +998,7 @@
   EXPECT_EQ(storage()->GetAttributionReports(base::Time::Max()).size(), 1u);
 }
 
-TEST_F(AttributionStorageTest, ClearDataOutsideRange_NoDelete) {
+TEST_F(AttributionResolverTest, ClearDataOutsideRange_NoDelete) {
   base::Time now = base::Time::Now();
   auto impression = SourceBuilder(now).Build();
   storage()->StoreSource(impression);
@@ -1008,7 +1009,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest, ClearDataImpression) {
+TEST_F(AttributionResolverTest, ClearDataImpression) {
   base::Time now = base::Time::Now();
 
   {
@@ -1022,7 +1023,7 @@
   }
 }
 
-TEST_F(AttributionStorageTest, ClearDataImpressionConversion) {
+TEST_F(AttributionResolverTest, ClearDataImpressionConversion) {
   base::Time now = base::Time::Now();
   auto impression = SourceBuilder(now).Build();
   auto conversion = DefaultTrigger();
@@ -1038,7 +1039,7 @@
 }
 
 // The null filter should match all origins.
-TEST_F(AttributionStorageTest, ClearDataNullFilter) {
+TEST_F(AttributionResolverTest, ClearDataNullFilter) {
   base::Time now = base::Time::Now();
 
   for (int i = 0; i < 10; i++) {
@@ -1082,7 +1083,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), SizeIs(5));
 }
 
-TEST_F(AttributionStorageTest, ClearDataWithImpressionOutsideRange) {
+TEST_F(AttributionResolverTest, ClearDataWithImpressionOutsideRange) {
   base::Time start = base::Time::Now();
   auto impression = SourceBuilder(start).SetExpiry(base::Days(30)).Build();
   auto conversion = DefaultTrigger();
@@ -1098,7 +1099,7 @@
 
 // Deletions with time range between the impression and conversion should not
 // delete anything, unless the time range intersects one of the events.
-TEST_F(AttributionStorageTest, ClearDataRangeBetweenEvents) {
+TEST_F(AttributionResolverTest, ClearDataRangeBetweenEvents) {
   base::Time start = base::Time::Now();
 
   auto impression = SourceBuilder().SetExpiry(base::Days(30)).Build();
@@ -1119,7 +1120,7 @@
 
 // Test that only a subset of impressions / conversions are deleted with
 // multiple impressions per conversion, if only a subset of impressions match.
-TEST_F(AttributionStorageTest, ClearDataWithMultiTouch) {
+TEST_F(AttributionResolverTest, ClearDataWithMultiTouch) {
   base::Time start = base::Time::Now();
   auto impression1 = SourceBuilder(start).SetExpiry(base::Days(30)).Build();
   storage()->StoreSource(impression1);
@@ -1139,7 +1140,7 @@
 }
 
 // The max time range with a null filter should delete everything.
-TEST_F(AttributionStorageTest, DeleteAll) {
+TEST_F(AttributionResolverTest, DeleteAll) {
   base::Time start = base::Time::Now();
   for (int i = 0; i < 10; i++) {
     storage()->StoreSource(
@@ -1162,7 +1163,7 @@
 
 // Same as the above test, but uses base::Time() instead of base::Time::Min()
 // for delete_begin, which should yield the same behavior.
-TEST_F(AttributionStorageTest, DeleteAllNullDeleteBegin) {
+TEST_F(AttributionResolverTest, DeleteAllNullDeleteBegin) {
   base::Time start = base::Time::Now();
   for (int i = 0; i < 10; i++) {
     storage()->StoreSource(
@@ -1183,7 +1184,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, MaxAttributionsBetweenSites) {
+TEST_F(AttributionResolverTest, MaxAttributionsBetweenSites) {
   base::HistogramTester histogram_tester;
 
   delegate()->set_rate_limits([]() {
@@ -1265,7 +1266,7 @@
                                base::Bucket(2, 1)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAttributionReportsBetweenSites_IgnoresSourceType) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
@@ -1289,7 +1290,7 @@
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        NeverAttributeImpression_EventLevelReportNotStored) {
   delegate()->set_randomized_response(
       std::vector<attribution_reporting::FakeEventLevelReport>{});
@@ -1312,7 +1313,7 @@
                       DefaultAggregatableHistogramContributions()))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AttributeFalseImpression_OtherSourceDeactivated) {
   storage()->StoreSource(SourceBuilder().SetSourceEventId(7).Build());
 
@@ -1333,7 +1334,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), ElementsAre(SourceEventIdIs(5u)));
 }
 
-TEST_F(AttributionStorageTest, NeverAttributeImpression_RateLimitsChanged) {
+TEST_F(AttributionResolverTest, NeverAttributeImpression_RateLimitsChanged) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.time_window = base::TimeDelta::Max();
@@ -1361,7 +1362,7 @@
                 AttributionTrigger::AggregatableResult::kSuccess)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        NeverAndTruthfullyAttributeImpressions_EventLevelReportNotStored) {
   TestAggregatableSourceProvider provider;
 
@@ -1400,7 +1401,7 @@
                       AggregatableHistogramContributionsAre(contributions))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxDestinationsPerSource_ScopedToSourceSiteAndReportingSite) {
   delegate()->set_max_destinations_per_source_site_reporting_site(3);
 
@@ -1449,7 +1450,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(6));
 }
 
-TEST_F(AttributionStorageTest, DestinationLimit_ApplyLimit) {
+TEST_F(AttributionResolverTest, DestinationLimit_ApplyLimit) {
   delegate()->set_max_destinations_per_source_site_reporting_site(1);
   delegate()->set_delete_expired_sources_frequency(base::Milliseconds(10));
 
@@ -1504,7 +1505,7 @@
       StorableSource::Result::kSuccess);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAttributionDestinationsPerSource_AppliesToNavigationSources) {
   delegate()->set_max_destinations_per_source_site_reporting_site(1);
   storage()->StoreSource(
@@ -1521,7 +1522,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAttributionDestinationsPerSource_CountsAllSourceTypes) {
   delegate()->set_max_destinations_per_source_site_reporting_site(1);
   storage()->StoreSource(
@@ -1547,7 +1548,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAttributionDestinationsPerSource_CountsUnexpiredSources) {
   delegate()->set_max_destinations_per_source_site_reporting_site(1);
   delegate()->set_delete_expired_rate_limits_frequency(base::Milliseconds(10));
@@ -1583,7 +1584,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaxAttributionDestinationsPerSource_SourceWithTooManyDestinations) {
   delegate()->set_max_destinations_per_source_site_reporting_site(1);
 
@@ -1605,7 +1606,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        HandleSource_DestinationThrottleReportingLimitReached) {
   // Current reporting limit for Destination Throttle
   int max_per_reporting_source_site = 50;
@@ -1626,7 +1627,7 @@
               StorableSource::Result::kDestinationReportingLimitReached);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        HandleSource_DestinationThrottleGlobalLimitReached) {
   // Current global limit for Destination Throttle
   int max_global_source_site = 200;
@@ -1647,7 +1648,7 @@
               StorableSource::Result::kDestinationGlobalLimitReached);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        HandleSource_DestinationThrottleBothLimitsReached) {
   int max_global_source_site = 200;
   for (int i = 0; i < max_global_source_site; i += 4) {
@@ -1687,7 +1688,7 @@
               StorableSource::Result::kDestinationBothLimitsReached);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MultipleImpressionsPerConversion_MostRecentAttributesForSamePriority) {
   storage()->StoreSource(SourceBuilder().SetSourceEventId(3).Build());
 
@@ -1706,7 +1707,7 @@
               ElementsAre(ReportSourceIs(SourceEventIdIs(5u))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MultipleImpressionsPerConversion_HighestPriorityAttributes) {
   storage()->StoreSource(
       SourceBuilder().SetPriority(100).SetSourceEventId(3).Build());
@@ -1727,7 +1728,7 @@
               ElementsAre(ReportSourceIs(SourceEventIdIs(5u))));
 }
 
-TEST_F(AttributionStorageTest, MultipleImpressions_CorrectDeactivation) {
+TEST_F(AttributionResolverTest, MultipleImpressions_CorrectDeactivation) {
   storage()->StoreSource(
       SourceBuilder().SetSourceEventId(3).SetPriority(0).Build());
   storage()->StoreSource(
@@ -1743,7 +1744,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), ElementsAre(SourceEventIdIs(5u)));
 }
 
-TEST_F(AttributionStorageTest, FalselyAttributeImpression_ReportStored) {
+TEST_F(AttributionResolverTest, FalselyAttributeImpression_ReportStored) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.time_window = base::TimeDelta::Max();
@@ -1856,7 +1857,7 @@
                   expected_aggregatable_report));
 }
 
-TEST_F(AttributionStorageTest, StoreSource_ReturnsMinFakeReportTime) {
+TEST_F(AttributionResolverTest, StoreSource_ReturnsMinFakeReportTime) {
   const base::Time now = base::Time::Now();
 
   const struct {
@@ -1899,7 +1900,7 @@
   }
 }
 
-TEST_F(AttributionStorageTest, TriggerPriority) {
+TEST_F(AttributionResolverTest, TriggerPriority) {
   storage()->StoreSource(SourceBuilder()
                              .SetSourceEventId(3)
                              .SetPriority(0)
@@ -1960,7 +1961,7 @@
 
 // Regression test for erroneous use of report_time instead of
 // initial_report_time in event-level prioritization (http://crbug.com/1500598).
-TEST_F(AttributionStorageTest, TriggerPriority_UsesOriginalReportTime) {
+TEST_F(AttributionResolverTest, TriggerPriority_UsesOriginalReportTime) {
   delegate()->use_realistic_report_times();
 
   storage()->StoreSource(
@@ -2016,7 +2017,7 @@
                 Optional(ReportTimeIs(expected_first_report_time)))));
 }
 
-TEST_F(AttributionStorageTest, TriggerPriority_Simple) {
+TEST_F(AttributionResolverTest, TriggerPriority_Simple) {
   storage()->StoreSource(SourceBuilder().SetMaxEventLevelReports(1).Build());
 
   int i = 0;
@@ -2036,7 +2037,7 @@
               ElementsAre(TriggerDebugKeyIs(9u)));
 }
 
-TEST_F(AttributionStorageTest, TriggerPriority_SamePriorityDeletesMostRecent) {
+TEST_F(AttributionResolverTest, TriggerPriority_SamePriorityDeletesMostRecent) {
   storage()->StoreSource(SourceBuilder().SetMaxEventLevelReports(2).Build());
 
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
@@ -2069,7 +2070,7 @@
                           EventLevelDataIs(TriggerDataIs(5u))));
 }
 
-TEST_F(AttributionStorageTest, TriggerPriority_DeactivatesImpression) {
+TEST_F(AttributionResolverTest, TriggerPriority_DeactivatesImpression) {
   storage()->StoreSource(SourceBuilder()
                              .SetSourceEventId(3)
                              .SetPriority(0)
@@ -2111,7 +2112,7 @@
           StoredSource::ActiveState::kReachedEventLevelAttributionLimit)));
 }
 
-TEST_F(AttributionStorageTest, TriggerPriority_AttributionRateLimitAdjusted) {
+TEST_F(AttributionResolverTest, TriggerPriority_AttributionRateLimitAdjusted) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.time_window = base::TimeDelta::Max();
@@ -2142,7 +2143,7 @@
               ElementsAre(TriggerDebugKeyIs(1u), TriggerDebugKeyIs(2u)));
 }
 
-TEST_F(AttributionStorageTest, DedupKey_Dedups) {
+TEST_F(AttributionResolverTest, DedupKey_Dedups) {
   storage()->StoreSource(
       SourceBuilder()
           .SetDestinationSites(
@@ -2220,7 +2221,7 @@
                           DedupKeysAre(ElementsAre(12))));
 }
 
-TEST_F(AttributionStorageTest, DedupKey_DedupsAfterConversionDeletion) {
+TEST_F(AttributionResolverTest, DedupKey_DedupsAfterConversionDeletion) {
   storage()->StoreSource(
       SourceBuilder()
           .SetDestinationSites(
@@ -2265,7 +2266,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, AggregatableDedupKey_Dedups) {
+TEST_F(AttributionResolverTest, AggregatableDedupKey_Dedups) {
   TestAggregatableSourceProvider provider;
   storage()->StoreSource(
       provider.GetBuilder()
@@ -2346,7 +2347,7 @@
                           AggregatableDedupKeysAre(ElementsAre(12))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableDedupKey_DedupsAfterConversionDeletion) {
   storage()->StoreSource(
       TestAggregatableSourceProvider()
@@ -2394,7 +2395,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, DedupKey_AggregatableReportNotDedups) {
+TEST_F(AttributionResolverTest, DedupKey_AggregatableReportNotDedups) {
   storage()->StoreSource(
       TestAggregatableSourceProvider()
           .GetBuilder()
@@ -2427,7 +2428,7 @@
             result.aggregatable_status());
 }
 
-TEST_F(AttributionStorageTest, AggregatableDedupKey_EventLevelReportNotDedups) {
+TEST_F(AttributionResolverTest, AggregatableDedupKey_EventLevelReportNotDedups) {
   storage()->StoreSource(
       TestAggregatableSourceProvider()
           .GetBuilder()
@@ -2460,7 +2461,7 @@
             result.aggregatable_status());
 }
 
-TEST_F(AttributionStorageTest, AggregatableDedupKeysFiltering) {
+TEST_F(AttributionResolverTest, AggregatableDedupKeysFiltering) {
   const auto origin = *SuitableOrigin::Deserialize("https://r.test");
 
   std::vector<attribution_reporting::AggregatableTriggerData>
@@ -2624,7 +2625,7 @@
   }
 }
 
-TEST_F(AttributionStorageTest, GetAttributionReports_SetsPriority) {
+TEST_F(AttributionResolverTest, GetAttributionReports_SetsPriority) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
@@ -2634,7 +2635,7 @@
               ElementsAre(EventLevelDataIs(TriggerPriorityIs(13))));
 }
 
-TEST_F(AttributionStorageTest, NoIDReuse_Impression) {
+TEST_F(AttributionResolverTest, NoIDReuse_Impression) {
   storage()->StoreSource(SourceBuilder().Build());
   auto sources = storage()->GetActiveSources();
   const StoredSource::Id id1 = sources.front().source_id();
@@ -2650,7 +2651,7 @@
   EXPECT_NE(id1, id2);
 }
 
-TEST_F(AttributionStorageTest, NoIDReuse_Conversion) {
+TEST_F(AttributionResolverTest, NoIDReuse_Conversion) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
@@ -2672,7 +2673,7 @@
   EXPECT_NE(id1, id2);
 }
 
-TEST_F(AttributionStorageTest, UpdateReportForSendFailure) {
+TEST_F(AttributionResolverTest, UpdateReportForSendFailure) {
   storage()->StoreSource(TestAggregatableSourceProvider().GetBuilder().Build());
   EXPECT_THAT(storage()->MaybeCreateAndStoreReport(
                   DefaultAggregatableTriggerBuilder().Build()),
@@ -2709,7 +2710,7 @@
           AllOf(FailedSendAttemptsIs(1), ReportTimeIs(new_report_time))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaybeCreateAndStoreEventLevelReport_ReturnsDeactivatedSources) {
   storage()->StoreSource(
       SourceBuilder().SetMaxEventLevelReports(kMaxConversions).Build());
@@ -2744,7 +2745,7 @@
           StoredSource::ActiveState::kReachedEventLevelAttributionLimit)));
 }
 
-TEST_F(AttributionStorageTest, ReportID_RoundTrips) {
+TEST_F(AttributionResolverTest, ReportID_RoundTrips) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(DefaultTrigger()));
@@ -2755,11 +2756,11 @@
   EXPECT_EQ(DefaultExternalReportID(), actual_reports[0].external_report_id());
 }
 
-TEST_F(AttributionStorageTest, AdjustOfflineReportTimes) {
+TEST_F(AttributionResolverTest, AdjustOfflineReportTimes) {
   EXPECT_EQ(storage()->AdjustOfflineReportTimes(), std::nullopt);
 
   delegate()->set_offline_report_delay_config(
-      AttributionStorageDelegate::OfflineReportDelayConfig{
+      AttributionResolverDelegate::OfflineReportDelayConfig{
           .min = base::Hours(1), .max = base::Hours(1)});
   EXPECT_EQ(storage()->AdjustOfflineReportTimes(), std::nullopt);
 
@@ -2802,9 +2803,9 @@
                                 InitialReportTimeIs(original_report_time))));
 }
 
-TEST_F(AttributionStorageTest, AdjustOfflineReportTimes_Range) {
+TEST_F(AttributionResolverTest, AdjustOfflineReportTimes_Range) {
   delegate()->set_offline_report_delay_config(
-      AttributionStorageDelegate::OfflineReportDelayConfig{
+      AttributionResolverDelegate::OfflineReportDelayConfig{
           .min = base::Hours(1), .max = base::Hours(3)});
 
   storage()->StoreSource(TestAggregatableSourceProvider().GetBuilder().Build());
@@ -2836,7 +2837,7 @@
                 InitialReportTimeIs(original_report_time))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AdjustOfflineReportTimes_ReturnsMinReportTimeWithoutDelay) {
   delegate()->set_offline_report_delay_config(std::nullopt);
 
@@ -2854,7 +2855,7 @@
             reports.front().report_time());
 }
 
-TEST_F(AttributionStorageTest, GetNextEventReportTime) {
+TEST_F(AttributionResolverTest, GetNextEventReportTime) {
   const auto origin_a = *SuitableOrigin::Deserialize("https://a.example/");
   const auto origin_b = *SuitableOrigin::Deserialize("https://b.example/");
 
@@ -2883,7 +2884,7 @@
   EXPECT_EQ(storage()->GetNextReportTime(report_time_b), std::nullopt);
 }
 
-TEST_F(AttributionStorageTest, GetAttributionReports_Shuffles) {
+TEST_F(AttributionResolverTest, GetAttributionReports_Shuffles) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
@@ -2910,7 +2911,7 @@
                           EventLevelDataIs(TriggerDataIs(3))));
 }
 
-TEST_F(AttributionStorageTest, GetAttributionReportsExceedLimit_Shuffles) {
+TEST_F(AttributionResolverTest, GetAttributionReportsExceedLimit_Shuffles) {
   storage()->StoreSource(TestAggregatableSourceProvider().GetBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
@@ -2947,7 +2948,7 @@
                           EventLevelDataIs(TriggerDataIs(3))));
 }
 
-TEST_F(AttributionStorageTest, GetAttributionDataKeysSet) {
+TEST_F(AttributionResolverTest, GetAttributionDataKeysSet) {
   auto expected_1 = AttributionDataModel::DataKey(
       url::Origin::Create(GURL("https://a.r.test")));
   auto expected_2 = AttributionDataModel::DataKey(
@@ -2980,13 +2981,13 @@
   EXPECT_THAT(storage()->GetAllDataKeys(), ElementsAre(expected_1, expected_2));
 }
 
-TEST_F(AttributionStorageTest, SourceDebugKey_RoundTrips) {
+TEST_F(AttributionResolverTest, SourceDebugKey_RoundTrips) {
   storage()->StoreSource(
       SourceBuilder().SetDebugKey(33).SetDebugCookieSet(true).Build());
   EXPECT_THAT(storage()->GetActiveSources(), ElementsAre(SourceDebugKeyIs(33)));
 }
 
-TEST_F(AttributionStorageTest, TriggerDebugKey_RoundTrips) {
+TEST_F(AttributionResolverTest, TriggerDebugKey_RoundTrips) {
   storage()->StoreSource(
       SourceBuilder().SetDebugKey(22).SetDebugCookieSet(true).Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
@@ -2998,7 +2999,7 @@
                                 TriggerDebugKeyIs(33))));
 }
 
-TEST_F(AttributionStorageTest, AttributionAggregationKeys_RoundTrips) {
+TEST_F(AttributionResolverTest, AttributionAggregationKeys_RoundTrips) {
   auto aggregation_keys =
       attribution_reporting::AggregationKeys::FromKeys({{"key", 345}});
   ASSERT_TRUE(aggregation_keys.has_value());
@@ -3008,7 +3009,7 @@
               ElementsAre(AggregationKeysAre(*aggregation_keys)));
 }
 
-TEST_F(AttributionStorageTest, MaybeCreateAndStoreReport_ReturnsNewReport) {
+TEST_F(AttributionResolverTest, MaybeCreateAndStoreReport_ReturnsNewReport) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_THAT(storage()->MaybeCreateAndStoreReport(TriggerBuilder().Build()),
               AllOf(CreateReportEventLevelStatusIs(
@@ -3019,7 +3020,7 @@
 
 // This is tested more thoroughly by the `RateLimitTable` unit tests. Here just
 // ensure that the rate limits are consulted at all.
-TEST_F(AttributionStorageTest, MaxReportingOriginsPerSource) {
+TEST_F(AttributionResolverTest, MaxReportingOriginsPerSource) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.time_window = base::TimeDelta::Max();
@@ -3065,7 +3066,7 @@
 // This is tested more thoroughly by the `RateLimitTable` unit tests. Here just
 // ensure that the rate limits are consulted at all and the rate limit is shared
 // between event-level and aggregatable reports.
-TEST_F(AttributionStorageTest, MaxReportingOriginsPerAttribution) {
+TEST_F(AttributionResolverTest, MaxReportingOriginsPerAttribution) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.time_window = base::TimeDelta::Max();
@@ -3127,13 +3128,13 @@
               UnorderedElementsAre(TriggerDebugKeyIs(1), TriggerDebugKeyIs(2)));
 }
 
-TEST_F(AttributionStorageTest, SourceBudgetValueRetrieved) {
+TEST_F(AttributionResolverTest, SourceBudgetValueRetrieved) {
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_THAT(storage()->GetActiveSources(),
               ElementsAre(AggregatableBudgetConsumedIs(0)));
 }
 
-TEST_F(AttributionStorageTest, MaxAggregatableBudgetPerSource) {
+TEST_F(AttributionResolverTest, MaxAggregatableBudgetPerSource) {
   auto provider = TestAggregatableSourceProvider(/*size=*/2);
   storage()->StoreSource(provider.GetBuilder().Build());
 
@@ -3184,7 +3185,7 @@
                   AttributionTrigger::AggregatableResult::kSuccess));
 }
 
-TEST_F(AttributionStorageTest, BudgetConsumedAfterTriggerIsRetrieved) {
+TEST_F(AttributionResolverTest, BudgetConsumedAfterTriggerIsRetrieved) {
   auto provider = TestAggregatableSourceProvider(/*size=*/1);
   storage()->StoreSource(provider.GetBuilder().Build());
 
@@ -3198,7 +3199,7 @@
               ElementsAre(AggregatableBudgetConsumedIs(2)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        GetAttributionReports_SetsRandomizedTriggerRate) {
   delegate()->set_randomized_response_rate(0.1);
 
@@ -3227,7 +3228,7 @@
                                        RandomizedResponseRateIs(0.1)))));
 }
 
-TEST_F(AttributionStorageTest, RandomizedResponseRatePerSourceUsed) {
+TEST_F(AttributionResolverTest, RandomizedResponseRatePerSourceUsed) {
   delegate()->set_randomized_response_rate(0.1);
   storage()->StoreSource(SourceBuilder().Build());
   delegate()->set_randomized_response_rate(0.2);
@@ -3240,7 +3241,7 @@
 
 // Will return minimum of next event-level report and next aggregatable report
 // time if both present.
-TEST_F(AttributionStorageTest, GetNextReportTime) {
+TEST_F(AttributionResolverTest, GetNextReportTime) {
   EXPECT_EQ(storage()->GetNextReportTime(base::Time::Min()), std::nullopt);
 
   storage()->StoreSource(TestAggregatableSourceProvider()
@@ -3283,7 +3284,7 @@
   EXPECT_EQ(storage()->GetNextReportTime(report_time_c), std::nullopt);
 }
 
-TEST_F(AttributionStorageTest, TriggerDataSanitized) {
+TEST_F(AttributionResolverTest, TriggerDataSanitized) {
   const auto origin1 = *SuitableOrigin::Deserialize("https://r1.test");
   const auto origin2 = *SuitableOrigin::Deserialize("https://r2.test");
 
@@ -3309,7 +3310,7 @@
                         EventLevelDataIs(TriggerDataIs(1)))));
 }
 
-TEST_F(AttributionStorageTest, SourceFilterData_RoundTrips) {
+TEST_F(AttributionResolverTest, SourceFilterData_RoundTrips) {
   storage()->StoreSource(SourceBuilder()
                              .SetFilterData(AttributionFilterData())
                              .SetSourceType(SourceType::kNavigation)
@@ -3328,7 +3329,7 @@
                           SourceFilterDataIs(filter_data)));
 }
 
-TEST_F(AttributionStorageTest, NoMatchingTriggerData_ReturnsError) {
+TEST_F(AttributionResolverTest, NoMatchingTriggerData_ReturnsError) {
   const auto origin = *SuitableOrigin::Deserialize("https://r.test");
 
   storage()->StoreSource(SourceBuilder()
@@ -3360,7 +3361,7 @@
               ElementsAre(DedupKeysAre(IsEmpty())));
 }
 
-TEST_F(AttributionStorageTest, MatchingTriggerData_UsesCorrectData) {
+TEST_F(AttributionResolverTest, MatchingTriggerData_UsesCorrectData) {
   const auto origin = *SuitableOrigin::Deserialize("https://r.test");
 
   storage()->StoreSource(
@@ -3436,7 +3437,7 @@
               ElementsAre(DedupKeysAre(ElementsAre(33))));
 }
 
-TEST_F(AttributionStorageTest, TopLevelTriggerFiltering) {
+TEST_F(AttributionResolverTest, TopLevelTriggerFiltering) {
   const auto origin = *SuitableOrigin::Deserialize("https://r.test");
 
   std::vector<attribution_reporting::EventTriggerData> event_triggers = {
@@ -3545,7 +3546,7 @@
                             kNoMatchingSourceFilterData)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableAttributionNoMatchingSources_NoSourcesReturned) {
   EXPECT_THAT(
       storage()->MaybeCreateAndStoreReport(
@@ -3557,7 +3558,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableAttributionNoAggregationKeys_NoContributions) {
   storage()->StoreSource(SourceBuilder().Build());
 
@@ -3576,7 +3577,7 @@
             NewAggregatableReportIs(Eq(std::nullopt))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableAttributionValues_NoMatchingFilters_NoContributions) {
   storage()->StoreSource(
       SourceBuilder()
@@ -3599,7 +3600,7 @@
       AttributionTrigger::AggregatableResult::kNoHistograms);
 }
 
-TEST_F(AttributionStorageTest, AggregatableAttribution_ReportsScheduled) {
+TEST_F(AttributionResolverTest, AggregatableAttribution_ReportsScheduled) {
   auto source_builder = TestAggregatableSourceProvider().GetBuilder();
   storage()->StoreSource(source_builder.Build());
 
@@ -3633,7 +3634,7 @@
 }
 
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     MaybeCreateAndStoreAggregatableReport_reachedEventLevelAttributionLimit) {
   storage()->StoreSource(TestAggregatableSourceProvider()
                              .GetBuilder()
@@ -3686,7 +3687,7 @@
           StoredSource::ActiveState::kReachedEventLevelAttributionLimit)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AggregatableTriggerDataOrValuesNotSet_Registered) {
   storage()->StoreSource(
       SourceBuilder()
@@ -3710,7 +3711,7 @@
             AttributionTrigger::AggregatableResult::kSuccess);
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        PrioritizationConsidersAttributedAndUnattributedSources) {
   storage()->StoreSource(
       SourceBuilder().SetSourceEventId(3).SetPriority(10).Build());
@@ -3729,7 +3730,7 @@
                           ReportSourceIs(SourceEventIdIs(3))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        MaybeCreateAndStoreEventLevelReport_DeactivatesUnattributedSources) {
   storage()->StoreSource(
       SourceBuilder().SetSourceEventId(3).SetPriority(1).Build());
@@ -3752,7 +3753,7 @@
                           ReportSourceIs(SourceEventIdIs(7))));
 }
 
-TEST_F(AttributionStorageTest, AggregationCoordinator_RoundTrip) {
+TEST_F(AttributionResolverTest, AggregationCoordinator_RoundTrip) {
   base::test::ScopedFeatureList scoped_feature_list(
       ::aggregation_service::kAggregationServiceMultipleCloudProviders);
 
@@ -3775,7 +3776,7 @@
           AggregationCoordinatorOriginIs(coordinator_origin))));
 }
 
-TEST_F(AttributionStorageTest, MaxAttributions_BoundedBySourceTimeWindow) {
+TEST_F(AttributionResolverTest, MaxAttributions_BoundedBySourceTimeWindow) {
   constexpr base::TimeDelta kTimeWindow = base::Days(1);
   delegate()->set_rate_limits([kTimeWindow]() {
     AttributionConfig::RateLimitConfig r;
@@ -3803,7 +3804,7 @@
             MaybeCreateAndStoreEventLevelReport(trigger));
 }
 
-TEST_F(AttributionStorageTest, NoEventTriggerData_NotRegisteredReturned) {
+TEST_F(AttributionResolverTest, NoEventTriggerData_NotRegisteredReturned) {
   EXPECT_THAT(
       storage()->MaybeCreateAndStoreReport(
           DefaultAggregatableTriggerBuilder().Build(
@@ -3817,7 +3818,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, StoreNullAggregatableReport) {
+TEST_F(AttributionResolverTest, StoreNullAggregatableReport) {
   base::Time now = base::Time::Now();
   base::Time report_time = now + kReportDelay;
   base::Time fake_source_time = now;
@@ -3842,7 +3843,7 @@
               ElementsAre(expected_report));
 }
 
-TEST_F(AttributionStorageTest, NoAggregatableData_NoNullReport) {
+TEST_F(AttributionResolverTest, NoAggregatableData_NoNullReport) {
   delegate()->set_null_aggregatable_reports_lookback_days({0});
   auto result = storage()->MaybeCreateAndStoreReport(DefaultTrigger());
   delegate()->set_null_aggregatable_reports_lookback_days({});
@@ -3851,7 +3852,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Max()), IsEmpty());
 }
 
-TEST_F(AttributionStorageTest, BothRealAndNullAggregatableReports) {
+TEST_F(AttributionResolverTest, BothRealAndNullAggregatableReports) {
   base::Time now = base::Time::Now();
 
   SourceBuilder builder = TestAggregatableSourceProvider().GetBuilder(now);
@@ -3888,7 +3889,7 @@
       UnorderedElementsAre(expected_aggregatable_report, expected_null_report));
 }
 
-TEST_F(AttributionStorageTest, SourceRegistrationTimeConfig_RoundTrip) {
+TEST_F(AttributionResolverTest, SourceRegistrationTimeConfig_RoundTrip) {
   for (auto config :
        base::EnumSet<attribution_reporting::mojom::SourceRegistrationTimeConfig,
                      attribution_reporting::mojom::
@@ -3911,7 +3912,7 @@
   }
 }
 
-TEST_F(AttributionStorageTest, MaximumAggregatableReportsPerSource) {
+TEST_F(AttributionResolverTest, MaximumAggregatableReportsPerSource) {
   auto source = TestAggregatableSourceProvider().GetBuilder().Build();
   storage()->StoreSource(source);
   AttributionTrigger trigger = DefaultAggregatableTriggerBuilder().Build();
@@ -3923,7 +3924,7 @@
             MaybeCreateAndStoreAggregatableReport(trigger));
 }
 
-TEST_F(AttributionStorageTest, MaxSourceReportingOriginsPerSite) {
+TEST_F(AttributionResolverTest, MaxSourceReportingOriginsPerSite) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
     r.max_reporting_origins_per_source_reporting_site = 1;
@@ -3966,7 +3967,7 @@
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(4));
 }
 
-TEST_F(AttributionStorageTest, TriggerDataMatching) {
+TEST_F(AttributionResolverTest, TriggerDataMatching) {
   const struct {
     const char* desc;
     TriggerDataMatching trigger_data_matching;
@@ -4014,7 +4015,7 @@
   }
 }
 
-TEST_F(AttributionStorageTest, EventLevelDedupBeforeWindowCheck) {
+TEST_F(AttributionResolverTest, EventLevelDedupBeforeWindowCheck) {
   storage()->StoreSource(
       SourceBuilder()
           .SetTriggerSpecs(
@@ -4036,7 +4037,7 @@
                 TriggerBuilder().SetDedupKey(11).Build()));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AttributionAggregatableReportWithTriggerContextId_RoundTrip) {
   storage()->StoreSource(TestAggregatableSourceProvider().GetBuilder().Build());
 
@@ -4062,7 +4063,7 @@
                         ReportTimeIs(report_time))));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        NullAggregatableReportWithTriggerContextId_RoundTrip) {
   base::Time now = base::Time::Now();
   base::Time report_time = now;
@@ -4086,7 +4087,7 @@
 }
 
 // TODO(crbug.com/40941848): Support multiple trigger specs instead of just 1.
-TEST_F(AttributionStorageTest, RejectsMultipleTriggerSpecs) {
+TEST_F(AttributionResolverTest, RejectsMultipleTriggerSpecs) {
   auto source = SourceBuilder().Build();
   source.registration().trigger_specs =
       *TriggerSpecs::Create(/*trigger_data_indices=*/{{0, 0}},
@@ -4100,7 +4101,7 @@
 
 // Regression test for https://crbug.com/331100922.
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     FakeSourceCreateAggregatableReport_EffectiveDestinationAttributionRateLimitRecord) {
   delegate()->set_rate_limits([]() {
     AttributionConfig::RateLimitConfig r;
@@ -4161,7 +4162,7 @@
                         AttributionTrigger::AggregatableResult::kSuccess)));
 }
 
-TEST_F(AttributionStorageTest,
+TEST_F(AttributionResolverTest,
        AttributedTriggerIncludeSourceRegistrationTime_NullAggregatableReports) {
   SourceBuilder builder = TestAggregatableSourceProvider().GetBuilder();
   storage()->StoreSource(builder.Build());
@@ -4201,7 +4202,7 @@
 }
 
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     UnattributedTriggerIncludeSourceRegistrationTime_NullAggregatableReports) {
   const base::Time now = base::Time::Now();
 
@@ -4241,7 +4242,7 @@
 }
 
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     AttributedTriggerExcludeSourceRegistrationTime_NoNullAggregatableReport) {
   SourceBuilder builder = TestAggregatableSourceProvider().GetBuilder();
   storage()->StoreSource(builder.Build());
@@ -4265,7 +4266,7 @@
 }
 
 TEST_F(
-    AttributionStorageTest,
+    AttributionResolverTest,
     UnattributedTriggerExcludeSourceRegistrationTime_NullAggregatableReport) {
   const base::Time now = base::Time::Now();
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 34c8e09..43ee168 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -61,7 +61,7 @@
 #include "content/browser/attribution_reporting/attribution_info.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_reporting.pb.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 #include "content/browser/attribution_reporting/attribution_storage_sql_migrations.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
@@ -465,13 +465,13 @@
 
 AttributionStorageSql::AttributionStorageSql(
     const base::FilePath& user_data_directory,
-    std::unique_ptr<AttributionStorageDelegate> delegate)
+    AttributionResolverDelegate* delegate)
     : path_to_database_(user_data_directory.empty()
                             ? base::FilePath()
                             : DatabasePath(user_data_directory)),
       db_(sql::DatabaseOptions{.page_size = 4096, .cache_size = 32}),
-      delegate_(std::move(delegate)),
-      rate_limit_table_(delegate_.get()) {
+      delegate_(delegate),
+      rate_limit_table_(delegate_) {
   DCHECK(delegate_);
 
   db_.set_histogram_tag("Conversions");
@@ -3292,12 +3292,11 @@
             /*delete_rate_limit_data=*/true);
 }
 
-void AttributionStorageSql::SetDelegate(
-    std::unique_ptr<AttributionStorageDelegate> delegate) {
+void AttributionStorageSql::SetDelegate(AttributionResolverDelegate* delegate) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(delegate);
   rate_limit_table_.SetDelegate(*delegate);
-  delegate_ = std::move(delegate);
+  delegate_ = delegate;
 }
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h
index aa7641c..ee2c854d 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql.h
@@ -19,7 +19,7 @@
 #include "base/time/time.h"
 #include "base/types/expected.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/rate_limit_table.h"
 #include "content/browser/attribution_reporting/stored_source.h"
@@ -35,17 +35,17 @@
 
 namespace content {
 
-class AttributionStorageDelegate;
+class AttributionResolverDelegate;
 class StorableSource;
 class StoreSourceResult;
 struct AttributionInfo;
 
 enum class RateLimitResult : int;
 
-// Provides an implementation of AttributionStorage that is backed by SQLite.
+// Provides an implementation of storage that is backed by SQLite.
 // This class may be constructed on any sequence but must be accessed and
 // destroyed on the same sequence. The sequence must outlive |this|.
-class CONTENT_EXPORT AttributionStorageSql : public AttributionStorage {
+class CONTENT_EXPORT AttributionStorageSql {
  public:
   // Version number of the database.
   static constexpr int kCurrentVersionNumber = 59;
@@ -64,12 +64,12 @@
   // If `user_data_directory` is empty, the DB is created in memory and no data
   // is persisted to disk.
   AttributionStorageSql(const base::FilePath& user_data_directory,
-                        std::unique_ptr<AttributionStorageDelegate> delegate);
+                        AttributionResolverDelegate* delegate);
   AttributionStorageSql(const AttributionStorageSql&) = delete;
   AttributionStorageSql& operator=(const AttributionStorageSql&) = delete;
   AttributionStorageSql(AttributionStorageSql&&) = delete;
   AttributionStorageSql& operator=(AttributionStorageSql&&) = delete;
-  ~AttributionStorageSql() override;
+  ~AttributionStorageSql();
 
   void set_ignore_errors_for_testing(bool ignore_for_testing) {
     ignore_errors_for_testing_ = ignore_for_testing;
@@ -128,6 +128,26 @@
   // Deletes corrupt sources/reports if `deletion_counts` is not `nullptr`.
   void VerifyReports(DeletionCounts* deletion_counts);
 
+  StoreSourceResult StoreSource(StorableSource source);
+  CreateReportResult MaybeCreateAndStoreReport(AttributionTrigger);
+  std::vector<AttributionReport> GetAttributionReports(
+      base::Time max_report_time,
+      int limit = -1);
+  std::optional<base::Time> GetNextReportTime(base::Time time);
+  std::optional<AttributionReport> GetReport(AttributionReport::Id);
+  std::vector<StoredSource> GetActiveSources(int limit = -1);
+  std::set<AttributionDataModel::DataKey> GetAllDataKeys();
+  void DeleteByDataKey(const AttributionDataModel::DataKey& datakey);
+  bool DeleteReport(AttributionReport::Id report_id);
+  bool UpdateReportForSendFailure(AttributionReport::Id report_id,
+                                  base::Time new_report_time);
+  std::optional<base::Time> AdjustOfflineReportTimes();
+  void ClearData(base::Time delete_begin,
+                 base::Time delete_end,
+                 StoragePartition::StorageKeyMatcherFunction filter,
+                 bool delete_rate_limit_data);
+  void SetDelegate(AttributionResolverDelegate*);
+
  private:
   using ReportCorruptionStatusSet =
       base::EnumSet<ReportCorruptionStatus,
@@ -156,27 +176,6 @@
     kIgnoreIfAbsent,
   };
 
-  // AttributionStorage:
-  StoreSourceResult StoreSource(StorableSource source) override;
-  CreateReportResult MaybeCreateAndStoreReport(AttributionTrigger) override;
-  std::vector<AttributionReport> GetAttributionReports(
-      base::Time max_report_time,
-      int limit = -1) override;
-  std::optional<base::Time> GetNextReportTime(base::Time time) override;
-  std::optional<AttributionReport> GetReport(AttributionReport::Id) override;
-  std::vector<StoredSource> GetActiveSources(int limit = -1) override;
-  std::set<AttributionDataModel::DataKey> GetAllDataKeys() override;
-  void DeleteByDataKey(const AttributionDataModel::DataKey& datakey) override;
-  bool DeleteReport(AttributionReport::Id report_id) override;
-  bool UpdateReportForSendFailure(AttributionReport::Id report_id,
-                                  base::Time new_report_time) override;
-  std::optional<base::Time> AdjustOfflineReportTimes() override;
-  void ClearData(base::Time delete_begin,
-                 base::Time delete_end,
-                 StoragePartition::StorageKeyMatcherFunction filter,
-                 bool delete_rate_limit_data) override;
-  void SetDelegate(std::unique_ptr<AttributionStorageDelegate>) override;
-
   void ClearAllDataAllTime(bool delete_rate_limit_data)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
@@ -426,7 +425,7 @@
 
   sql::Database db_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  std::unique_ptr<AttributionStorageDelegate> delegate_
+  raw_ptr<AttributionResolverDelegate> delegate_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Table which stores timestamps of sent reports, and checks if new reports
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
index 4dcd85f..caa1686b 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
@@ -16,7 +16,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/time/time.h"
 #include "content/browser/attribution_reporting/attribution_reporting.pb.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
+#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
 #include "content/browser/attribution_reporting/attribution_storage_sql.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/store_source_result.h"
@@ -56,13 +57,13 @@
   void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); }
 
   void MigrateDatabase() {
-    AttributionStorageSql storage(
+    AttributionResolverImpl storage(
         temp_directory_.GetPath(),
         std::make_unique<ConfigurableStorageDelegate>());
 
     // We need to run an operation on storage to force the lazy initialization.
     std::ignore =
-        static_cast<AttributionStorage*>(&storage)->GetAttributionReports(
+        static_cast<AttributionResolver*>(&storage)->GetAttributionReports(
             base::Time::Min());
   }
 
@@ -133,13 +134,13 @@
 TEST_F(AttributionStorageSqlMigrationsTest, MigrateEmptyToCurrent) {
   base::HistogramTester histograms;
   {
-    AttributionStorageSql storage(
+    AttributionResolverImpl storage(
         temp_directory_.GetPath(),
         std::make_unique<ConfigurableStorageDelegate>());
 
     // We need to perform an operation that is non-trivial on an empty database
     // to force initialization.
-    static_cast<AttributionStorage*>(&storage)->StoreSource(
+    static_cast<AttributionResolver*>(&storage)->StoreSource(
         SourceBuilder(base::Time::Min()).Build());
   }
 
@@ -400,14 +401,14 @@
     ASSERT_TRUE(db.Open(DbPath()));
   }
   {
-    AttributionStorageSql storage(
+    AttributionResolverImpl storage(
         temp_directory_.GetPath(),
         std::make_unique<ConfigurableStorageDelegate>());
 
     // Store a valid report to verify corruption deletion.
-    static_cast<AttributionStorage*>(&storage)->StoreSource(
+    static_cast<AttributionResolver*>(&storage)->StoreSource(
         SourceBuilder().Build());
-    static_cast<AttributionStorage*>(&storage)->MaybeCreateAndStoreReport(
+    static_cast<AttributionResolver*>(&storage)->MaybeCreateAndStoreReport(
         DefaultTrigger());
   }
   MigrateDatabase();
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_query_plans_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_query_plans_unittest.cc
index 743f2afa..b10b432 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_query_plans_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_query_plans_unittest.cc
@@ -11,7 +11,8 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/string_util.h"
 #include "base/test/gmock_expected_support.h"
-#include "content/browser/attribution_reporting/attribution_storage_sql.h"
+#include "content/browser/attribution_reporting/attribution_resolver.h"
+#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/sql_queries.h"
 #include "content/browser/attribution_reporting/sql_query_plan_test_util.h"
@@ -35,8 +36,8 @@
     ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
 
     {
-      std::unique_ptr<AttributionStorage> storage =
-          std::make_unique<AttributionStorageSql>(
+      std::unique_ptr<AttributionResolver> storage =
+          std::make_unique<AttributionResolverImpl>(
               temp_directory_.GetPath(),
               std::make_unique<ConfigurableStorageDelegate>());
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
index 34b8086..efeed791 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
@@ -48,6 +48,7 @@
 #include "content/browser/attribution_reporting/attribution_features.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_reporting.pb.h"
+#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/sql_utils.h"
@@ -270,7 +271,7 @@
     CloseDatabase();
     auto delegate = std::make_unique<ConfigurableStorageDelegate>();
     delegate_ = delegate.get();
-    storage_ = std::make_unique<AttributionStorageSql>(
+    storage_ = std::make_unique<AttributionResolverImpl>(
         temp_directory_.GetPath(), std::move(delegate));
   }
 
@@ -304,7 +305,7 @@
     return temp_directory_.GetPath().Append(FILE_PATH_LITERAL("Conversions"));
   }
 
-  AttributionStorage* storage() { return storage_.get(); }
+  AttributionResolver* storage() { return storage_.get(); }
 
   ConfigurableStorageDelegate* delegate() { return delegate_; }
 
@@ -393,7 +394,7 @@
   base::ScopedTempDir temp_directory_;
 
  private:
-  std::unique_ptr<AttributionStorage> storage_;
+  std::unique_ptr<AttributionResolver> storage_;
   raw_ptr<ConfigurableStorageDelegate> delegate_ = nullptr;
 };
 
@@ -1085,12 +1086,12 @@
 TEST_F(AttributionStorageSqlTest, CantOpenDb_FailsSilentlyInRelease) {
   base::CreateDirectoryAndGetError(db_path(), nullptr);
 
-  auto sql_storage = std::make_unique<AttributionStorageSql>(
+  auto sql_storage = std::make_unique<AttributionResolverImpl>(
       temp_directory_.GetPath(),
       std::make_unique<ConfigurableStorageDelegate>());
   sql_storage->set_ignore_errors_for_testing(true);
 
-  std::unique_ptr<AttributionStorage> storage = std::move(sql_storage);
+  std::unique_ptr<AttributionResolver> storage = std::move(sql_storage);
 
   // These calls should be no-ops.
   storage->StoreSource(SourceBuilder().Build());
@@ -1101,8 +1102,8 @@
 
 TEST_F(AttributionStorageSqlTest, DatabaseDirDoesExist_CreateDirAndOpenDB) {
   // Give the storage layer a database directory that doesn't exist.
-  std::unique_ptr<AttributionStorage> storage =
-      std::make_unique<AttributionStorageSql>(
+  std::unique_ptr<AttributionResolver> storage =
+      std::make_unique<AttributionResolverImpl>(
           temp_directory_.GetPath().Append(
               FILE_PATH_LITERAL("ConversionFolder/")),
           std::make_unique<ConfigurableStorageDelegate>());
diff --git a/content/browser/attribution_reporting/interop/runner.cc b/content/browser/attribution_reporting/interop/runner.cc
index c7cb0a9..8977865 100644
--- a/content/browser/attribution_reporting/interop/runner.cc
+++ b/content/browser/attribution_reporting/interop/runner.cc
@@ -58,7 +58,7 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_network_sender.h"
 #include "content/browser/attribution_reporting/attribution_reporting.mojom.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h"
 #include "content/browser/attribution_reporting/attribution_suitable_context.h"
 #include "content/browser/attribution_reporting/interop/parser.h"
 #include "content/browser/storage_partition_impl.h"
@@ -163,12 +163,12 @@
   base::flat_set<base::Time> debug_cookie_set_;
 };
 
-class ControllableStorageDelegate : public AttributionStorageDelegateImpl {
+class ControllableStorageDelegate : public AttributionResolverDelegateImpl {
  public:
   explicit ControllableStorageDelegate(AttributionInteropRun& run)
-      : AttributionStorageDelegateImpl(AttributionNoiseMode::kNone,
-                                       AttributionDelayMode::kDefault,
-                                       run.config.attribution_config) {
+      : AttributionResolverDelegateImpl(AttributionNoiseMode::kNone,
+                                        AttributionDelayMode::kDefault,
+                                        run.config.attribution_config) {
     std::vector<std::pair<base::Time, RandomizedResponse>> responses;
     std::vector<std::pair<base::Time, base::flat_set<int>>>
         null_aggregatable_reports_days;
@@ -203,7 +203,7 @@
       delete;
 
  private:
-  // AttributionStorageDelegateImpl:
+  // AttributionResolverDelegateImpl:
   GetRandomizedResponseResult GetRandomizedResponse(
       const attribution_reporting::mojom::SourceType source_type,
       const attribution_reporting::TriggerSpecs& trigger_specs,
@@ -213,7 +213,7 @@
 
     ASSIGN_OR_RETURN(
         auto response_data,
-        AttributionStorageDelegateImpl::GetRandomizedResponse(
+        AttributionResolverDelegateImpl::GetRandomizedResponse(
             source_type, trigger_specs, max_event_level_reports, epsilon));
 
     auto it = randomized_responses_.find(base::Time::Now());
@@ -242,7 +242,7 @@
           source_registration_time_config) const override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    bool ret = AttributionStorageDelegateImpl::
+    bool ret = AttributionResolverDelegateImpl::
         GenerateNullAggregatableReportForLookbackDay(
             lookback_day, source_registration_time_config);
     auto it = null_aggregatable_reports_days_.find(base::Time::Now());
diff --git a/content/browser/attribution_reporting/rate_limit_table.cc b/content/browser/attribution_reporting/rate_limit_table.cc
index f25146ed2..a67695c 100644
--- a/content/browser/attribution_reporting/rate_limit_table.cc
+++ b/content/browser/attribution_reporting/rate_limit_table.cc
@@ -18,7 +18,7 @@
 #include "components/attribution_reporting/suitable_origin.h"
 #include "content/browser/attribution_reporting/attribution_config.h"
 #include "content/browser/attribution_reporting/attribution_info.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/rate_limit_result.h"
 #include "content/browser/attribution_reporting/sql_queries.h"
@@ -49,9 +49,9 @@
 
 }  // namespace
 
-RateLimitTable::RateLimitTable(const AttributionStorageDelegate* delegate)
-    : delegate_(raw_ref<const AttributionStorageDelegate>::from_ptr(delegate)) {
-}
+RateLimitTable::RateLimitTable(const AttributionResolverDelegate* delegate)
+    : delegate_(
+          raw_ref<const AttributionResolverDelegate>::from_ptr(delegate)) {}
 
 RateLimitTable::~RateLimitTable() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -635,7 +635,7 @@
   }
 }
 
-void RateLimitTable::SetDelegate(const AttributionStorageDelegate& delegate) {
+void RateLimitTable::SetDelegate(const AttributionResolverDelegate& delegate) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   delegate_ = delegate;
 }
diff --git a/content/browser/attribution_reporting/rate_limit_table.h b/content/browser/attribution_reporting/rate_limit_table.h
index 5bf26af..8405601 100644
--- a/content/browser/attribution_reporting/rate_limit_table.h
+++ b/content/browser/attribution_reporting/rate_limit_table.h
@@ -37,7 +37,7 @@
 namespace content {
 
 struct AttributionInfo;
-class AttributionStorageDelegate;
+class AttributionResolverDelegate;
 class CommonSourceInfo;
 class StorableSource;
 
@@ -70,7 +70,7 @@
     kMaxValue = kError,
   };
 
-  explicit RateLimitTable(const AttributionStorageDelegate*);
+  explicit RateLimitTable(const AttributionResolverDelegate*);
   RateLimitTable(const RateLimitTable&) = delete;
   RateLimitTable& operator=(const RateLimitTable&) = delete;
   RateLimitTable(RateLimitTable&&) = delete;
@@ -145,7 +145,7 @@
   void AppendRateLimitDataKeys(sql::Database* db,
                                std::set<AttributionDataModel::DataKey>& keys);
 
-  void SetDelegate(const AttributionStorageDelegate&);
+  void SetDelegate(const AttributionResolverDelegate&);
 
   static constexpr int64_t kUnsetReportId = -1;
 
@@ -179,7 +179,7 @@
   [[nodiscard]] bool DeleteExpiredRateLimits(sql::Database* db)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
-  raw_ref<const AttributionStorageDelegate> delegate_
+  raw_ref<const AttributionResolverDelegate> delegate_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Time at which `DeleteExpiredRateLimits()` was last called. Initialized to
diff --git a/content/browser/attribution_reporting/test/configurable_storage_delegate.cc b/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
index 6011724c..9a15f50 100644
--- a/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
+++ b/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
@@ -24,14 +24,14 @@
 #include "components/attribution_reporting/privacy_math.h"
 #include "content/browser/attribution_reporting/attribution_config.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "services/network/public/cpp/trigger_verification.h"
 
 namespace content {
 
 ConfigurableStorageDelegate::ConfigurableStorageDelegate()
-    : AttributionStorageDelegate([]() {
+    : AttributionResolverDelegate([]() {
         AttributionConfig c;
         c.max_sources_per_origin = std::numeric_limits<int>::max(),
         c.max_destinations_per_source_site_reporting_site =
@@ -98,7 +98,7 @@
   return DefaultExternalReportID();
 }
 
-std::optional<AttributionStorageDelegate::OfflineReportDelayConfig>
+std::optional<AttributionResolverDelegate::OfflineReportDelayConfig>
 ConfigurableStorageDelegate::GetOfflineReportDelayConfig() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return offline_report_delay_config_;
@@ -129,7 +129,7 @@
   return randomized_response_rate_;
 }
 
-AttributionStorageDelegate::GetRandomizedResponseResult
+AttributionResolverDelegate::GetRandomizedResponseResult
 ConfigurableStorageDelegate::GetRandomizedResponse(
     attribution_reporting::mojom::SourceType,
     const attribution_reporting::TriggerSpecs&,
diff --git a/content/browser/attribution_reporting/test/configurable_storage_delegate.h b/content/browser/attribution_reporting/test/configurable_storage_delegate.h
index 7b481bf..bd8de17 100644
--- a/content/browser/attribution_reporting/test/configurable_storage_delegate.h
+++ b/content/browser/attribution_reporting/test/configurable_storage_delegate.h
@@ -14,16 +14,16 @@
 #include "components/attribution_reporting/privacy_math.h"
 #include "content/browser/attribution_reporting/attribution_config.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_storage_delegate.h"
+#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
 
 namespace content {
 
-class ConfigurableStorageDelegate : public AttributionStorageDelegate {
+class ConfigurableStorageDelegate : public AttributionResolverDelegate {
  public:
   ConfigurableStorageDelegate();
   ~ConfigurableStorageDelegate() override;
 
-  // AttributionStorageDelegate:
+  // AttributionResolverDelegate:
   base::Time GetEventLevelReportTime(
       const attribution_reporting::EventReportWindows& event_report_windows,
       base::Time source_time,
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index 891cecd7..79eb3f9 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -7124,7 +7124,7 @@
           .destination = {"a.test",
                           "/set-header"
                           "?Supports-Loading-Mode: fenced-frame"
-                          "&Allow-Cross-Origin-Event-Reporting: true"},
+                          "&Allow-Cross-Origin-Event-Reporting: ?1"},
           .report_event_result = Step::Result::kSuccess,
       },
       {
@@ -7158,7 +7158,7 @@
           .destination = {"a.test",
                           "/set-header"
                           "?Supports-Loading-Mode: fenced-frame"
-                          "&Allow-Cross-Origin-Event-Reporting: true"},
+                          "&Allow-Cross-Origin-Event-Reporting: ?1"},
           .report_event_result = Step::Result::kSuccess,
       },
       {
@@ -7382,7 +7382,7 @@
           .destination = {"a.test",
                           "/set-header"
                           "?Supports-Loading-Mode: fenced-frame"
-                          "&Allow-Cross-Origin-Event-Reporting: true"},
+                          "&Allow-Cross-Origin-Event-Reporting: ?1"},
           .report_event_result = Step::Result::kSuccess,
       },
       {
@@ -7408,7 +7408,7 @@
           .destination = {"a.test",
                           "/set-header"
                           "?Supports-Loading-Mode: fenced-frame"
-                          "&Allow-Cross-Origin-Event-Reporting: true"},
+                          "&Allow-Cross-Origin-Event-Reporting: ?1"},
           .report_event_result = Step::Result::kSuccess,
       },
       {
diff --git a/content/browser/indexed_db/indexed_db_bucket_context.cc b/content/browser/indexed_db/indexed_db_bucket_context.cc
index 3eca4d1..ecf77bd 100644
--- a/content/browser/indexed_db/indexed_db_bucket_context.cc
+++ b/content/browser/indexed_db/indexed_db_bucket_context.cc
@@ -20,6 +20,8 @@
 #include "base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/containers/contains.h"
+#include "base/debug/crash_logging.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -1641,10 +1643,23 @@
   constexpr static const int kNumOpenTries = 2;
   for (int i = 0; i < kNumOpenTries; ++i) {
     const bool is_first_attempt = i == 0;
-    std::tie(backing_store, status, data_loss_info, disk_full) =
-        OpenAndVerifyIndexedDBBackingStore(data_path_, database_path, blob_path,
-                                           lock_manager.get(), is_first_attempt,
-                                           create_if_missing);
+    {
+      SCOPED_CRASH_KEY_STRING256("crbug/340398745", "data_path",
+                                 data_path_.AsUTF8Unsafe());
+      SCOPED_CRASH_KEY_BOOL("crbug/340398745", "first_attempt",
+                            is_first_attempt);
+      SCOPED_CRASH_KEY_BOOL(
+          "crbug/340398745", "sharding",
+          base::FeatureList::IsEnabled(features::kIndexedDBShardBackingStores));
+      SCOPED_CRASH_KEY_NUMBER("crbug/340398745", "store_open_count",
+                              backing_store_open_count_);
+
+      std::tie(backing_store, status, data_loss_info, disk_full) =
+          OpenAndVerifyIndexedDBBackingStore(
+              data_path_, database_path, blob_path, lock_manager.get(),
+              is_first_attempt, create_if_missing);
+    }
+
     if (is_first_attempt) [[likely]] {
       first_try_status = status;
     }
@@ -1711,6 +1726,7 @@
 
   lock_manager_ = std::move(lock_manager);
   backing_store_ = std::move(backing_store);
+  backing_store_open_count_++;
   backing_store_->set_bucket_context(this);
   delegate().on_files_written.Run(/*flushed=*/true);
   return {leveldb::Status::OK(), IndexedDBDatabaseError(), data_loss_info};
diff --git a/content/browser/indexed_db/indexed_db_bucket_context.h b/content/browser/indexed_db/indexed_db_bucket_context.h
index adced8fb..f7a866f3 100644
--- a/content/browser/indexed_db/indexed_db_bucket_context.h
+++ b/content/browser/indexed_db/indexed_db_bucket_context.h
@@ -509,6 +509,9 @@
   // True if there's already a task queued to call `RunTasks()`.
   bool task_run_queued_ = false;
 
+  // Debug field. TODO(crbug/340398745): remove.
+  size_t backing_store_open_count_ = 0;
+
   mojo::ReceiverSet<blink::mojom::IDBFactory, ReceiverContext> receivers_;
 
   base::WeakPtrFactory<IndexedDBBucketContext> weak_factory_{this};
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 26784f5c..425b3c0 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -455,6 +455,8 @@
                      rhs->no_vary_search_with_parse_error));
   CHECK(mojo::Equals(adjusted_lhs->observe_browsing_topics,
                      rhs->observe_browsing_topics));
+  CHECK(mojo::Equals(adjusted_lhs->allow_cross_origin_event_reporting,
+                     rhs->allow_cross_origin_event_reporting));
   NOTREACHED_IN_MIGRATION()
       << "The parsed headers don't match, but we don't know which "
          "field does not match. Please add a DCHECK before this one "
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 8eb15fe..909f2f6 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -6076,14 +6076,9 @@
   // cross-origin subframes to use their reporting metadata to send
   // `reportEvent()` beacons. The cross-origin subframes still require a
   // separate per-report opt-in.
-  {
-    std::string allow;
-    if (fenced_frame_properties_.has_value() && response() &&
-        response()->headers->GetNormalizedHeader(
-            "Allow-Cross-Origin-Event-Reporting", &allow) &&
-        allow == "true") {
-      fenced_frame_properties_->SetAllowCrossOriginEventReporting();
-    }
+  if (fenced_frame_properties_.has_value() && response_head_ &&
+      response_head_->parsed_headers->allow_cross_origin_event_reporting) {
+    fenced_frame_properties_->SetAllowCrossOriginEventReporting();
   }
 
   // Create a view of the fenced frame properties from the perspective of the
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index b5fbea93..16852fb 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -1247,6 +1247,24 @@
 
   bool ResolveSelectURLToConfig() override { return GetParam(); }
 
+  mojo_base::BigBuffer GetCodeCacheDataForUrl(RenderFrameHost* rfh,
+                                              const GURL& url) {
+    mojo::PendingRemote<blink::mojom::CodeCacheHost> pending_code_cache_host;
+    RenderFrameHostImpl::From(rfh)->CreateCodeCacheHost(
+        pending_code_cache_host.InitWithNewPipeAndPassReceiver());
+
+    mojo::Remote<blink::mojom::CodeCacheHost> inspecting_code_cache_host(
+        std::move(pending_code_cache_host));
+
+    base::test::TestFuture<base::Time, mojo_base::BigBuffer> code_cache_future;
+    inspecting_code_cache_host->FetchCachedCode(
+        blink::mojom::CodeCacheType::kJavascript, url,
+        code_cache_future.GetCallback());
+
+    auto [response_time, data] = code_cache_future.Take();
+    return std::move(data);
+  }
+
   ~SharedStorageBrowserTest() override = default;
 
  private:
@@ -1434,6 +1452,63 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
+                       AddModue_TheThirdTimeCompilesWithV8CodeCache) {
+  // The test assumes pages get deleted after navigation. To ensure this,
+  // disable back/forward cache.
+  content::DisableBackForwardCacheForTesting(
+      shell()->web_contents(),
+      content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
+
+  GURL url = https_server()->GetURL("a.test", kSimplePagePath);
+  GURL module_url = https_server()->GetURL(
+      "a.test", "/shared_storage/large_cacheable_script.js");
+
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+
+  // Initially, the code cache has no data.
+  mojo_base::BigBuffer code_cache_data0 = GetCodeCacheDataForUrl(
+      shell()->web_contents()->GetPrimaryMainFrame(), module_url);
+  EXPECT_EQ(code_cache_data0.size(), 0u);
+
+  EXPECT_TRUE(ExecJs(shell(), R"(
+      sharedStorage.worklet.addModule(
+        'shared_storage/large_cacheable_script.js');
+    )"));
+
+  mojo_base::BigBuffer code_cache_data1 = GetCodeCacheDataForUrl(
+      shell()->web_contents()->GetPrimaryMainFrame(), module_url);
+  EXPECT_GT(code_cache_data1.size(), 0u);
+
+  // After the first script loading, the code cache has some data.
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  EXPECT_TRUE(ExecJs(shell(), R"(
+      sharedStorage.worklet.addModule(
+        'shared_storage/large_cacheable_script.js');
+    )"));
+
+  // After the second script loading, the code cache has more data. This implies
+  // that the code cache wasn't used for the second compilation. This is
+  // expected, as we won't store the cached code entirely for first seen URLs.
+  mojo_base::BigBuffer code_cache_data2 = GetCodeCacheDataForUrl(
+      shell()->web_contents()->GetPrimaryMainFrame(), module_url);
+  EXPECT_GT(code_cache_data2.size(), code_cache_data1.size());
+
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  EXPECT_TRUE(ExecJs(shell(), R"(
+      sharedStorage.worklet.addModule(
+        'shared_storage/large_cacheable_script.js');
+    )"));
+
+  // After the third script loading, the code cache does not change. This
+  // implies that the code cache was used for the third compilation.
+  mojo_base::BigBuffer code_cache_data3 = GetCodeCacheDataForUrl(
+      shell()->web_contents()->GetPrimaryMainFrame(), module_url);
+  EXPECT_EQ(code_cache_data3.size(), code_cache_data2.size());
+  EXPECT_TRUE(std::equal(code_cache_data3.begin(), code_cache_data3.end(),
+                         code_cache_data2.begin()));
+}
+
 IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, RunOperation_Success) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
diff --git a/content/browser/shared_storage/shared_storage_code_cache_host_proxy.cc b/content/browser/shared_storage/shared_storage_code_cache_host_proxy.cc
new file mode 100644
index 0000000..8d29a1f0
--- /dev/null
+++ b/content/browser/shared_storage/shared_storage_code_cache_host_proxy.cc
@@ -0,0 +1,71 @@
+// 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 "content/browser/shared_storage/shared_storage_code_cache_host_proxy.h"
+
+#include "base/logging.h"
+
+namespace content {
+
+SharedStorageCodeCacheHostProxy::SharedStorageCodeCacheHostProxy(
+    mojo::PendingRemote<blink::mojom::CodeCacheHost> actual_code_cache_host,
+    mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver,
+    const GURL& script_url)
+    : actual_code_cache_host_(std::move(actual_code_cache_host)),
+      receiver_(this, std::move(receiver)),
+      script_url_(script_url) {}
+
+SharedStorageCodeCacheHostProxy::~SharedStorageCodeCacheHostProxy() = default;
+
+void SharedStorageCodeCacheHostProxy::DidGenerateCacheableMetadata(
+    blink::mojom::CodeCacheType cache_type,
+    const GURL& url,
+    base::Time expected_response_time,
+    mojo_base::BigBuffer data) {
+  // The only URL we expect to use the code cache is the worklet's script URL.
+  if (script_url_ != url) {
+    receiver_.ReportBadMessage("Unexpected request url");
+    return;
+  }
+  actual_code_cache_host_->DidGenerateCacheableMetadata(
+      cache_type, url, expected_response_time, std::move(data));
+}
+
+void SharedStorageCodeCacheHostProxy::FetchCachedCode(
+    blink::mojom::CodeCacheType cache_type,
+    const GURL& url,
+    FetchCachedCodeCallback callback) {
+  // The only URL we expect to use the code cache is the worklet's script URL.
+  if (script_url_ != url) {
+    receiver_.ReportBadMessage("Unexpected request url");
+    return;
+  }
+  actual_code_cache_host_->FetchCachedCode(cache_type, url,
+                                           std::move(callback));
+}
+
+void SharedStorageCodeCacheHostProxy::ClearCodeCacheEntry(
+    blink::mojom::CodeCacheType cache_type,
+    const GURL& url) {
+  // The only URL we expect to use the code cache is the worklet's script URL.
+  if (script_url_ != url) {
+    receiver_.ReportBadMessage("Unexpected request url");
+    return;
+  }
+  actual_code_cache_host_->ClearCodeCacheEntry(cache_type, url);
+}
+
+void SharedStorageCodeCacheHostProxy::
+    DidGenerateCacheableMetadataInCacheStorage(
+        const GURL& url,
+        base::Time expected_response_time,
+        mojo_base::BigBuffer data,
+        const std::string& cache_storage_cache_name) {
+  // CacheStorage writes require a Service Worker context, which is inapplicable
+  // here for Shared Storage Worklets. Thus, this method should never invoke.
+  receiver_.ReportBadMessage(
+      "Unexpected call of DidGenerateCacheableMetadataInCacheStorage");
+}
+
+}  // namespace content
diff --git a/content/browser/shared_storage/shared_storage_code_cache_host_proxy.h b/content/browser/shared_storage/shared_storage_code_cache_host_proxy.h
new file mode 100644
index 0000000..c6ad7d4
--- /dev/null
+++ b/content/browser/shared_storage/shared_storage_code_cache_host_proxy.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_BROWSER_SHARED_STORAGE_SHARED_STORAGE_CODE_CACHE_HOST_PROXY_H_
+#define CONTENT_BROWSER_SHARED_STORAGE_SHARED_STORAGE_CODE_CACHE_HOST_PROXY_H_
+
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Proxy CodeCacheHost, to limit the requests that a shared storage worklet can
+// make. It also implicitly adds a scope -- requests are only allowed during the
+// lifetime of `SharedStorageCodeCacheHostProxy`.
+class CONTENT_EXPORT SharedStorageCodeCacheHostProxy
+    : public blink::mojom::CodeCacheHost {
+ public:
+  explicit SharedStorageCodeCacheHostProxy(
+      mojo::PendingRemote<blink::mojom::CodeCacheHost> actual_code_cache_host,
+      mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver,
+      const GURL& script_url);
+  ~SharedStorageCodeCacheHostProxy() override;
+
+  void DidGenerateCacheableMetadata(blink::mojom::CodeCacheType cache_type,
+                                    const GURL& url,
+                                    base::Time expected_response_time,
+                                    mojo_base::BigBuffer data) override;
+
+  void FetchCachedCode(blink::mojom::CodeCacheType cache_type,
+                       const GURL& url,
+                       FetchCachedCodeCallback callback) override;
+
+  void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type,
+                           const GURL& url) override;
+
+  void DidGenerateCacheableMetadataInCacheStorage(
+      const GURL& url,
+      base::Time expected_response_time,
+      mojo_base::BigBuffer data,
+      const std::string& cache_storage_cache_name) override;
+
+ private:
+  mojo::Remote<blink::mojom::CodeCacheHost> actual_code_cache_host_;
+  mojo::Receiver<blink::mojom::CodeCacheHost> receiver_;
+  const GURL script_url_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SHARED_STORAGE_SHARED_STORAGE_CODE_CACHE_HOST_PROXY_H_
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index 94b343ff..624726c 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -26,6 +26,7 @@
 #include "content/browser/private_aggregation/private_aggregation_manager.h"
 #include "content/browser/renderer_host/page_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/browser/shared_storage/shared_storage_code_cache_host_proxy.h"
 #include "content/browser/shared_storage/shared_storage_document_service_impl.h"
 #include "content/browser/shared_storage/shared_storage_render_thread_worklet_driver.h"
 #include "content/browser/shared_storage/shared_storage_url_loader_factory_proxy.h"
@@ -1097,6 +1098,20 @@
     blink::mojom::SharedStorageDocumentService::CreateWorkletCallback callback,
     bool success,
     const std::string& error_message) {
+  // After the initial script loading, accessing shared storage will be allowed.
+  // We want to disable the communication with network and with the cache, to
+  // prevent leaking shared storage data.
+  //
+  // Note: The last code cache message (i.e. `DidGenerateCacheableMetadata()`,
+  // if any) could race with this `OnAddModuleOnWorkletFinished()` callback, as
+  // they are from separate mojom channels. It could impact the utility (i.e.
+  // the generated code is not stored when the race happens).
+  //
+  // TODO(crbug.com/341690728): Measure how often the race happens, and
+  // rearchitect if necessary.
+  url_loader_factory_proxy_.reset();
+  code_cache_host_proxy_.reset();
+
   std::move(callback).Run(success, error_message);
 
   DecrementPendingOperationsCount();
@@ -1312,11 +1327,24 @@
         document_service_->render_frame_host().IsFeatureEnabled(
             blink::mojom::PermissionsPolicyFeature::kPrivateAggregation);
 
+    mojo::PendingRemote<blink::mojom::CodeCacheHost> actual_code_cache_host;
+    static_cast<RenderFrameHostImpl&>(document_service_->render_frame_host())
+        .CreateCodeCacheHost(
+            actual_code_cache_host.InitWithNewPipeAndPassReceiver());
+
+    mojo::PendingRemote<blink::mojom::CodeCacheHost> proxied_code_cache_host;
+
+    code_cache_host_proxy_ = std::make_unique<SharedStorageCodeCacheHostProxy>(
+        std::move(actual_code_cache_host),
+        proxied_code_cache_host.InitWithNewPipeAndPassReceiver(),
+        script_source_url_);
+
     auto global_scope_creation_params =
         blink::mojom::WorkletGlobalScopeCreationParams::New(
             script_source_url_, shared_storage_origin_, origin_trial_features_,
             devtools_handle_->devtools_token(),
             devtools_handle_->BindNewPipeAndPassRemote(),
+            std::move(proxied_code_cache_host),
             devtools_handle_->wait_for_debugger());
 
     driver_->StartWorkletService(
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.h b/content/browser/shared_storage/shared_storage_worklet_host.h
index 5776768..327ddef 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.h
+++ b/content/browser/shared_storage/shared_storage_worklet_host.h
@@ -33,6 +33,7 @@
 class RenderProcessHost;
 class SharedStorageDocumentServiceImpl;
 class SharedStorageURLLoaderFactoryProxy;
+class SharedStorageCodeCacheHostProxy;
 class SharedStorageWorkletDriver;
 class SharedStorageWorkletHostManager;
 class StoragePartitionImpl;
@@ -341,6 +342,12 @@
   // application/javascript request header; to enforce same-origin mode; etc.
   std::unique_ptr<SharedStorageURLLoaderFactoryProxy> url_loader_factory_proxy_;
 
+  // The proxy is used to limit the request that the worklet can make, e.g. to
+  // ensure the URL is not modified by a compromised worklet. This is reset
+  // after the script loading finishes, to prevent leaking the shared storage
+  // data after that.
+  std::unique_ptr<SharedStorageCodeCacheHostProxy> code_cache_host_proxy_;
+
   base::WeakPtrFactory<SharedStorageWorkletHost> weak_ptr_factory_{this};
 };
 
diff --git a/content/browser/speech/speech_recognizer_impl.h b/content/browser/speech/speech_recognizer_impl.h
index 7e65764..617c279 100644
--- a/content/browser/speech/speech_recognizer_impl.h
+++ b/content/browser/speech/speech_recognizer_impl.h
@@ -10,7 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "content/browser/speech/endpointer/endpointer.h"
+#include "components/speech/endpointer/endpointer.h"
 #include "content/browser/speech/speech_recognition_engine.h"
 #include "content/browser/speech/speech_recognizer.h"
 #include "content/common/content_export.h"
@@ -172,7 +172,7 @@
 
   raw_ptr<media::AudioSystem, DanglingUntriaged> audio_system_;
   std::unique_ptr<SpeechRecognitionEngine> recognition_engine_;
-  Endpointer endpointer_;
+  speech::Endpointer endpointer_;
   scoped_refptr<media::AudioCapturerSource> audio_capturer_source_;
   int num_samples_recorded_;
   float audio_level_;
diff --git a/content/renderer/accessibility/ax_action_target_factory.cc b/content/renderer/accessibility/ax_action_target_factory.cc
index daf1bbd2..70f30d85 100644
--- a/content/renderer/accessibility/ax_action_target_factory.cc
+++ b/content/renderer/accessibility/ax_action_target_factory.cc
@@ -8,6 +8,8 @@
 #include "content/renderer/accessibility/blink_ax_action_target.h"
 #include "third_party/blink/public/web/web_ax_object.h"
 #include "third_party/blink/public/web/web_document.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
+#include "ui/accessibility/ax_node_id_forward.h"
 #include "ui/accessibility/null_ax_action_target.h"
 
 namespace content {
@@ -19,10 +21,13 @@
     content::PluginAXTreeActionTargetAdapter* plugin_tree_adapter,
     ui::AXNodeID node_id,
     ax::mojom::Role role) {
-  CHECK(node_id == -1 || role == ax::mojom::Role::kNone)
+  if (node_id == ui::kInvalidAXNodeID && role == ax::mojom::Role::kUnknown) {
+    return std::make_unique<ui::NullAXActionTarget>();
+  }
+  CHECK(node_id == ui::kInvalidAXNodeID || role == ax::mojom::Role::kUnknown)
       << "We cannot set both the `node_id` and the `role`";
   blink::WebAXObject blink_target;
-  if (role != ax::mojom::Role::kNone) {
+  if (role != ax::mojom::Role::kUnknown) {
     blink_target =
         blink::WebAXObject::FromWebDocumentFirstWithRole(document, role);
   } else {
diff --git a/content/renderer/accessibility/ax_action_target_factory.h b/content/renderer/accessibility/ax_action_target_factory.h
index fb9fcccdd..9547624 100644
--- a/content/renderer/accessibility/ax_action_target_factory.h
+++ b/content/renderer/accessibility/ax_action_target_factory.h
@@ -32,7 +32,7 @@
       const blink::WebDocument& document,
       content::PluginAXTreeActionTargetAdapter* plugin_tree_adapter,
       ui::AXNodeID node_id,
-      ax::mojom::Role role = ax::mojom::Role::kNone);
+      ax::mojom::Role role = ax::mojom::Role::kUnknown);
 };
 
 }  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index ac283ac6..3325f775f9 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2360,10 +2360,10 @@
     "../browser/attribution_reporting/attribution_manager_impl_unittest.cc",
     "../browser/attribution_reporting/attribution_report_network_sender_unittest.cc",
     "../browser/attribution_reporting/attribution_report_unittest.cc",
-    "../browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc",
+    "../browser/attribution_reporting/attribution_resolver_delegate_impl_unittest.cc",
+    "../browser/attribution_reporting/attribution_resolver_unittest.cc",
     "../browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc",
     "../browser/attribution_reporting/attribution_storage_sql_unittest.cc",
-    "../browser/attribution_reporting/attribution_storage_unittest.cc",
     "../browser/attribution_reporting/attribution_suitable_context_unittest.cc",
     "../browser/attribution_reporting/interop/interop_unittest.cc",
     "../browser/attribution_reporting/interop/parser_unittest.cc",
@@ -3339,7 +3339,6 @@
       "../browser/host_zoom_map_impl_unittest.cc",
       "../browser/picture_in_picture/document_picture_in_picture_navigation_throttle_unittest.cc",
       "../browser/serial/serial_unittest.cc",
-      "../browser/speech/endpointer/endpointer_unittest.cc",
       "../browser/speech/network_speech_recognition_engine_impl_unittest.cc",
       "../browser/speech/speech_recognizer_impl_unittest.cc",
       "../browser/tracing/tracing_ui_unittest.cc",
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index df8f928..87ceb51 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -7346,6 +7346,8 @@
 data/shared_storage/erroneous_function_module.js
 data/shared_storage/erroneous_module.js
 data/shared_storage/getter_module.js
+data/shared_storage/large_cacheable_script.js
+data/shared_storage/large_cacheable_script.js.mock-http-headers
 data/shared_storage/module_with_custom_header.js
 data/shared_storage/module_with_custom_header.js.mock-http-headers
 data/shared_storage/page-with-non-shared-storage-writable-iframe.html
diff --git a/content/test/data/shared_storage/large_cacheable_script.js b/content/test/data/shared_storage/large_cacheable_script.js
new file mode 100644
index 0000000..3af9dd2
--- /dev/null
+++ b/content/test/data/shared_storage/large_cacheable_script.js
@@ -0,0 +1,17 @@
+// 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.
+
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
+a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;a=1;
diff --git a/content/test/data/shared_storage/large_cacheable_script.js.mock-http-headers b/content/test/data/shared_storage/large_cacheable_script.js.mock-http-headers
new file mode 100644
index 0000000..beae210
--- /dev/null
+++ b/content/test/data/shared_storage/large_cacheable_script.js.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/javascript
+Cache-Control: max-age=2592000
diff --git a/content/web_test/renderer/test_runner.cc b/content/web_test/renderer/test_runner.cc
index 8fbf5061..6f475ee3 100644
--- a/content/web_test/renderer/test_runner.cc
+++ b/content/web_test/renderer/test_runner.cc
@@ -2764,6 +2764,10 @@
          web_test_runtime_flags_.is_printing();
 }
 
+bool TestRunner::IsPrinting() const {
+  return web_test_runtime_flags_.is_printing();
+}
+
 #if BUILDFLAG(ENABLE_PRINTING)
 gfx::Size TestRunner::GetPrintingPageSize(blink::WebLocalFrame* frame) const {
   const int printing_width = web_test_runtime_flags_.printing_width();
diff --git a/content/web_test/renderer/test_runner.h b/content/web_test/renderer/test_runner.h
index 3cf6d38..9b97c024 100644
--- a/content/web_test/renderer/test_runner.h
+++ b/content/web_test/renderer/test_runner.h
@@ -129,6 +129,8 @@
   // can be done locally in the renderer via DumpPixelsInRenderer().
   bool CanDumpPixelsFromRenderer() const;
 
+  bool IsPrinting() const;
+
 #if BUILDFLAG(ENABLE_PRINTING)
   // Returns the default page size to be used for printing. This is either the
   // size that was explicitly set via SetPrintingSize or the size of the frame
diff --git a/content/web_test/renderer/web_frame_test_proxy.cc b/content/web_test/renderer/web_frame_test_proxy.cc
index eec6155..f5d4a7bb 100644
--- a/content/web_test/renderer/web_frame_test_proxy.cc
+++ b/content/web_test/renderer/web_frame_test_proxy.cc
@@ -787,14 +787,24 @@
 void WebFrameTestProxy::StartTest() {
   CHECK(!should_block_parsing_in_next_commit_);
   GetWebFrame()->FlushInputForTesting(base::BindOnce(
-      [](base::WeakPtr<RenderFrameImpl> render_frame) {
-        if (!render_frame || !render_frame->GetWebFrame()) {
+      [](base::WeakPtr<RenderFrameImpl> render_frame,
+         const TestRunner* test_runner) {
+        if (!render_frame) {
           return;
         }
 
-        render_frame->GetWebFrame()->ResumeParserForTesting();
+        auto* web_frame = render_frame->GetWebFrame();
+        if (!web_frame) {
+          return;
+        }
+
+        web_frame->ResumeParserForTesting();
+
+        if (test_runner->IsPrinting()) {
+          web_frame->WillPrintSoon();
+        }
       },
-      GetWeakPtr()));
+      GetWeakPtr(), this->test_runner_));
 }
 
 blink::FrameWidgetTestHelper*
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index fa7ff8c..779a609 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -175,6 +175,11 @@
              "EnableMSAAOnNewIntelGPUs",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables the use of ANGLE validation for non-WebGL contexts.
+BASE_FEATURE(kDefaultEnableANGLEValidation,
+             "DefaultEnableANGLEValidation",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables canvas to free its resources by default when it's running in
 // the background.
 BASE_FEATURE(kCanvasContextLostInBackground,
@@ -573,6 +578,11 @@
   return IsDrDcEnabled() || IsUsingThreadSafeMediaForWebView();
 }
 
+bool IsANGLEValidationEnabled() {
+  return base::FeatureList::IsEnabled(kDefaultEnableANGLEValidation) &&
+         UsePassthroughCommandDecoder();
+}
+
 namespace {
 bool IsSkiaGraphiteSupportedByDevice(const base::CommandLine* command_line) {
 #if BUILDFLAG(IS_APPLE)
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index e7fca776..e4a49f7 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -43,6 +43,8 @@
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kEnableMSAAOnNewIntelGPUs);
 
+GPU_EXPORT BASE_DECLARE_FEATURE(kDefaultEnableANGLEValidation);
+
 GPU_EXPORT BASE_DECLARE_FEATURE(kCanvasContextLostInBackground);
 
 #if BUILDFLAG(IS_WIN)
@@ -116,6 +118,7 @@
 GPU_EXPORT bool IsUsingVulkan();
 GPU_EXPORT bool IsDrDcEnabled();
 GPU_EXPORT bool NeedThreadSafeAndroidMedia();
+GPU_EXPORT bool IsANGLEValidationEnabled();
 GPU_EXPORT bool IsSkiaGraphiteEnabled(const base::CommandLine* command_line);
 GPU_EXPORT bool EnablePurgeGpuImageDecodeCache();
 GPU_EXPORT bool EnablePruneOldTransferCacheEntries();
diff --git a/gpu/ipc/service/gpu_channel_manager.cc b/gpu/ipc/service/gpu_channel_manager.cc
index 4b28124..271b10140 100644
--- a/gpu/ipc/service/gpu_channel_manager.cc
+++ b/gpu/ipc/service/gpu_channel_manager.cc
@@ -914,6 +914,10 @@
       gpu_driver_bug_workarounds_.use_virtualized_gl_contexts;
 
   bool enable_angle_validation = features::IsANGLEValidationEnabled();
+#if DCHECK_IS_ON()
+  // Force validation on for all debug builds and testing
+  enable_angle_validation = true;
+#endif
 
   scoped_refptr<gl::GLShareGroup> share_group;
   bool use_passthrough_decoder = use_passthrough_cmd_decoder();
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 7a12c5a..89a195a 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -267,16 +267,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 127.0.6490.0',
+    'description': 'Run with ash-chrome version 127.0.6491.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6490.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v127.0.6490.0',
-          'revision': 'version:127.0.6490.0',
+          'location': 'lacros_version_skew_tests_v127.0.6491.0',
+          'revision': 'version:127.0.6491.0',
         },
       ],
     },
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 5f4eadb5..c32a163 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6490.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 127.0.6490.0",
+    "description": "Run with ash-chrome version 127.0.6491.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_v127.0.6490.0",
-          "revision": "version:127.0.6490.0"
+          "location": "lacros_version_skew_tests_v127.0.6491.0",
+          "revision": "version:127.0.6491.0"
         }
       ]
     }
diff --git a/internal b/internal
index 60b76f3..1ec6eaf 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 60b76f3502c88409b8190bc66362666ffe6dce81
+Subproject commit 1ec6eaf1463ef817385ef7fd9014c5e74ce4b91b
diff --git a/ios/chrome/browser/parcel_tracking/BUILD.gn b/ios/chrome/browser/parcel_tracking/BUILD.gn
index b5aedf8..3706932 100644
--- a/ios/chrome/browser/parcel_tracking/BUILD.gn
+++ b/ios/chrome/browser/parcel_tracking/BUILD.gn
@@ -44,6 +44,10 @@
   sources = [ "parcel_tracking_step.h" ]
 }
 
+source_set("opt_in_status") {
+  sources = [ "parcel_tracking_opt_in_status.h" ]
+}
+
 source_set("prefs") {
   sources = [
     "parcel_tracking_prefs.h",
@@ -62,6 +66,7 @@
   ]
   deps = [
     ":metrics",
+    ":opt_in_status",
     ":prefs",
     ":tracking_source",
     "//base",
diff --git a/ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h b/ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h
new file mode 100644
index 0000000..95eaa80
--- /dev/null
+++ b/ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h
@@ -0,0 +1,17 @@
+// 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 IOS_CHROME_BROWSER_PARCEL_TRACKING_PARCEL_TRACKING_OPT_IN_STATUS_H_
+#define IOS_CHROME_BROWSER_PARCEL_TRACKING_PARCEL_TRACKING_OPT_IN_STATUS_H_
+
+// Enum for the different values of the parcel tracking opt-in status.
+enum class IOSParcelTrackingOptInStatus {
+  kNeverTrack = 0,
+  kAlwaysTrack = 1,
+  kAskToTrack = 2,
+  kStatusNotSet = 3,
+  kMaxValue = kStatusNotSet,
+};
+
+#endif  // IOS_CHROME_BROWSER_PARCEL_TRACKING_PARCEL_TRACKING_OPT_IN_STATUS_H_
diff --git a/ios/chrome/browser/parcel_tracking/parcel_tracking_util.h b/ios/chrome/browser/parcel_tracking/parcel_tracking_util.h
index 8dcea2f6..b2d15f8f2 100644
--- a/ios/chrome/browser/parcel_tracking/parcel_tracking_util.h
+++ b/ios/chrome/browser/parcel_tracking/parcel_tracking_util.h
@@ -17,13 +17,6 @@
 
 @protocol ParcelTrackingOptInCommands;
 
-// Enum for the different values of the parcel tracking opt-in status.
-enum class IOSParcelTrackingOptInStatus {
-  kNeverTrack = 0,
-  kAlwaysTrack = 1,
-  kAskToTrack = 2,
-};
-
 // Returns true if the parcel tracking feature is enabled. This returns true if
 // 1) the policy is not disabled for enterprise users and 2) the user's
 // permanent location is in the US.
diff --git a/ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.mm b/ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.mm
index 98bb123..4239d4a2 100644
--- a/ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.mm
+++ b/ios/chrome/browser/safety_check/model/ios_chrome_safety_check_manager.mm
@@ -503,7 +503,7 @@
       break;
   }
 
-  // If the Omaha response isn't recieved after `kOmahaNetworkWaitTime`,
+  // If the Omaha response isn't received after `kOmahaNetworkWaitTime`,
   // consider this an Omaha failure.
   task_runner_->PostDelayedTask(
       FROM_HERE,
diff --git a/ios/chrome/browser/shared/model/prefs/BUILD.gn b/ios/chrome/browser/shared/model/prefs/BUILD.gn
index 93173085..f989cd2d 100644
--- a/ios/chrome/browser/shared/model/prefs/BUILD.gn
+++ b/ios/chrome/browser/shared/model/prefs/BUILD.gn
@@ -89,6 +89,7 @@
     "//ios/chrome/browser/net/model",
     "//ios/chrome/browser/ntp/model:set_up_list_prefs",
     "//ios/chrome/browser/ntp_tiles/model/tab_resumption:tab_resumption_prefs",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:prefs",
     "//ios/chrome/browser/photos/model:policy",
     "//ios/chrome/browser/policy/model:policy_util",
diff --git a/ios/chrome/browser/shared/model/prefs/DEPS b/ios/chrome/browser/shared/model/prefs/DEPS
index 0247a0b..5ebd1ed 100644
--- a/ios/chrome/browser/shared/model/prefs/DEPS
+++ b/ios/chrome/browser/shared/model/prefs/DEPS
@@ -15,6 +15,7 @@
   "+ios/chrome/browser/photos/model/photos_policy.h",
   "+ios/chrome/browser/drive/model/drive_policy.h",
   "+ios/chrome/browser/web/model/annotations/annotations_util.h",
+  "+ios/chrome/browser/parcel_tracking",
 ]
 
 specific_include_rules = {
diff --git a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
index 172035fe..4b6bd388 100644
--- a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
@@ -79,6 +79,7 @@
 #import "ios/chrome/browser/metrics/model/ios_chrome_metrics_service_client.h"
 #import "ios/chrome/browser/ntp/model/set_up_list_prefs.h"
 #import "ios/chrome/browser/ntp_tiles/model/tab_resumption/tab_resumption_prefs.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/parcel_tracking/parcel_tracking_prefs.h"
 #import "ios/chrome/browser/photos/model/photos_policy.h"
 #import "ios/chrome/browser/policy/model/policy_util.h"
@@ -716,7 +717,9 @@
   // Preferences related to parcel tracking.
   registry->RegisterBooleanPref(
       prefs::kIosParcelTrackingOptInPromptDisplayLimitMet, false);
-  registry->RegisterIntegerPref(prefs::kIosParcelTrackingOptInStatus, 2);
+  registry->RegisterIntegerPref(
+      prefs::kIosParcelTrackingOptInStatus,
+      static_cast<int>(IOSParcelTrackingOptInStatus::kStatusNotSet));
   registry->RegisterBooleanPref(prefs::kIosParcelTrackingOptInPromptSwipedDown,
                                 false);
 
diff --git a/ios/chrome/browser/shared/ui/util/property_animator_group.h b/ios/chrome/browser/shared/ui/util/property_animator_group.h
index 7d878e85..3d4fe16 100644
--- a/ios/chrome/browser/shared/ui/util/property_animator_group.h
+++ b/ios/chrome/browser/shared/ui/util/property_animator_group.h
@@ -13,7 +13,7 @@
 // animators in a group. Since instances of this class conform to that protocol,
 // in most cases an animator group can be used where a single property animator
 // would be (provided that the call sites want a id<UIViewImplicitlyAnimating>).
-// Protocol methods that mutate the reciever are sequentially appiled to all of
+// Protocol methods that mutate the receiver are sequentially appiled to all of
 // the animators in the group (this include -startAnimation, -stopAnimation:,
 // and so on). -addAnimations:, -addCompletion:, and similar methods are just
 // applied to the first animator in the group. Methods (and property getters)
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index 2ad6d1d6..c513934 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -86,6 +86,7 @@
     "//ios/chrome/browser/overlays/model/public/web_content_area",
     "//ios/chrome/browser/overscroll_actions/model",
     "//ios/chrome/browser/parcel_tracking:infobar_delegate",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:parcel_tracking_step",
     "//ios/chrome/browser/parcel_tracking:prefs",
     "//ios/chrome/browser/parcel_tracking:tracking_source",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index de8da10..19e025e 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -55,6 +55,7 @@
 #import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/overscroll_actions/model/overscroll_actions_tab_helper.h"
 #import "ios/chrome/browser/parcel_tracking/parcel_tracking_infobar_delegate.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/parcel_tracking/parcel_tracking_step.h"
 #import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
 #import "ios/chrome/browser/parcel_tracking/tracking_source.h"
@@ -2701,6 +2702,7 @@
                                                      kAskedToTrackPackage];
       break;
     case IOSParcelTrackingOptInStatus::kNeverTrack:
+    case IOSParcelTrackingOptInStatus::kStatusNotSet:
       // Do not display infobar.
       break;
   }
diff --git a/ios/chrome/browser/ui/parcel_tracking/BUILD.gn b/ios/chrome/browser/ui/parcel_tracking/BUILD.gn
index 70a939d..8464b6d 100644
--- a/ios/chrome/browser/ui/parcel_tracking/BUILD.gn
+++ b/ios/chrome/browser/ui/parcel_tracking/BUILD.gn
@@ -16,6 +16,7 @@
     "//components/prefs",
     "//ios/chrome/browser/commerce/model:shopping_service",
     "//ios/chrome/browser/parcel_tracking:metrics",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:parcel_tracking_step",
     "//ios/chrome/browser/parcel_tracking:tracking_source",
     "//ios/chrome/browser/parcel_tracking:util",
@@ -39,7 +40,7 @@
   deps = [
     "//base",
     "//ios/chrome/app/strings",
-    "//ios/chrome/browser/parcel_tracking:util",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/ui/parcel_tracking/resources",
@@ -61,6 +62,7 @@
     "//base/test:test_support",
     "//components/prefs",
     "//ios/chrome/browser/parcel_tracking:metrics",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:util",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
     "//ios/chrome/browser/shared/model/browser_state:test_support",
diff --git a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator.mm b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator.mm
index 4622b73..f40194d0 100644
--- a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator.mm
+++ b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator.mm
@@ -8,7 +8,7 @@
 #import "base/metrics/histogram_functions.h"
 #import "components/prefs/pref_service.h"
 #import "ios/chrome/browser/parcel_tracking/metrics.h"
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
diff --git a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator_unittest.mm b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator_unittest.mm
index 28a704c..a15e69b1 100644
--- a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_coordinator_unittest.mm
@@ -9,7 +9,7 @@
 #import "base/test/metrics/histogram_tester.h"
 #import "components/prefs/pref_service.h"
 #import "ios/chrome/browser/parcel_tracking/metrics.h"
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
diff --git a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
index 042f5ab..bb9c058 100644
--- a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
+++ b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.h"
 
 #import "base/notreached.h"
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/ui/symbols/symbols.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
 #import "ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller_delegate.h"
@@ -115,6 +115,7 @@
       [self.delegate askToTrackTapped];
       break;
     case IOSParcelTrackingOptInStatus::kNeverTrack:
+    case IOSParcelTrackingOptInStatus::kStatusNotSet:
       NOTREACHED_IN_MIGRATION();
       break;
   }
diff --git a/ios/chrome/browser/ui/settings/google_services/BUILD.gn b/ios/chrome/browser/ui/settings/google_services/BUILD.gn
index 93eeeb4..c59c066 100644
--- a/ios/chrome/browser/ui/settings/google_services/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/google_services/BUILD.gn
@@ -51,6 +51,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/commerce/model:model",
     "//ios/chrome/browser/net/model:crurl",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:util",
     "//ios/chrome/browser/policy/model:policy_util",
     "//ios/chrome/browser/search_engines/model:template_url_service_factory",
@@ -119,6 +120,7 @@
     "//components/prefs",
     "//components/prefs/ios",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/parcel_tracking:util",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/model/browser",
@@ -280,7 +282,7 @@
     "//components/variations:test_support",
     "//google_apis",
     "//ios/chrome/app/strings",
-    "//ios/chrome/browser/parcel_tracking:util",
+    "//ios/chrome/browser/parcel_tracking:opt_in_status",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
     "//ios/chrome/browser/shared/model/browser_state:test_support",
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index fdc1240b..41901e3e 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -15,6 +15,7 @@
 #import "components/supervised_user/core/browser/supervised_user_preferences.h"
 #import "components/sync/service/sync_service.h"
 #import "components/unified_consent/pref_names.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
 #import "ios/chrome/browser/policy/model/policy_util.h"
 #import "ios/chrome/browser/settings/model/sync/utils/sync_util.h"
@@ -468,6 +469,7 @@
             IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTO_TRACK_PACKAGES_ALL);
         break;
       case IOSParcelTrackingOptInStatus::kAskToTrack:
+      case IOSParcelTrackingOptInStatus::kStatusNotSet:
         currentOptInStatusString = l10n_util::GetNSString(
             IDS_IOS_PARCEL_TRACKING_OPT_IN_TERTIARY_ACTION);
         break;
diff --git a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator.mm
index 80de38b..d6cc90f 100644
--- a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator.mm
@@ -8,7 +8,7 @@
 #import "components/prefs/pref_change_registrar.h"
 #import "components/prefs/pref_member.h"
 #import "components/prefs/pref_service.h"
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_model_consumer.h"
 
diff --git a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator_unittest.mm b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator_unittest.mm
index 3a16a63f..ea26e715 100644
--- a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator_unittest.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_mediator.h"
 
 #import "components/sync_preferences/testing_pref_service_syncable.h"
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/shared/ui/list_model/list_model.h"
@@ -56,7 +56,8 @@
 TEST_F(ParcelTrackingSettingsMediatorUnittest, TestLoadModel) {
   mediator_.consumer = consumer_;
 
-  EXPECT_EQ(consumer_.latestStatus, IOSParcelTrackingOptInStatus::kAskToTrack);
+  EXPECT_EQ(consumer_.latestStatus,
+            IOSParcelTrackingOptInStatus::kStatusNotSet);
 
   [mediator_ disconnect];
 }
diff --git a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.mm
index 4344b89..5cd9860 100644
--- a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.mm
@@ -4,7 +4,8 @@
 
 #import "ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.h"
 
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "base/notreached.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/ui/list_model/list_model.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.h"
@@ -131,6 +132,15 @@
     UITableViewCellAccessoryType desiredType =
         itemSetting == newState ? UITableViewCellAccessoryCheckmark
                                 : UITableViewCellAccessoryNone;
+
+    // If the status is not explicitly set (default), then "Ask To Track" should
+    // be selected. kStatusNotSet and kAskToTrack have the same behavior and are
+    // only differentiated for metrics.
+    if (newState == IOSParcelTrackingOptInStatus::kStatusNotSet &&
+        itemSetting == IOSParcelTrackingOptInStatus::kAskToTrack) {
+      desiredType = UITableViewCellAccessoryCheckmark;
+    }
+
     if (item.accessoryType != desiredType) {
       item.accessoryType = desiredType;
       [modifiedItems addObject:item];
diff --git a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller_unittest.mm
index e4a32bd..f527ffb 100644
--- a/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller_unittest.mm
@@ -4,7 +4,7 @@
 
 #import "ios/chrome/browser/ui/settings/google_services/parcel_tracking_settings_view_controller.h"
 
-#import "ios/chrome/browser/parcel_tracking/parcel_tracking_util.h"
+#import "ios/chrome/browser/parcel_tracking/parcel_tracking_opt_in_status.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.h"
 #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
 #import "testing/platform_test.h"
diff --git a/ios/chrome/browser/ui/sharing/share_download_overlay_coordinator.mm b/ios/chrome/browser/ui/sharing/share_download_overlay_coordinator.mm
index 1bfb80ca..caf4a88e 100644
--- a/ios/chrome/browser/ui/sharing/share_download_overlay_coordinator.mm
+++ b/ios/chrome/browser/ui/sharing/share_download_overlay_coordinator.mm
@@ -19,7 +19,7 @@
 }  // namespace
 
 @interface ShareDownloadOverlayCoordinator () {
-  // Web state that will recieve the overlay view.
+  // Web state that will receive the overlay view.
   raw_ptr<web::WebState> _webState;
 }
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/transitions/legacy_grid_transition_layout.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/transitions/legacy_grid_transition_layout.h
index 98f59333..0f5ff58 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/transitions/legacy_grid_transition_layout.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/transitions/legacy_grid_transition_layout.h
@@ -86,7 +86,7 @@
                       center:(CGPoint)center
                         size:(CGSize)size;
 
-// Populate the `cell` view of the reciever by extracting snapshots from `view`,
+// Populate the `cell` view of the receiver by extracting snapshots from `view`,
 // using `rect` to define (in `view`'s coordinates) the main tab view, with any
 // space above and below `rect` being the top and bottom tab views.
 - (void)populateWithSnapshotsFromView:(UIView*)view middleRect:(CGRect)rect;
diff --git a/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.h b/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.h
index 82c146e..64159c1 100644
--- a/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.h
+++ b/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.h
@@ -17,7 +17,7 @@
 
 // Starts a New Tab animation in `parentView`, from `originPoint` with
 // a `completion` block. The new tab will scale up and move from the direction
-// if `originPoint` to the center of the reciever. `originPoint` must be in
+// if `originPoint` to the center of the receiver. `originPoint` must be in
 // UIWindow coordinates.
 - (void)animateFrom:(CGPoint)originPoint withCompletion:(void (^)())completion;
 
diff --git a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h
index c82c663d..be2d28c7 100644
--- a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h
+++ b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h
@@ -156,7 +156,7 @@
   // A PendingReceiver for `url_loader_factory_`, used during service
   // initialization.
   mojo::PendingReceiver<network::mojom::URLLoaderFactory>
-      url_loader_factory_pending_reciever_;
+      url_loader_factory_pending_receiver_;
 
   // A SharedURLLoaderFactory that wraps `url_loader_factory_`.
   scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
diff --git a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
index 45dd42a6..70e8c426 100644
--- a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
+++ b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
@@ -59,7 +59,7 @@
 }  // namespace
 
 SafeBrowsingServiceImpl::SafeBrowsingServiceImpl() {
-  url_loader_factory_pending_reciever_ =
+  url_loader_factory_pending_receiver_ =
       url_loader_factory_.BindNewPipeAndPassReceiver();
   shared_url_loader_factory_ =
       base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
@@ -103,7 +103,7 @@
   url_loader_factory_params->process_id = network::mojom::kBrowserProcessId;
   url_loader_factory_params->is_orb_enabled = false;
   network_context_client_->CreateURLLoaderFactory(
-      std::move(url_loader_factory_pending_reciever_),
+      std::move(url_loader_factory_pending_receiver_),
       std::move(url_loader_factory_params));
 
   // Watch for changes to the Safe Browsing opt-out preference.
diff --git a/ios/web/js_messaging/web_view_js_utils_unittest.mm b/ios/web/js_messaging/web_view_js_utils_unittest.mm
index 96bdedd..f5512fa 100644
--- a/ios/web/js_messaging/web_view_js_utils_unittest.mm
+++ b/ios/web/js_messaging/web_view_js_utils_unittest.mm
@@ -34,7 +34,7 @@
 
 // Returns the WKFrameInfo instance for the main frame of `web_view`.
 WKFrameInfo* GetMainFrameWKFrameInfo(WKWebView* web_view) {
-  // Setup a message handler and recieve a message to obtain a WKFrameInfo
+  // Setup a message handler and receive a message to obtain a WKFrameInfo
   // instance.
   CRWFakeScriptMessageHandler* script_message_handler =
       [[CRWFakeScriptMessageHandler alloc] init];
diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc
index 5f0d608..434cc62 100644
--- a/media/gpu/av1_decoder.cc
+++ b/media/gpu/av1_decoder.cc
@@ -5,14 +5,15 @@
 #include "media/gpu/av1_decoder.h"
 
 #include <bitset>
+#include <utility>
 
-#include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/ranges/algorithm.h"
 #include "media/base/limits.h"
 #include "media/base/media_switches.h"
 #include "media/gpu/av1_picture.h"
+#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 #include "third_party/libgav1/src/src/decoder_state.h"
 #include "third_party/libgav1/src/src/gav1/status_code.h"
 #include "third_party/libgav1/src/src/utils/constants.h"
@@ -250,15 +251,14 @@
     return kRanOutOfStreamData;
   }
   while (parser_->HasData() || current_frame_header_) {
-    base::ScopedClosureRunner clear_current_frame(
-        base::BindOnce(&AV1Decoder::ClearCurrentFrame, base::Unretained(this)));
+    absl::Cleanup clear_current_frame = [this] { ClearCurrentFrame(); };
     if (pending_pic_) {
       const AV1Accelerator::Status status = DecodeAndOutputPicture(
           std::move(pending_pic_), parser_->tile_buffers());
       if (status == AV1Accelerator::Status::kFail)
         return kDecodeError;
       if (status == AV1Accelerator::Status::kTryAgain) {
-        clear_current_frame.ReplaceClosure(base::DoNothing());
+        std::move(clear_current_frame).Cancel();
         return kTryAgain;
       }
       // Continue so that we force |clear_current_frame| to run before moving
@@ -368,7 +368,7 @@
           profile_ = new_profile;
           bit_depth_ = new_bit_depth;
           picture_color_space_ = new_color_space;
-          clear_current_frame.ReplaceClosure(base::DoNothing());
+          std::move(clear_current_frame).Cancel();
           return kConfigChange;
         }
       }
@@ -480,7 +480,7 @@
                               : accelerator_->CreateAV1Picture(
                                     frame_header.film_grain_params.apply_grain);
     if (!pic) {
-      clear_current_frame.ReplaceClosure(base::DoNothing());
+      std::move(clear_current_frame).Cancel();
       return kRanOutOfSurfaces;
     }
 
@@ -501,7 +501,7 @@
     if (status == AV1Accelerator::Status::kFail)
       return kDecodeError;
     if (status == AV1Accelerator::Status::kTryAgain) {
-      clear_current_frame.ReplaceClosure(base::DoNothing());
+      std::move(clear_current_frame).Cancel();
       return kTryAgain;
     }
   }
diff --git a/media/gpu/v4l2/stateless/v4l2_stateless_video_decoder.cc b/media/gpu/v4l2/stateless/v4l2_stateless_video_decoder.cc
index 4c7bded..1489ace 100644
--- a/media/gpu/v4l2/stateless/v4l2_stateless_video_decoder.cc
+++ b/media/gpu/v4l2/stateless/v4l2_stateless_video_decoder.cc
@@ -24,6 +24,7 @@
 #include "media/gpu/v4l2/stateless/vp8_delegate.h"
 #include "media/gpu/v4l2/stateless/vp9_delegate.h"
 #include "media/gpu/v4l2/v4l2_status.h"
+#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 
 // Logging for the decoder is following this convention:
 //
@@ -242,10 +243,11 @@
 
   // In order to preserve the order of the callbacks between Decode() and
   // Reset(), we also trampoline |reset_cb|.
-  base::ScopedClosureRunner scoped_trampoline_reset_cb(
-      base::BindOnce(base::IgnoreResult(&base::SequencedTaskRunner::PostTask),
-                     base::SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
-                     std::move(reset_cb)));
+  absl::Cleanup scoped_trampoline_reset_cb =
+      [reset_cb = std::move(reset_cb)]() mutable {
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, std::move(reset_cb));
+      };
 
   decoder_->Reset();
 
diff --git a/media/gpu/v4l2/v4l2_stateful_video_decoder.cc b/media/gpu/v4l2/v4l2_stateful_video_decoder.cc
index b3b2813e..8d9d1a51 100644
--- a/media/gpu/v4l2/v4l2_stateful_video_decoder.cc
+++ b/media/gpu/v4l2/v4l2_stateful_video_decoder.cc
@@ -13,7 +13,6 @@
 #include "base/containers/contains.h"
 #include "base/containers/heap_array.h"
 #include "base/files/file_util.h"
-#include "base/functional/callback_helpers.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
@@ -30,6 +29,7 @@
 #include "media/gpu/v4l2/v4l2_queue.h"
 #include "media/gpu/v4l2/v4l2_utils.h"
 #include "media/video/h264_parser.h"
+#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace {
@@ -532,10 +532,11 @@
 
   // In order to preserve the order of the callbacks between Decode() and
   // Reset(), we also trampoline |closure|.
-  base::ScopedClosureRunner scoped_trampoline_reset_cb(
-      base::BindOnce(base::IgnoreResult(&base::SequencedTaskRunner::PostTask),
-                     base::SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
-                     std::move(closure)));
+  absl::Cleanup scoped_trampoline_reset = [closure =
+                                               std::move(closure)]() mutable {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, std::move(closure));
+  };
 
   // Invalidate pointers from and cancel all hypothetical in-flight requests
   // to the WaitOnceForEvents() routine.
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
index 1e4aad8..b692a37 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
@@ -10,7 +10,6 @@
 #include <utility>
 
 #include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
@@ -31,6 +30,7 @@
 #include "media/gpu/vaapi/vaapi_jpeg_encoder.h"
 #include "media/gpu/vaapi/vaapi_utils.h"
 #include "media/parsers/jpeg_parser.h"
+#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 
 namespace media {
 
@@ -316,8 +316,9 @@
     notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
     return;
   }
-  base::ScopedClosureRunner output_gmb_buffer_unmapper(base::BindOnce(
-      &gfx::GpuMemoryBuffer::Unmap, base::Unretained(output_gmb_buffer.get())));
+  absl::Cleanup output_gmb_buffer_unmapper = [&output_gmb_buffer] {
+    output_gmb_buffer->Unmap();
+  };
 
   // Get the encoded output. DownloadFromVABuffer() is a blocking call. It
   // would wait until encoding is finished.
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 339067b..66a3fe6 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -56,6 +56,7 @@
 // Auto-generated for dlopen libva libraries
 #include "media/gpu/vaapi/va_stubs.h"
 #include "media/media_buildflags.h"
+#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 #include "third_party/libva_protected_content/va_protected_content.h"
 #include "third_party/libyuv/include/libyuv.h"
 #include "ui/gfx/buffer_format_util.h"
@@ -1170,16 +1171,15 @@
       vaCreateConfig(va_display, va_profile, entrypoint, &required_attribs[0],
                      required_attribs.size(), &va_config_id);
   VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false);
-  base::ScopedClosureRunner vaconfig_destroyer(base::BindOnce(
-      [](VADisplay display, VAConfigID id) {
-        if (id != VA_INVALID_ID) {
-          VAStatus va_res = vaDestroyConfig(display, id);
-          if (va_res != VA_STATUS_SUCCESS)
-            LOG(ERROR) << "vaDestroyConfig failed. VA error: "
-                       << vaErrorStr(va_res);
-        }
-      },
-      va_display, va_config_id));
+  absl::Cleanup vaconfig_destroyer = [va_display, va_config_id] {
+    if (va_config_id != VA_INVALID_ID) {
+      VAStatus va_res = vaDestroyConfig(va_display, va_config_id);
+      if (va_res != VA_STATUS_SUCCESS) {
+        LOG(ERROR) << "vaDestroyConfig failed. VA error: "
+                   << vaErrorStr(va_res);
+      }
+    }
+  };
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // Nothing further to query for protected profile.
@@ -1271,16 +1271,15 @@
   va_res = vaCreateConfig(va_display, va_profile, entrypoint, nullptr, 0,
                           &va_config_id);
   VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false);
-  base::ScopedClosureRunner vaconfig_no_attribs_destroyer(base::BindOnce(
-      [](VADisplay display, VAConfigID id) {
-        if (id != VA_INVALID_ID) {
-          VAStatus va_res = vaDestroyConfig(display, id);
-          if (va_res != VA_STATUS_SUCCESS)
-            LOG(ERROR) << "vaDestroyConfig failed. VA error: "
-                       << vaErrorStr(va_res);
-        }
-      },
-      va_display, va_config_id));
+  absl::Cleanup vaconfig_no_attribs_destroyer = [va_display, va_config_id] {
+    if (va_config_id != VA_INVALID_ID) {
+      VAStatus va_res = vaDestroyConfig(va_display, va_config_id);
+      if (va_res != VA_STATUS_SUCCESS) {
+        LOG(ERROR) << "vaDestroyConfig failed. VA error: "
+                   << vaErrorStr(va_res);
+      }
+    }
+  };
   profile_info->supported_internal_formats = {};
   size_t max_num_config_attributes;
   if (!base::CheckedNumeric<int>(vaMaxNumConfigAttributes(va_display))
@@ -1319,7 +1318,7 @@
   return is_any_profile_supported;
 }
 
-void DestroyVAImage(VADisplay va_display, VAImage image) {
+void DestroyVAImage(VADisplay va_display, const VAImage& image) {
   if (image.image_id != VA_INVALID_ID)
     vaDestroyImage(va_display, image.image_id);
 }
@@ -1573,13 +1572,11 @@
   }
 
   const VADisplay va_display = vaGetDisplayDRM(drm_fd_.get());
-  base::ScopedClosureRunner va_display_cleaner_cb(base::BindOnce(
-      [](VADisplay va_display) {
-        if (vaDisplayIsValid(va_display)) {
-          vaTerminate(va_display);
-        }
-      },
-      va_display));
+  absl::Cleanup va_display_cleaner_cb = [va_display] {
+    if (vaDisplayIsValid(va_display)) {
+      vaTerminate(va_display);
+    }
+  };
 
   if (!vaDisplayIsValid(va_display)) {
     LOG(ERROR) << "Could not get a valid VA display";
@@ -1632,7 +1629,7 @@
     return false;
   }
 
-  std::ignore = va_display_cleaner_cb.Release();
+  std::move(va_display_cleaner_cb).Cancel();
   refcount_ = 1;
   va_display_ = va_display;
   implementation_type_ = implementation_type;
@@ -2814,8 +2811,11 @@
     VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateImage, false);
     needs_va_put_image = true;
   }
-  base::ScopedClosureRunner vaimage_deleter(
-      base::BindOnce(&DestroyVAImage, va_display_, image));
+  absl::Cleanup vaimage_deleter =
+      [this, &image]() EXCLUSIVE_LOCKS_REQUIRED(va_lock_.get()) {
+        VAAPI_CHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+        DestroyVAImage(va_display_, image);
+      };
 
   if (image.format.fourcc != VA_FOURCC_NV12) {
     LOG(ERROR) << "Unsupported image format: " << image.format.fourcc;
@@ -3121,20 +3121,22 @@
   }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  base::ScopedClosureRunner protected_session_detacher;
   if (va_protected_session_id != VA_INVALID_ID) {
     const VAStatus va_res = vaAttachProtectedSession(
         va_display_, va_context_id_, va_protected_session_id);
     VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAAttachProtectedSession,
                          false);
-    // Note that we use a lambda expression to wrap vaDetachProtectedSession()
-    // because the function in |protected_session_detacher| must return void.
-    protected_session_detacher.ReplaceClosure(base::BindOnce(
-        [](VADisplay va_display, VAContextID va_context_id) {
-          vaDetachProtectedSession(va_display, va_context_id);
-        },
-        va_display_, va_context_id_));
   }
+
+  absl::Cleanup protected_session_detacher =
+      [va_protected_session_id, this]()
+          EXCLUSIVE_LOCKS_REQUIRED(va_lock_.get()) {
+            if (va_protected_session_id == VA_INVALID_ID) {
+              return;
+            }
+            VAAPI_CHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+            vaDetachProtectedSession(va_display_, va_context_id_);
+          };
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   TRACE_EVENT2("media,gpu", "VaapiWrapper::BlitSurface", "src_rect",
@@ -3504,8 +3506,11 @@
   MAYBE_ASSERT_ACQUIRED(va_lock_);
 
   DCHECK(IsValidVABufferType(va_buffer.type));
-  base::ScopedClosureRunner pending_buffers_destroyer_on_failure(base::BindOnce(
-      &VaapiWrapper::DestroyPendingBuffers_Locked, base::Unretained(this)));
+  absl::Cleanup pending_buffers_destroyer_on_failure =
+      [this]() EXCLUSIVE_LOCKS_REQUIRED(va_lock_.get()) {
+        VAAPI_CHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+        DestroyPendingBuffers_Locked();
+      };
   unsigned int va_buffer_size;
   // We use a null |va_buffer|.data for testing: it signals that we want this
   // SubmitBuffer_Locked() call to fail.
@@ -3529,7 +3534,7 @@
   }
 
   pending_va_buffers_.push_back(buffer_id);
-  pending_buffers_destroyer_on_failure.ReplaceClosure(base::DoNothing());
+  std::move(pending_buffers_destroyer_on_failure).Cancel();
   return true;
 }
 
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index 64fee2cc..0d880bc 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -326,6 +326,7 @@
         auth_handler_factory_(HttpAuthHandlerFactory::CreateDefault()),
         http_server_properties_(std::make_unique<HttpServerProperties>()),
         ssl_data_(ASYNC, OK) {
+    SetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5, true);
     if (GetParam().priority_header_enabled) {
       feature_list_.InitAndEnableFeature(net::features::kPriorityHeader);
     } else {
@@ -1778,16 +1779,25 @@
                           ConstructClientAckPacket(packet_num++, 3, 2));
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more data to read
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientDataPacket(packet_num++, GetQpackDecoderStreamId(), false,
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            client_maker_->MakeDataAndRstPacket(
+                                packet_num++, GetQpackDecoderStreamId(),
+                                StreamCancellationQpackDecoderInstruction(0),
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            ConstructClientDataPacket(
+                                packet_num++, GetQpackDecoderStreamId(), false,
                                 StreamCancellationQpackDecoderInstruction(0)));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -3410,12 +3420,21 @@
                                1, GetNthClientInitiatedBidirectionalStreamId(0),
                                quic::QUIC_HEADERS_TOO_LARGE));
 
-  quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeAckRstAndDataPacket(
-          packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-          quic::QUIC_HEADERS_TOO_LARGE, 1, 1, GetQpackDecoderStreamId(), false,
-          StreamCancellationQpackDecoderInstruction(0)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckAndRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_HEADERS_TOO_LARGE, 1, 1,
+            /*include_stop_sending_if_v99=*/false));
+  } else {
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckRstAndDataPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_HEADERS_TOO_LARGE, 1, 1, GetQpackDecoderStreamId(),
+            false, StreamCancellationQpackDecoderInstruction(0)));
+  }
 
   quic_data.AddRead(ASYNC, OK);
   quic_data.AddSocketDataToFactory(&socket_factory_);
@@ -3679,12 +3698,21 @@
       ConstructServerRstPacket(3, GetNthClientInitiatedBidirectionalStreamId(1),
                                quic::QUIC_HEADERS_TOO_LARGE));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeAckRstAndDataPacket(
-          packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
-          quic::QUIC_HEADERS_TOO_LARGE, 3, 2, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(1)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckAndRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_HEADERS_TOO_LARGE, 3, 2,
+            /*include_stop_sending_if_v99=*/false));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckRstAndDataPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_HEADERS_TOO_LARGE, 3, 2, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(1)));
+  }
 
   mock_quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // No more data to read
   mock_quic_data.AddRead(ASYNC, ERR_CONNECTION_CLOSED);
@@ -4681,15 +4709,24 @@
       ASYNC, ConstructServerResponseHeadersPacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false,
                  GetResponseHeaders("425")));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientAckAndDataPacket(
-                       packet_number++, GetQpackDecoderStreamId(), 1, 1, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeRstPacket(
-          packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
-          quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckDataAndRst(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientAckAndDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(), 1, 1,
+                         false, StreamCancellationQpackDecoderInstruction(0)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   client_maker_->SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
 
@@ -4763,15 +4800,24 @@
       ASYNC, ConstructServerResponseHeadersPacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false,
                  GetResponseHeaders("425")));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientAckAndDataPacket(
-                       packet_number++, GetQpackDecoderStreamId(), 1, 1, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeRstPacket(
-          packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
-          quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckDataAndRst(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientAckAndDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(), 1, 1,
+                         false, StreamCancellationQpackDecoderInstruction(0)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   client_maker_->SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
 
@@ -4784,15 +4830,25 @@
       ASYNC, ConstructServerResponseHeadersPacket(
                  2, GetNthClientInitiatedBidirectionalStreamId(1), false,
                  GetResponseHeaders("425")));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientAckAndDataPacket(
-                       packet_number++, GetQpackDecoderStreamId(), 2, 1, false,
-                       StreamCancellationQpackDecoderInstruction(1, false)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeRstPacket(
-          packet_number++, GetNthClientInitiatedBidirectionalStreamId(1),
-          quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckDataAndRst(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_STREAM_CANCELLED, 2, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(1, false)));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckAndDataPacket(
+            packet_number++, GetQpackDecoderStreamId(), 2, 1, false,
+            StreamCancellationQpackDecoderInstruction(1, false)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   mock_quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // No more data to read
   mock_quic_data.AddRead(ASYNC, ERR_CONNECTION_CLOSED);
 
@@ -4978,12 +5034,21 @@
       ConstructServerRstPacket(2, GetNthClientInitiatedBidirectionalStreamId(0),
                                quic::QUIC_STREAM_CANCELLED));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeAckRstAndDataPacket(
-          packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-          quic::QUIC_STREAM_CANCELLED, 2, 1, GetQpackDecoderStreamId(), false,
-          StreamCancellationQpackDecoderInstruction(0)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckAndRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 2, 1,
+            /*include_stop_sending_if_v99=*/false));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckRstAndDataPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 2, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  }
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more read data.
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -5047,12 +5112,21 @@
       ConstructServerRstPacket(1, GetNthClientInitiatedBidirectionalStreamId(0),
                                quic::QUIC_STREAM_CANCELLED));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_->MakeAckRstAndDataPacket(
-          packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-          quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
-          StreamCancellationQpackDecoderInstruction(0)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckAndRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1,
+            /*include_stop_sending_if_v99=*/false));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_->MakeAckRstAndDataPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  }
 
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more read data.
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
@@ -6062,6 +6136,7 @@
             ConfiguredProxyResolutionService::CreateDirect()),
         auth_handler_factory_(HttpAuthHandlerFactory::CreateDefault()),
         ssl_data_(ASYNC, OK) {
+    SetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5, true);
     FLAGS_quic_enable_http3_grease_randomness = false;
   }
 
@@ -6594,19 +6669,30 @@
       .AddWrite("response-ack", ConstructClientAckPacket(packet_num++, 3, 2))
       .Sync();
 
-  socket_data
-      .AddWrite("qpack-cancel",
-                ConstructClientDataPacket(
-                    packet_num++, GetQpackDecoderStreamId(), false,
-                    StreamCancellationQpackDecoderInstruction(0)))
-      .Sync();
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data
+        .AddWrite("qpack-cancel-rst",
+                  client_maker_->MakeDataAndRstPacket(
+                      packet_num++, GetQpackDecoderStreamId(),
+                      StreamCancellationQpackDecoderInstruction(0),
+                      GetNthClientInitiatedBidirectionalStreamId(0),
+                      quic::QUIC_STREAM_CANCELLED))
+        .Sync();
+  } else {
+    socket_data
+        .AddWrite("qpack-cancel",
+                  ConstructClientDataPacket(
+                      packet_num++, GetQpackDecoderStreamId(), false,
+                      StreamCancellationQpackDecoderInstruction(0)))
+        .Sync();
 
-  socket_data
-      .AddWrite("rst",
-                ConstructClientRstPacket(
-                    packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-                    quic::QUIC_STREAM_CANCELLED))
-      .Sync();
+    socket_data
+        .AddWrite("rst", ConstructClientRstPacket(
+                             packet_num++,
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED))
+        .Sync();
+  }
 
   socket_factory_.AddSocketDataProvider(&socket_data);
   socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
@@ -6685,16 +6771,25 @@
                           ConstructClientAckPacket(packet_num++, 3, 2));
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more data to read
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientDataPacket(packet_num++, GetQpackDecoderStreamId(), false,
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            client_maker_->MakeDataAndRstPacket(
+                                packet_num++, GetQpackDecoderStreamId(),
+                                StreamCancellationQpackDecoderInstruction(0),
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            ConstructClientDataPacket(
+                                packet_num++, GetQpackDecoderStreamId(), false,
                                 StreamCancellationQpackDecoderInstruction(0)));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -6932,19 +7027,30 @@
       .AddWrite("response-ack", ConstructClientAckPacket(packet_num++, 3, 2))
       .Sync();
 
-  socket_data
-      .AddWrite("qpack-cancel",
-                ConstructClientDataPacket(
-                    packet_num++, GetQpackDecoderStreamId(), false,
-                    StreamCancellationQpackDecoderInstruction(0)))
-      .Sync();
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data
+        .AddWrite("qpack-cancel-rst",
+                  client_maker_->MakeDataAndRstPacket(
+                      packet_num++, GetQpackDecoderStreamId(),
+                      StreamCancellationQpackDecoderInstruction(0),
+                      GetNthClientInitiatedBidirectionalStreamId(0),
+                      quic::QUIC_STREAM_CANCELLED))
+        .Sync();
+  } else {
+    socket_data
+        .AddWrite("qpack-cancel",
+                  ConstructClientDataPacket(
+                      packet_num++, GetQpackDecoderStreamId(), false,
+                      StreamCancellationQpackDecoderInstruction(0)))
+        .Sync();
 
-  socket_data
-      .AddWrite("rst",
-                ConstructClientRstPacket(
-                    packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-                    quic::QUIC_STREAM_CANCELLED))
-      .Sync();
+    socket_data
+        .AddWrite("rst", ConstructClientRstPacket(
+                             packet_num++,
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED))
+        .Sync();
+  }
 
   socket_factory_.AddSocketDataProvider(&socket_data);
   socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
@@ -7054,16 +7160,25 @@
                           ConstructClientAckPacket(write_packet_index++, 5, 4));
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more data to read
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientDataPacket(
-                       write_packet_index++, GetQpackDecoderStreamId(), false,
-                       StreamCancellationQpackDecoderInstruction(0)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            client_maker_->MakeDataAndRstPacket(
+                                write_packet_index++, GetQpackDecoderStreamId(),
+                                StreamCancellationQpackDecoderInstruction(0),
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientDataPacket(
+                         write_packet_index++, GetQpackDecoderStreamId(), false,
+                         StreamCancellationQpackDecoderInstruction(0)));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(write_packet_index++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(write_packet_index++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -7197,25 +7312,41 @@
   mock_quic_data.AddWrite(SYNCHRONOUS,
                           ConstructClientAckPacket(packet_num++, 6, 5));
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more data to read
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientDataPacket(packet_num++, GetQpackDecoderStreamId(), false,
-                                StreamCancellationQpackDecoderInstruction(0)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), false,
-                       StreamCancellationQpackDecoderInstruction(1, false)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            client_maker_->MakeDataAndRstPacket(
+                                packet_num++, GetQpackDecoderStreamId(),
+                                StreamCancellationQpackDecoderInstruction(0),
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                quic::QUIC_STREAM_CANCELLED));
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(1),
-                               quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_->MakeDataAndRstPacket(
+                         packet_num++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(1, false),
+                         GetNthClientInitiatedBidirectionalStreamId(1),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            ConstructClientDataPacket(
+                                packet_num++, GetQpackDecoderStreamId(), false,
+                                StreamCancellationQpackDecoderInstruction(0)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), false,
+                         StreamCancellationQpackDecoderInstruction(1, false)));
+
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(1),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -7316,16 +7447,25 @@
   mock_quic_data_1.AddRead(SYNCHRONOUS,
                            ERR_IO_PENDING);  // No more data to read
 
-  mock_quic_data_1.AddWrite(
-      SYNCHRONOUS, ConstructClientDataPacket(
-                       write_packet_index++, GetQpackDecoderStreamId(), false,
-                       StreamCancellationQpackDecoderInstruction(0)));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data_1.AddWrite(
+        SYNCHRONOUS, client_maker_->MakeDataAndRstPacket(
+                         write_packet_index++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(0),
+                         GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data_1.AddWrite(
+        SYNCHRONOUS, ConstructClientDataPacket(
+                         write_packet_index++, GetQpackDecoderStreamId(), false,
+                         StreamCancellationQpackDecoderInstruction(0)));
 
-  mock_quic_data_1.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(write_packet_index++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
+    mock_quic_data_1.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(write_packet_index++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data_1.AddSocketDataToFactory(&socket_factory_);
 
@@ -7534,15 +7674,24 @@
       ASYNC, ConstructServerResponseHeadersPacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false,
                  GetResponseHeaders("200")));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientAckAndDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(0),
-                               quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientAckAndDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
+                         StreamCancellationQpackDecoderInstruction(0)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(0),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddWrite(
       SYNCHRONOUS,
@@ -7588,15 +7737,24 @@
                           ConstructClientAckPacket(packet_num++, 4, 3));
   mock_quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // No more data to read
 
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS, ConstructClientDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), false,
-                       StreamCancellationQpackDecoderInstruction(1, false)));
-  mock_quic_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++,
-                               GetNthClientInitiatedBidirectionalStreamId(1),
-                               quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_->MakeDataAndRstPacket(
+                         packet_num++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(1, false),
+                         GetNthClientInitiatedBidirectionalStreamId(1),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS, ConstructClientDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), false,
+                         StreamCancellationQpackDecoderInstruction(1, false)));
+    mock_quic_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++,
+                                 GetNthClientInitiatedBidirectionalStreamId(1),
+                                 quic::QUIC_STREAM_CANCELLED));
+  }
 
   mock_quic_data.AddSocketDataToFactory(&socket_factory_);
 
@@ -7872,18 +8030,27 @@
     mock_quic_data.AddWrite(SYNCHRONOUS,
                             client_maker.MakeAckPacket(packet_num++, 2, 1));
 
-    mock_quic_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker.MakeDataPacket(
-            packet_num++, GetQpackDecoderStreamId(),
-            /* fin = */ false, StreamCancellationQpackDecoderInstruction(0)));
+    if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+      mock_quic_data.AddWrite(SYNCHRONOUS,
+                              client_maker.MakeDataAndRstPacket(
+                                  packet_num++, GetQpackDecoderStreamId(),
+                                  StreamCancellationQpackDecoderInstruction(0),
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  quic::QUIC_STREAM_CANCELLED));
+    } else {
+      mock_quic_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker.MakeDataPacket(
+              packet_num++, GetQpackDecoderStreamId(),
+              /* fin = */ false, StreamCancellationQpackDecoderInstruction(0)));
 
-    mock_quic_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker.MakeRstPacket(
-            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED,
-            /*include_stop_sending_if_v99=*/true));
+      mock_quic_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker.MakeRstPacket(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+              quic::QUIC_STREAM_CANCELLED,
+              /*include_stop_sending_if_v99=*/true));
+    }
 
     mock_quic_data.AddWrite(
         SYNCHRONOUS,
@@ -7911,15 +8078,25 @@
     mock_quic_data.AddRead(SYNCHRONOUS,
                            ERR_IO_PENDING);  // No more data to read
 
-    mock_quic_data.AddWrite(
-        SYNCHRONOUS, client_maker.MakeAckAndDataPacket(
-                         packet_num++, GetQpackDecoderStreamId(), 3, 3, false,
-                         StreamCancellationQpackDecoderInstruction(1, false)));
-    mock_quic_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker.MakeRstPacket(
-            packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
-            quic::QUIC_STREAM_CANCELLED));
+    if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+      mock_quic_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker.MakeAckDataAndRst(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+              quic::QUIC_STREAM_CANCELLED, 3, 3, GetQpackDecoderStreamId(),
+              false, StreamCancellationQpackDecoderInstruction(1, false)));
+    } else {
+      mock_quic_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker.MakeAckAndDataPacket(
+              packet_num++, GetQpackDecoderStreamId(), 3, 3, false,
+              StreamCancellationQpackDecoderInstruction(1, false)));
+      mock_quic_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker.MakeRstPacket(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+              quic::QUIC_STREAM_CANCELLED));
+    }
 
     mock_quic_data.AddSocketDataToFactory(&socket_factory_);
     mock_quic_data.GetSequencedSocketData()->set_busy_before_sync_reads(true);
diff --git a/net/quic/quic_session_pool_test.cc b/net/quic/quic_session_pool_test.cc
index ae1c2ae..ef1b6bf 100644
--- a/net/quic/quic_session_pool_test.cc
+++ b/net/quic/quic_session_pool_test.cc
@@ -386,15 +386,25 @@
   socket_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
                                          packet_num++,
                                          /*sequence_number=*/0u));
-  socket_data2.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -1731,13 +1741,22 @@
   socket_data.AddWrite(SYNCHRONOUS, client_maker_.MakeStreamsBlockedPacket(
                                         packet_num++, 50,
                                         /*unidirectional=*/false));
-  socket_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeDataPacket(
-                           packet_num++, GetQpackDecoderStreamId(), false,
-                           StreamCancellationQpackDecoderInstruction(0)));
-  socket_data.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeRstPacket(packet_num++, stream_id,
-                                               quic::QUIC_STREAM_CANCELLED));
+
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataAndRstPacket(
+                             packet_num++, GetQpackDecoderStreamId(),
+                             StreamCancellationQpackDecoderInstruction(0),
+                             stream_id, quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataPacket(
+                             packet_num++, GetQpackDecoderStreamId(), false,
+                             StreamCancellationQpackDecoderInstruction(0)));
+    socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(packet_num++, stream_id,
+                                                 quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data.AddRead(ASYNC, server_maker_.MakeRstPacket(
                                  1, stream_id, quic::QUIC_STREAM_CANCELLED));
   socket_data.AddRead(
@@ -2426,13 +2445,23 @@
   int packet_num = 1;
   socket_data.AddWrite(SYNCHRONOUS,
                        ConstructInitialSettingsPacket(packet_num++));
-  socket_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeDataPacket(
-                           packet_num++, GetQpackDecoderStreamId(), false,
-                           StreamCancellationQpackDecoderInstruction(0)));
-  socket_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num, quic::QUIC_STREAM_CANCELLED));
+
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataAndRstPacket(
+                             packet_num++, GetQpackDecoderStreamId(),
+                             StreamCancellationQpackDecoderInstruction(0),
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataPacket(
+                             packet_num++, GetQpackDecoderStreamId(), false,
+                             StreamCancellationQpackDecoderInstruction(0)));
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num, quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   RequestBuilder builder(this);
@@ -2521,15 +2550,24 @@
       ASYNC, ConstructOkResponsePacket(
                  2, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeAckAndDataPacket(
-                          packet_num++, GetQpackDecoderStreamId(), 2, 2, false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 2, 2, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), 2, 2, false,
+                         StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -2676,15 +2714,24 @@
                                        /*smallest_received=*/1)
                           .AddRetireConnectionIdFrame(/*sequence_number=*/0u)
                           .Build());
-  quic_data2.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeDataPacket(
-                          packet_num++, GetQpackDecoderStreamId(), false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_num++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataPacket(
+                            packet_num++, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
@@ -2971,15 +3018,24 @@
   int packet_num = 1;
   socket_data.AddWrite(SYNCHRONOUS,
                        ConstructInitialSettingsPacket(packet_num++));
-  socket_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeDataPacket(
-                           packet_num++, GetQpackDecoderStreamId(), false,
-                           StreamCancellationQpackDecoderInstruction(0)));
-  socket_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataAndRstPacket(
+                             packet_num++, GetQpackDecoderStreamId(),
+                             StreamCancellationQpackDecoderInstruction(0),
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataPacket(
+                             packet_num++, GetQpackDecoderStreamId(), false,
+                             StreamCancellationQpackDecoderInstruction(0)));
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -3054,23 +3110,47 @@
     failed_socket_data.AddWrite(SYNCHRONOUS,
                                 ConstructInitialSettingsPacket(packet_num++));
     // A RESET will be sent to the peer to cancel the non-migratable stream.
-    failed_socket_data.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeDataPacket(
-                         packet_num++, GetQpackDecoderStreamId(), false,
-                         StreamCancellationQpackDecoderInstruction(0)));
-    failed_socket_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakeRstPacket(
-            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED));
+    if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+      failed_socket_data.AddWrite(
+          SYNCHRONOUS, client_maker_.MakeDataAndRstPacket(
+                           packet_num++, GetQpackDecoderStreamId(),
+                           StreamCancellationQpackDecoderInstruction(0),
+                           GetNthClientInitiatedBidirectionalStreamId(0),
+                           quic::QUIC_STREAM_CANCELLED));
+    } else {
+      failed_socket_data.AddWrite(
+          SYNCHRONOUS, client_maker_.MakeDataPacket(
+                           packet_num++, GetQpackDecoderStreamId(), false,
+                           StreamCancellationQpackDecoderInstruction(0)));
+      failed_socket_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRstPacket(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+              quic::QUIC_STREAM_CANCELLED));
+    }
     failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
     // Set up second socket data provider that is used after migration.
     client_maker_.set_connection_id(cid_on_new_path);
     socket_data.AddReadPauseForever();
-    socket_data.AddWrite(SYNCHRONOUS,
-                         client_maker_.MakeCombinedRetransmissionPacket(
-                             {3, 1, 2}, packet_num++));
+    if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+      auto packet_one_frames = client_maker_.CloneSavedFrames(1);  // STREAM
+      auto packet_two_frames =
+          client_maker_.CloneSavedFrames(2);  // STREAM, RST_STREAM
+      auto& packet = client_maker_.Packet(packet_num++);
+      for (size_t i = 1; i < packet_two_frames.size(); ++i) {
+        packet.AddFrame(packet_two_frames[i]);
+      }
+      for (auto& frame : packet_one_frames) {
+        packet.AddFrame(frame);
+      }
+      packet.AddFrame(packet_two_frames[0]);
+      socket_data.AddWrite(SYNCHRONOUS, packet.Build());
+    } else {
+      socket_data.AddWrite(SYNCHRONOUS,
+                           client_maker_.MakeCombinedRetransmissionPacket(
+                               {3, 1, 2}, packet_num++));
+    }
     // Ping packet to send after migration.
     socket_data.AddWrite(SYNCHRONOUS,
                          client_maker_.MakePingPacket(packet_num++));
@@ -3083,15 +3163,24 @@
     int packet_num = 1;
     socket_data.AddWrite(SYNCHRONOUS,
                          ConstructInitialSettingsPacket(packet_num++));
-    socket_data.AddWrite(SYNCHRONOUS,
-                         client_maker_.MakeDataPacket(
-                             packet_num++, GetQpackDecoderStreamId(), false,
-                             StreamCancellationQpackDecoderInstruction(0)));
-    socket_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakeRstPacket(
-            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED));
+    if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+      socket_data.AddWrite(SYNCHRONOUS,
+                           client_maker_.MakeDataAndRstPacket(
+                               packet_num++, GetQpackDecoderStreamId(),
+                               StreamCancellationQpackDecoderInstruction(0),
+                               GetNthClientInitiatedBidirectionalStreamId(0),
+                               quic::QUIC_STREAM_CANCELLED));
+    } else {
+      socket_data.AddWrite(SYNCHRONOUS,
+                           client_maker_.MakeDataPacket(
+                               packet_num++, GetQpackDecoderStreamId(), false,
+                               StreamCancellationQpackDecoderInstruction(0)));
+      socket_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRstPacket(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+              quic::QUIC_STREAM_CANCELLED));
+    }
     socket_data.AddSocketDataToFactory(socket_factory_.get());
   }
 
@@ -3435,16 +3524,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_number++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_number++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_number++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Trigger connection migration.
@@ -3571,16 +3669,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Add a new network and notify the stream factory of a new connected network.
@@ -3682,15 +3789,24 @@
       ASYNC, ConstructOkResponsePacket(
                  5, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                       packet_number++, GetQpackDecoderStreamId(), 5, 1, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 5, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(), 5, 1,
+                         false, StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -3840,15 +3956,24 @@
       ASYNC, ConstructOkResponsePacket(
                  2, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                       packet_number++, GetQpackDecoderStreamId(), 2, 2, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 2, 2, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(), 2, 2,
+                         false, StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4244,13 +4369,23 @@
                                        5, /*sequence_number=*/0u));
   quic_data2.AddRead(ASYNC, server_maker_.MakeAckPacket(3, 5, 1));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(ASYNC, client_maker_.MakeDataPacket(
-                                 6, GetQpackDecoderStreamId(), false,
-                                 StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(ASYNC,
-                      client_maker_.MakeRstPacket(
-                          7, GetNthClientInitiatedBidirectionalStreamId(0),
-                          quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            6, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data2.AddWrite(ASYNC,
+                        client_maker_.MakeDataPacket(
+                            6, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(ASYNC,
+                        client_maker_.MakeRstPacket(
+                            7, GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  }
 
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
@@ -4531,15 +4666,24 @@
                       ConstructGetRequestPacket(
                           packet_number++,
                           GetNthClientInitiatedBidirectionalStreamId(0), true));
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeDataPacket(
-                          packet_number++, GetQpackDecoderStreamId(), false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_number++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataPacket(
+                            packet_number++, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4629,15 +4773,24 @@
                       ConstructGetRequestPacket(
                           packet_number++,
                           GetNthClientInitiatedBidirectionalStreamId(0), true));
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeDataPacket(
-                          packet_number + 1, GetQpackDecoderStreamId(), false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number + 2,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_number + 1, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataPacket(
+                            packet_number + 1, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number + 2, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up the second socket data provider that is used for migration probing.
@@ -5341,15 +5494,24 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeDataPacket(
-                          packet_num++, GetQpackDecoderStreamId(), false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_num++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataPacket(
+                            packet_num++, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   quic_data.AddSocketDataToFactory(socket_factory_.get());
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
@@ -5487,16 +5649,25 @@
                                        /*smallest_received=*/1)
                           .AddRetireConnectionIdFrame(/*sequence_number=*/0u)
                           .Build());
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_number++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_number++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_number++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -5978,15 +6149,24 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data.AddReadPauseForever();
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeAckAndDataPacket(
-                         packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
-                         StreamCancellationQpackDecoderInstruction(0)));
-  quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeAckAndDataPacket(
+                           packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
+                           StreamCancellationQpackDecoderInstruction(0)));
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up second socket that will immediately return disconnected.
@@ -6260,15 +6440,24 @@
       ASYNC, ConstructOkResponsePacket(
                  2, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data2.AddReadPauseForever();
-  quic_data2.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeAckAndDataPacket(
-                          packet_num++, GetQpackDecoderStreamId(), 2, 2, false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 2, 2, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), 2, 2, false,
+                         StreamCancellationQpackDecoderInstruction(0)));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
@@ -6508,15 +6697,24 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   quic_data.AddReadPauseForever();
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeAckAndDataPacket(
-                         packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
-                         StreamCancellationQpackDecoderInstruction(0)));
-  quic_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeAckAndDataPacket(
+                           packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
+                           StreamCancellationQpackDecoderInstruction(0)));
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   quic_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -6706,15 +6904,24 @@
   int packet_num = 1;
   socket_data.AddWrite(SYNCHRONOUS,
                        ConstructInitialSettingsPacket(packet_num++));
-  socket_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeDataPacket(
-                           packet_num++, GetQpackDecoderStreamId(), false,
-                           StreamCancellationQpackDecoderInstruction(0)));
-  socket_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataAndRstPacket(
+                             packet_num++, GetQpackDecoderStreamId(),
+                             StreamCancellationQpackDecoderInstruction(0),
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataPacket(
+                             packet_num++, GetQpackDecoderStreamId(), false,
+                             StreamCancellationQpackDecoderInstruction(0)));
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -6814,29 +7021,43 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataAndRstPacket(
+                         packet_num++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(1, false),
+                         GetNthClientInitiatedBidirectionalStreamId(1),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
 
-  socket_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(),
-                       /* fin = */ false,
-                       StreamCancellationQpackDecoderInstruction(1, false)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(1),
-                                  quic::QUIC_STREAM_CANCELLED,
-                                  /*include_stop_sending_if_v99=*/true));
-
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(),
+                         /* fin = */ false,
+                         StreamCancellationQpackDecoderInstruction(1, false)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_STREAM_CANCELLED,
+            /*include_stop_sending_if_v99=*/true));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request #1 and QuicHttpStream.
@@ -7069,16 +7290,25 @@
                                  /*largest_received=*/peer_packet_num - 1,
                                  /*smallest_received=*/1));
 
-  quic_data3.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeDataPacket(
-                          packet_num++, GetQpackDecoderStreamId(), false,
-                          StreamCancellationQpackDecoderInstruction(0)));
-  quic_data3.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED,
-                                  /*include_stop_sending_if_v99=*/true));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    quic_data3.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_num++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
+  } else {
+    quic_data3.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataPacket(
+                            packet_num++, GetQpackDecoderStreamId(), false,
+                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data3.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED,
+            /*include_stop_sending_if_v99=*/true));
+  }
   quic_data3.AddSocketDataToFactory(socket_factory_.get());
 
   // Fast forward to fire the migrate back timer and verify the session
@@ -7306,15 +7536,24 @@
   socket_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
                                          packet_num++,
                                          /*sequence_number=*/1u));
-  socket_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), /*fin=*/false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), /*fin=*/false,
+                         StreamCancellationQpackDecoderInstruction(0)));
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Socket data for probing on the default network.
@@ -7513,15 +7752,24 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data2.AddReadPauseForever();
-  socket_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
-                       StreamCancellationQpackDecoderInstruction(0)));
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeAckDataAndRst(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED, 1, 1, GetQpackDecoderStreamId(), false,
+            StreamCancellationQpackDecoderInstruction(0)));
+  } else {
+    socket_data2.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), 1, 1, false,
+                         StreamCancellationQpackDecoderInstruction(0)));
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request, should fail after the write of the CHLO fails.
@@ -7636,16 +7884,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Send GET request on stream. This should cause a write error, which triggers
@@ -7834,27 +8091,43 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
 
-  socket_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeDataPacket(
-                       packet_num++, GetQpackDecoderStreamId(), false,
-                       StreamCancellationQpackDecoderInstruction(1, false)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(1),
-                                  quic::QUIC_STREAM_CANCELLED,
-                                  /*include_stop_sending_if_v99=*/true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataAndRstPacket(
+                         packet_num++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(1, false),
+                         GetNthClientInitiatedBidirectionalStreamId(1),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), false,
+                         StreamCancellationQpackDecoderInstruction(1, false)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
+            quic::QUIC_STREAM_CANCELLED,
+            /*include_stop_sending_if_v99=*/true));
+  }
 
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
@@ -7991,16 +8264,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_number++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0, false)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataAndRstPacket(
+                         packet_number++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(0, false),
+                         GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(),
+                         /*fin=*/false,
+                         StreamCancellationQpackDecoderInstruction(0, false)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request #1 and QuicHttpStream.
@@ -8148,16 +8430,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_number++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0, false)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_number++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataAndRstPacket(
+                         packet_number++, GetQpackDecoderStreamId(),
+                         StreamCancellationQpackDecoderInstruction(0, false),
+                         GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_number++, GetQpackDecoderStreamId(),
+                         /*fin=*/false,
+                         StreamCancellationQpackDecoderInstruction(0, false)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request #1 and QuicHttpStream.
@@ -8658,16 +8949,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
@@ -8809,16 +9109,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
@@ -8976,16 +9285,25 @@
                        /*sequence_number=*/0u));
   socket_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // On a DISCONNECTED notification, nothing happens.
@@ -9105,16 +9423,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Send GET request on stream.
@@ -9225,16 +9552,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
@@ -9350,16 +9686,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
 
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
@@ -9485,15 +9830,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -9643,15 +9997,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -9769,15 +10132,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -9890,15 +10262,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -10012,15 +10393,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -10135,15 +10525,24 @@
                  3, GetNthClientInitiatedBidirectionalStreamId(0), true,
                  header + "hello!"));
   socket_data1.AddReadPauseForever();
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -10439,16 +10838,25 @@
                             /*original_packet_numbers=*/{1, 2}, packet_num++));
   socket_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
                                          packet_num++, /*sequence_number=*/0u));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
@@ -10981,16 +11389,25 @@
       ASYNC, ConstructOkResponsePacket(
                  1, GetNthClientInitiatedBidirectionalStreamId(0), false));
   socket_data2.AddReadPauseForever();
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeDataPacket(
-          packet_num++, GetQpackDecoderStreamId(),
-          /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data2.AddSocketDataToFactory(socket_factory_.get());
 
   const uint8_t kTestIpAddress[] = {1, 2, 3, 4};
@@ -11051,13 +11468,22 @@
       SYNCHRONOUS,
       ConstructGetRequestPacket(
           packet_num++, GetNthClientInitiatedBidirectionalStreamId(0), true));
-  socket_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeDataPacket(
-                           packet_num++, GetQpackDecoderStreamId(), false,
-                           StreamCancellationQpackDecoderInstruction(0)));
-  socket_data.AddWrite(
-      SYNCHRONOUS,
-      ConstructClientRstPacket(packet_num++, quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataAndRstPacket(
+                             packet_num++, GetQpackDecoderStreamId(),
+                             StreamCancellationQpackDecoderInstruction(0),
+                             GetNthClientInitiatedBidirectionalStreamId(0),
+                             quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data.AddWrite(SYNCHRONOUS,
+                         client_maker_.MakeDataPacket(
+                             packet_num++, GetQpackDecoderStreamId(), false,
+                             StreamCancellationQpackDecoderInstruction(0)));
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        ConstructClientRstPacket(packet_num++, quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -11192,15 +11618,24 @@
   int packet_num = 1;
   socket_data1.AddWrite(SYNCHRONOUS,
                         ConstructInitialSettingsPacket(packet_num++));
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -11268,15 +11703,24 @@
   int packet_num = 1;
   socket_data1.AddWrite(SYNCHRONOUS,
                         ConstructInitialSettingsPacket(packet_num++));
-  socket_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakeRstPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  quic::QUIC_STREAM_CANCELLED));
+  if (GetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5)) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataAndRstPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              StreamCancellationQpackDecoderInstruction(0),
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              quic::QUIC_STREAM_CANCELLED));
+  } else {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(), false,
+                              StreamCancellationQpackDecoderInstruction(0)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+  }
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
diff --git a/net/quic/quic_session_pool_test_base.cc b/net/quic/quic_session_pool_test_base.cc
index 00e4522..f3602dc 100644
--- a/net/quic/quic_session_pool_test_base.cc
+++ b/net/quic/quic_session_pool_test_base.cc
@@ -159,6 +159,7 @@
           &QuicSessionPoolTestBase::OnFailedOnDefaultNetwork,
           base::Unretained(this))),
       quic_params_(context_.params()) {
+  SetQuicRestartFlag(quic_opport_bundle_qpack_decoder_data5, true);
   enabled_features.push_back(features::kAsyncQuicSession);
   scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   FLAGS_quic_enable_http3_grease_randomness = false;
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index dd60b06..850c3b3 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -413,6 +413,12 @@
   return builder.Build();
 }
 
+quic::QuicFrames QuicTestPacketMaker::CloneSavedFrames(uint64_t packet_number) {
+  DCHECK(connection_state_.save_packet_frames);
+  return CloneFrames(
+      connection_state_.saved_frames[quic::QuicPacketNumber(packet_number)]);
+}
+
 std::unique_ptr<quic::QuicReceivedPacket>
 QuicTestPacketMaker::MakeCombinedRetransmissionPacket(
     const std::vector<uint64_t>& original_packet_numbers,
diff --git a/net/quic/quic_test_packet_maker.h b/net/quic/quic_test_packet_maker.h
index 6951151..83a3e81 100644
--- a/net/quic/quic_test_packet_maker.h
+++ b/net/quic/quic_test_packet_maker.h
@@ -90,6 +90,9 @@
   // define the frames in the packet, and finish with its `Build` method.
   QuicTestPacketBuilder& Packet(uint64_t packet_number);
 
+  // Clone all frames from |packet_number|.
+  quic::QuicFrames CloneSavedFrames(uint64_t packet_number);
+
   std::unique_ptr<quic::QuicReceivedPacket> MakeConnectivityProbingPacket(
       uint64_t packet_number);
 
diff --git a/services/accessibility/features/javascript/automation.js b/services/accessibility/features/javascript/automation.js
index f9ed292..adbece8 100644
--- a/services/accessibility/features/javascript/automation.js
+++ b/services/accessibility/features/javascript/automation.js
@@ -2194,6 +2194,7 @@
     actionData.sourceExtensionId = '';
     actionData.requestId = 0;
     actionData.targetNodeId = 0;
+    actionData.targetRole = ax.mojom.Role.kUnknown;
     actionData.flags = 0;
     actionData.action = ax.mojom.Action.kNone;
     actionData.anchorNodeId = 0;
diff --git a/services/network/public/cpp/BUILD.gn b/services/network/public/cpp/BUILD.gn
index 10474ea..a62ce005 100644
--- a/services/network/public/cpp/BUILD.gn
+++ b/services/network/public/cpp/BUILD.gn
@@ -76,6 +76,8 @@
     "empty_url_loader_client.h",
     "features.cc",
     "features.h",
+    "fence_event_reporting_parser.cc",
+    "fence_event_reporting_parser.h",
     "header_util.cc",
     "header_util.h",
     "initiator_lock_compatibility.cc",
@@ -581,6 +583,7 @@
     "digitally_signed_mojom_traits_unittest.cc",
     "document_isolation_policy_parser_unittest.cc",
     "empty_url_loader_client_unittest.cc",
+    "fence_event_reporting_parser_unittest.cc",
     "file_enumeration_entry_mojom_traits_unittest.cc",
     "first_party_sets_mojom_traits_unittest.cc",
     "hash_value_mojom_traits_unittest.cc",
diff --git a/services/network/public/cpp/fence_event_reporting_parser.cc b/services/network/public/cpp/fence_event_reporting_parser.cc
new file mode 100644
index 0000000..42f8c80
--- /dev/null
+++ b/services/network/public/cpp/fence_event_reporting_parser.cc
@@ -0,0 +1,21 @@
+// 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 "services/network/public/cpp/fence_event_reporting_parser.h"
+
+#include "net/http/structured_headers.h"
+
+namespace network {
+
+bool ParseAllowCrossOriginEventReportingFromHeader(
+    const net::HttpResponseHeaders& headers) {
+  std::string header_value;
+  headers.GetNormalizedHeader("Allow-Cross-Origin-Event-Reporting",
+                              &header_value);
+  std::optional<net::structured_headers::Item> item =
+      net::structured_headers::ParseBareItem(header_value);
+  return item && item->is_boolean() && item->GetBoolean();
+}
+
+}  // namespace network
diff --git a/services/network/public/cpp/fence_event_reporting_parser.h b/services/network/public/cpp/fence_event_reporting_parser.h
new file mode 100644
index 0000000..ca5a5f1f
--- /dev/null
+++ b/services/network/public/cpp/fence_event_reporting_parser.h
@@ -0,0 +1,23 @@
+// 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 SERVICES_NETWORK_PUBLIC_CPP_FENCE_EVENT_REPORTING_PARSER_H_
+#define SERVICES_NETWORK_PUBLIC_CPP_FENCE_EVENT_REPORTING_PARSER_H_
+
+#include "base/component_export.h"
+#include "net/http/http_response_headers.h"
+
+namespace network {
+
+// Parses the `Allow-Cross-Origin-Event-Reporting` response header. Returns true
+// if the parsing succeeds and the parsed value is Boolean true; returns false
+// otherwise. See:
+// https://wicg.github.io/fenced-frame/#fenced-frame-config-cross-origin-reporting-allowed
+COMPONENT_EXPORT(NETWORK_CPP)
+bool ParseAllowCrossOriginEventReportingFromHeader(
+    const net::HttpResponseHeaders& headers);
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_PUBLIC_CPP_FENCE_EVENT_REPORTING_PARSER_H_
diff --git a/services/network/public/cpp/fence_event_reporting_parser_unittest.cc b/services/network/public/cpp/fence_event_reporting_parser_unittest.cc
new file mode 100644
index 0000000..3968abd
--- /dev/null
+++ b/services/network/public/cpp/fence_event_reporting_parser_unittest.cc
@@ -0,0 +1,68 @@
+// 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 "services/network/public/cpp/fence_event_reporting_parser.h"
+
+#include "net/http/http_response_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+
+TEST(FenceEventReportingParserTest,
+     ParseAllowCrossOriginEventReportingFromHeader_NoObserveTopicsHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
+  EXPECT_FALSE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+TEST(
+    FenceEventReportingParserTest,
+    ParseAllowCrossOriginEventReportingFromHeader_TrueValueObserveTopicsHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(
+          "HTTP/1.1 200 OK\r\n"
+          "Allow-Cross-Origin-Event-Reporting: ?1\r\n");
+  EXPECT_TRUE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+TEST(
+    FenceEventReportingParserTest,
+    ParseAllowCrossOriginEventReportingFromHeader_FalseValueObserveTopicsHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(
+          "HTTP/1.1 200 OK\r\n"
+          "Allow-Cross-Origin-Event-Reporting: ?0\r\n");
+  EXPECT_FALSE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+TEST(
+    FenceEventReportingParserTest,
+    ParseAllowCrossOriginEventReportingFromHeader_NotBooleanObserveTopicsHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(
+          "HTTP/1.1 200 OK\r\n"
+          "Allow-Cross-Origin-Event-Reporting: 1\r\n");
+  EXPECT_FALSE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+TEST(FenceEventReportingParserTest,
+     ParseAllowCrossOriginEventReportingFromHeader_InvalidObserveTopicsHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(
+          "HTTP/1.1 200 OK\r\n"
+          "Allow-Cross-Origin-Event-Reporting: !!!\r\n");
+  EXPECT_FALSE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+TEST(FenceEventReportingParserTest,
+     ParseAllowCrossOriginEventReportingFromHeader_InvalidNormalizedHeader) {
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      net::HttpResponseHeaders::TryToCreate(
+          "HTTP/1.1 200 OK\r\n"
+          "Allow-Cross-Origin-Event-Reporting: ?1\r\n"
+          "Allow-Cross-Origin-Event-Reporting: ?1\r\n");
+  EXPECT_FALSE(ParseAllowCrossOriginEventReportingFromHeader(*headers));
+}
+
+}  // namespace network
diff --git a/services/network/public/cpp/parsed_headers.cc b/services/network/public/cpp/parsed_headers.cc
index a9861d16..26e96f7 100644
--- a/services/network/public/cpp/parsed_headers.cc
+++ b/services/network/public/cpp/parsed_headers.cc
@@ -25,6 +25,7 @@
 #include "services/network/public/cpp/cross_origin_opener_policy_parser.h"
 #include "services/network/public/cpp/document_isolation_policy_parser.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/fence_event_reporting_parser.h"
 #include "services/network/public/cpp/link_header_parser.h"
 #include "services/network/public/cpp/no_vary_search_header_parser.h"
 #include "services/network/public/cpp/origin_agent_cluster_parser.h"
@@ -154,6 +155,9 @@
   parsed_headers->observe_browsing_topics =
       ParseObserveBrowsingTopicsFromHeader(*headers);
 
+  parsed_headers->allow_cross_origin_event_reporting =
+      ParseAllowCrossOriginEventReportingFromHeader(*headers);
+
   return parsed_headers;
 }
 
diff --git a/services/network/public/cpp/simple_url_loader.cc b/services/network/public/cpp/simple_url_loader.cc
index a002343..1543cf1 100644
--- a/services/network/public/cpp/simple_url_loader.cc
+++ b/services/network/public/cpp/simple_url_loader.cc
@@ -283,6 +283,7 @@
 
   int NetError() const override;
   const mojom::URLResponseHead* ResponseInfo() const override;
+  mojom::URLResponseHeadPtr TakeResponseInfo() override;
   const std::optional<URLLoaderCompletionStatus>& CompletionStatus()
       const override;
   const GURL& GetFinalURL() const override;
@@ -1586,6 +1587,12 @@
   return request_state_->response_info.get();
 }
 
+mojom::URLResponseHeadPtr SimpleURLLoaderImpl::TakeResponseInfo() {
+  // Should only be called once the request is complete.
+  DCHECK(request_state_->finished);
+  return std::move(request_state_->response_info);
+}
+
 const std::optional<URLLoaderCompletionStatus>&
 SimpleURLLoaderImpl::CompletionStatus() const {
   // Should only be called once the request is complete.
diff --git a/services/network/public/cpp/simple_url_loader.h b/services/network/public/cpp/simple_url_loader.h
index 2d89684..7858419 100644
--- a/services/network/public/cpp/simple_url_loader.h
+++ b/services/network/public/cpp/simple_url_loader.h
@@ -372,10 +372,15 @@
   virtual int NetError() const = 0;
 
   // The URLResponseHead for the request. Will be nullptr if ResponseInfo
-  // was never received. May only be called once the loader has informed the
-  // caller of completion.
+  // was never received or if `TakeResponseInfo()` has been called. May only be
+  // called once the loader has informed the caller of completion.
   virtual const mojom::URLResponseHead* ResponseInfo() const = 0;
 
+  // The URLResponseHead for the request. Ownership is transferred to the
+  // caller. Will be nullptr if ResponseInfo was never received. May only be
+  // called once the loader has informed the caller of completion.
+  virtual mojom::URLResponseHeadPtr TakeResponseInfo() = 0;
+
   // The URLLoaderCompletionStatus for the request. Will be nullopt if the
   // response never completed. May only be called once the loader has informed
   // the caller of completion.
diff --git a/services/network/public/mojom/parsed_headers.mojom b/services/network/public/mojom/parsed_headers.mojom
index 597d0c5..6173c821 100644
--- a/services/network/public/mojom/parsed_headers.mojom
+++ b/services/network/public/mojom/parsed_headers.mojom
@@ -145,5 +145,9 @@
   // https://patcg-individual-drafts.github.io/topics/#the-observe-browsing-topics-http-response-header-header
   bool observe_browsing_topics;
 
+  // The parsed value of the Allow-Cross-Origin-Event-Reporting header. See:
+  // https://wicg.github.io/fenced-frame/#fenced-frame-config-cross-origin-reporting-allowed
+  bool allow_cross_origin_event_reporting;
+
   // Please update `CheckParsedHeadersEquals` after adding new fields.
 };
diff --git a/services/passage_embeddings/passage_embeddings_service.cc b/services/passage_embeddings/passage_embeddings_service.cc
index 7239aaa..851abdb 100644
--- a/services/passage_embeddings/passage_embeddings_service.cc
+++ b/services/passage_embeddings/passage_embeddings_service.cc
@@ -16,7 +16,6 @@
 
 PassageEmbeddingsService::~PassageEmbeddingsService() = default;
 
-// TODO(b/338650221): Add histogram for whether model load succeeds or not.
 void PassageEmbeddingsService::LoadModels(
     mojom::PassageEmbeddingsLoadModelsParamsPtr params,
     mojo::PendingReceiver<mojom::PassageEmbedder> model,
diff --git a/services/webnn/features.gni b/services/webnn/features.gni
index 4ff8844..b41cfc22 100644
--- a/services/webnn/features.gni
+++ b/services/webnn/features.gni
@@ -5,7 +5,7 @@
 import("//build/config/features.gni")
 
 declare_args() {
-  webnn_use_tflite = is_android || is_chromeos || is_linux
+  webnn_use_tflite = is_android || is_chromeos || is_linux || is_win
 
   # Enable logging of TFLite profiling information on MLGraph destruction.
   webnn_enable_tflite_profiler = false
diff --git a/services/webnn/webnn_context_provider_impl.cc b/services/webnn/webnn_context_provider_impl.cc
index 33bdd35..b890e79 100644
--- a/services/webnn/webnn_context_provider_impl.cc
+++ b/services/webnn/webnn_context_provider_impl.cc
@@ -86,6 +86,18 @@
 }
 #endif
 
+#if BUILDFLAG(IS_WIN)
+bool ShouldCreateDmlContext(const mojom::CreateContextOptions& options) {
+  switch (options.device) {
+    case mojom::CreateContextOptions::Device::kCpu:
+      return false;
+    case mojom::CreateContextOptions::Device::kGpu:
+    case mojom::CreateContextOptions::Device::kNpu:
+      return true;
+  }
+}
+#endif  // BUILDFLAG(IS_WIN)
+
 }  // namespace
 
 WebNNContextProviderImpl::WebNNContextProviderImpl(
@@ -168,109 +180,97 @@
                                               std::move(callback));
     return;
   }
-#if BUILDFLAG(WEBNN_USE_TFLITE)
-  // TODO: crbug.com/41486052 - Create the TFLite context using `options`.
-
   // The remote sent to the renderer.
   mojo::PendingRemote<mojom::WebNNContext> blink_remote;
+  // The receiver bound to WebNNContextImpl.
   auto receiver = blink_remote.InitWithNewPipeAndPassReceiver();
+
+#if BUILDFLAG(IS_WIN)
+  if (ShouldCreateDmlContext(*options)) {
+    DCHECK(gpu_feature_info_.IsInitialized());
+    if (gpu_feature_info_.status_values[gpu::GPU_FEATURE_TYPE_WEBNN] !=
+        gpu::kGpuFeatureStatusEnabled) {
+      std::move(callback).Run(ToError<mojom::CreateContextResult>(
+          mojom::Error::Code::kNotSupportedError,
+          "WebNN is not compatible with GPU."));
+      LOG(ERROR) << "[WebNN] is not compatible with GPU.";
+      return;
+    }
+
+    // Get the `Adapter` instance which is created for the adapter according to
+    // the device type. At the current stage, all `ContextImpl` share one
+    // instance for one device type.
+    base::expected<scoped_refptr<dml::Adapter>, mojom::ErrorPtr>
+        adapter_creation_result;
+    switch (options->device) {
+      case mojom::CreateContextOptions::Device::kCpu:
+        NOTREACHED_NORETURN();
+      case mojom::CreateContextOptions::Device::kGpu:
+        adapter_creation_result = GetDmlGpuAdapter(shared_context_state_.get());
+        break;
+      case mojom::CreateContextOptions::Device::kNpu:
+        adapter_creation_result =
+            dml::Adapter::GetNpuInstance(kMinDMLFeatureLevelForWebNN);
+        break;
+    }
+    if (!adapter_creation_result.has_value()) {
+      std::move(callback).Run(mojom::CreateContextResult::NewError(
+          std::move(adapter_creation_result.error())));
+      return;
+    }
+
+    scoped_refptr<dml::Adapter> adapter = adapter_creation_result.value();
+    std::unique_ptr<dml::CommandRecorder> command_recorder =
+        dml::CommandRecorder::Create(adapter->command_queue(),
+                                     adapter->dml_device());
+    if (!command_recorder) {
+      std::move(callback).Run(mojom::CreateContextResult::NewError(
+          dml::CreateError(mojom::Error::Code::kUnknownError,
+                           "Failed to create a WebNN context.")));
+      LOG(ERROR) << "[WebNN] Failed to open the command recorder.";
+      return;
+    }
+
+    auto* context_impl =
+        new dml::ContextImplDml(std::move(adapter), std::move(receiver), this,
+                                std::move(command_recorder), gpu_feature_info_);
+    impls_.push_back(base::WrapUnique<WebNNContextImpl>(context_impl));
+    std::move(callback).Run(
+        mojom::CreateContextResult::NewContextRemote(std::move(blink_remote)));
+    return;
+  }
+#endif  // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_MAC)
+  if (__builtin_available(macOS 14, *)) {
+    // TODO: crbug.com/41481333 - Create the CoreML context using `options`.
+    auto* context_impl =
+        new coreml::ContextImplCoreml(std::move(receiver), this);
+    impls_.push_back(base::WrapUnique<WebNNContextImpl>(context_impl));
+    std::move(callback).Run(
+        mojom::CreateContextResult::NewContextRemote(std::move(blink_remote)));
+    return;
+  }
+#endif  // BUILDFLAG(IS_MAC)
+
+#if BUILDFLAG(WEBNN_USE_TFLITE)
+// TODO: crbug.com/41486052 - Create the TFLite context using `options`.
 #if BUILDFLAG(IS_CHROMEOS)
   auto* context_impl = new tflite::ContextImplCrOS(std::move(receiver), this);
 #else
   auto* context_impl = new tflite::ContextImplTflite(std::move(receiver), this,
                                                      std::move(options));
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
   impls_.push_back(base::WrapUnique<WebNNContextImpl>(context_impl));
   std::move(callback).Run(
       mojom::CreateContextResult::NewContextRemote(std::move(blink_remote)));
-#elif BUILDFLAG(IS_WIN)
-  // TODO: crbug.com/325612086 - Consider using TFLite to support CPU contexts
-  // on Windows.
-  if (options->device == mojom::CreateContextOptions::Device::kCpu) {
-    std::move(callback).Run(ToError<mojom::CreateContextResult>(
-        mojom::Error::Code::kNotSupportedError,
-        "The cpu device is not supported."));
-    LOG(ERROR) << "[WebNN] Service is not supported on CPU on Windows.";
-    return;
-  }
-
-  DCHECK(gpu_feature_info_.IsInitialized());
-  if (gpu_feature_info_.status_values[gpu::GPU_FEATURE_TYPE_WEBNN] !=
-      gpu::kGpuFeatureStatusEnabled) {
-    std::move(callback).Run(ToError<mojom::CreateContextResult>(
-        mojom::Error::Code::kNotSupportedError,
-        "WebNN is not compatible with GPU."));
-    LOG(ERROR) << "[WebNN] is not compatible with GPU.";
-    return;
-  }
-
-  // Get the `Adapter` instance which is created for the adapter according to
-  // the device type. At the current stage, all `ContextImplDml` share one
-  // instance for one device type.
-  base::expected<scoped_refptr<dml::Adapter>, mojom::ErrorPtr>
-      adapter_creation_result;
-  switch (options->device) {
-    case mojom::CreateContextOptions::Device::kCpu:
-      NOTREACHED_NORETURN();
-    case mojom::CreateContextOptions::Device::kGpu:
-      adapter_creation_result = GetDmlGpuAdapter(shared_context_state_.get());
-      break;
-    case mojom::CreateContextOptions::Device::kNpu:
-      adapter_creation_result =
-          dml::Adapter::GetNpuInstance(kMinDMLFeatureLevelForWebNN);
-      break;
-  }
-  if (!adapter_creation_result.has_value()) {
-    std::move(callback).Run(mojom::CreateContextResult::NewError(
-        std::move(adapter_creation_result.error())));
-    return;
-  }
-
-  scoped_refptr<dml::Adapter> adapter = adapter_creation_result.value();
-  std::unique_ptr<dml::CommandRecorder> command_recorder =
-      dml::CommandRecorder::Create(adapter->command_queue(),
-                                   adapter->dml_device());
-  if (!command_recorder) {
-    std::move(callback).Run(mojom::CreateContextResult::NewError(
-        dml::CreateError(mojom::Error::Code::kUnknownError,
-                         "Failed to create a WebNN context.")));
-    LOG(ERROR) << "[WebNN] Failed to open the command recorder.";
-    return;
-  }
-
-  // The remote sent to the renderer.
-  mojo::PendingRemote<mojom::WebNNContext> blink_remote;
-  // The receiver bound to WebNNContextImpl.
-  impls_.push_back(base::WrapUnique<WebNNContextImpl>(new dml::ContextImplDml(
-      std::move(adapter), blink_remote.InitWithNewPipeAndPassReceiver(), this,
-      std::move(command_recorder), gpu_feature_info_)));
-  std::move(callback).Run(
-      mojom::CreateContextResult::NewContextRemote(std::move(blink_remote)));
-#elif BUILDFLAG(IS_MAC)
-  if (__builtin_available(macOS 14, *)) {
-    // The remote sent to the renderer.
-    mojo::PendingRemote<mojom::WebNNContext> blink_remote;
-    // The receiver bound to WebNNContextImpl.
-    //
-    // TODO: crbug.com/41481333 - Create the CoreML context using `options`.
-    impls_.push_back(
-        base::WrapUnique<WebNNContextImpl>(new coreml::ContextImplCoreml(
-            blink_remote.InitWithNewPipeAndPassReceiver(), this)));
-    std::move(callback).Run(
-        mojom::CreateContextResult::NewContextRemote(std::move(blink_remote)));
-  } else {
-    std::move(callback).Run(ToError<mojom::CreateContextResult>(
-        mojom::Error::Code::kNotSupportedError,
-        "WebNN Service is not supported on this platform."));
-    LOG(ERROR) << "[WebNN] Service is not supported on this platform.";
-  }
 #else
   // TODO(crbug.com/40206287): Supporting WebNN Service on the platform.
   std::move(callback).Run(ToError<mojom::CreateContextResult>(
       mojom::Error::Code::kNotSupportedError,
       "WebNN Service is not supported on this platform."));
   LOG(ERROR) << "[WebNN] Service is not supported on this platform.";
-#endif
+#endif  // BUILDFLAG(WEBNN_USE_TFLITE)
 }
 
 }  // namespace webnn
diff --git a/services/webnn/webnn_context_provider_impl_unittest.cc b/services/webnn/webnn_context_provider_impl_unittest.cc
index b7a3555..2eada12 100644
--- a/services/webnn/webnn_context_provider_impl_unittest.cc
+++ b/services/webnn/webnn_context_provider_impl_unittest.cc
@@ -79,8 +79,7 @@
 
 #if BUILDFLAG(IS_WIN)
 
-// The DirectML implementation does not support CPU execution.
-TEST_F(WebNNContextProviderImplTest, CPUNotSupported) {
+TEST_F(WebNNContextProviderImplTest, CPUIsSupported) {
   mojo::Remote<mojom::WebNNContextProvider> provider_remote;
 
   WebNNContextProviderImpl::CreateForTesting(
@@ -94,10 +93,8 @@
           /*thread_count_hint=*/0),
       future.GetCallback());
   mojom::CreateContextResultPtr result = future.Take();
-  ASSERT_TRUE(result->is_error());
-  const mojom::ErrorPtr& create_context_error = result->get_error();
-  EXPECT_EQ(create_context_error->code, mojom::Error::Code::kNotSupportedError);
-  EXPECT_EQ(create_context_error->message, "The cpu device is not supported.");
+  ASSERT_TRUE(result->is_context_remote());
+  EXPECT_TRUE(result->get_context_remote().is_valid());
 }
 
 // Checking for GPU compatibility is Windows-specific because only the DirectML
diff --git a/services/webnn/webnn_graph_impl_backend_test.cc b/services/webnn/webnn_graph_impl_backend_test.cc
index b984a7c..05a99cf 100644
--- a/services/webnn/webnn_graph_impl_backend_test.cc
+++ b/services/webnn/webnn_graph_impl_backend_test.cc
@@ -371,7 +371,8 @@
 }
 #endif  // BUILDFLAG(IS_MAC)
 
-#if BUILDFLAG(WEBNN_USE_TFLITE)
+// TODO(crbug.com/325612086): Parameterize these tests for different backends.
+#if BUILDFLAG(WEBNN_USE_TFLITE) && !BUILDFLAG(IS_WIN)
 class WebNNGraphImplBackendTest : public testing::Test {
  public:
   WebNNGraphImplBackendTest()
@@ -420,7 +421,7 @@
     GTEST_SKIP() << "Skipping test because the operator is not yet supported.";
   }
 }
-#endif  // BUILDFLAG(WEBNN_USE_TFLITE)
+#endif  // BUILDFLAG(WEBNN_USE_TFLITE) && !BUILDFLAG(IS_WIN)
 
 template <typename T>
 struct ArgMinMaxTester {
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index dc67232..c1f64c2 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5487,9 +5487,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5499,8 +5499,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -5643,9 +5643,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5655,8 +5655,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index c2ed94a1..1416d7d 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -19664,9 +19664,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19676,8 +19676,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -19820,9 +19820,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19832,8 +19832,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index f21ecfb1..6f5ba95 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41840,9 +41840,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41851,8 +41851,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -41990,9 +41990,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42001,8 +42001,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -43339,9 +43339,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43351,8 +43351,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -43495,9 +43495,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43507,8 +43507,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -44820,9 +44820,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44831,8 +44831,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -44970,9 +44970,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_v127.0.6490.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44981,8 +44981,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 24e13b9..9830c8a 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -15763,12 +15763,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_v127.0.6490.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15778,8 +15778,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
@@ -15939,12 +15939,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_v127.0.6490.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 127.0.6490.0",
+        "description": "Run with ash-chrome version 127.0.6491.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15954,8 +15954,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v127.0.6490.0",
-              "revision": "version:127.0.6490.0"
+              "location": "lacros_version_skew_tests_v127.0.6491.0",
+              "revision": "version:127.0.6491.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 7a12c5a..89a195a 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -267,16 +267,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 127.0.6490.0',
+    'description': 'Run with ash-chrome version 127.0.6491.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6490.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v127.0.6491.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v127.0.6490.0',
-          'revision': 'version:127.0.6490.0',
+          'location': 'lacros_version_skew_tests_v127.0.6491.0',
+          'revision': 'version:127.0.6491.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f5afae7..d9d6e98 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4714,6 +4714,29 @@
             ]
         }
     ],
+    "ComposeOnDeviceModel": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "on_device_retract_unsafe_content": "false",
+                        "on_device_text_safety_token_interval": "10"
+                    },
+                    "enable_features": [
+                        "OptimizationGuideComposeOnDeviceEval",
+                        "OptimizationGuideOnDeviceModel",
+                        "TextSafetyClassifier"
+                    ]
+                }
+            ]
+        }
+    ],
     "ComposeUiRefinement": [
         {
             "platforms": [
@@ -17573,7 +17596,7 @@
                 {
                     "name": "Enabled",
                     "params": {
-                        "SafetyCheckMagicStackAutorunHoursThreshold": "24"
+                        "SafetyCheckMagicStackAutorunHoursThreshold": "720"
                     },
                     "enable_features": [
                         "SafetyCheckMagicStack"
@@ -19289,7 +19312,6 @@
                         "VisualQuerySuggestions"
                     ],
                     "disable_features": [
-                        "CompanionEnableNewBadgesInContextMenu",
                         "SideSearch"
                     ]
                 }
@@ -20279,27 +20301,6 @@
             ]
         }
     ],
-    "TextSafetyClassifier": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "on_device_retract_unsafe_content": "false",
-                        "on_device_text_safety_token_interval": "10"
-                    },
-                    "enable_features": [
-                        "TextSafetyClassifier"
-                    ]
-                }
-            ]
-        }
-    ],
     "TheoraVideoCodec": [
         {
             "platforms": [
diff --git a/third_party/blink/common/shared_storage/module_script_downloader.cc b/third_party/blink/common/shared_storage/module_script_downloader.cc
index d698607..5755db0d 100644
--- a/third_party/blink/common/shared_storage/module_script_downloader.cc
+++ b/third_party/blink/common/shared_storage/module_script_downloader.cc
@@ -133,7 +133,8 @@
           net::ErrorToString(simple_url_loader->NetError()).c_str());
     }
     std::move(module_script_downloader_callback_)
-        .Run(/*body=*/nullptr, error_message);
+        .Run(/*body=*/nullptr, error_message,
+             simple_url_loader->TakeResponseInfo());
     return;
   }
 
@@ -143,7 +144,8 @@
         .Run(/*body=*/nullptr,
              base::StringPrintf(
                  "Rejecting load of %s due to unexpected MIME type.",
-                 source_url_.spec().c_str()));
+                 source_url_.spec().c_str()),
+             simple_url_loader->TakeResponseInfo());
     return;
   }
 
@@ -152,13 +154,15 @@
         .Run(/*body=*/nullptr,
              base::StringPrintf(
                  "Rejecting load of %s due to unexpected charset.",
-                 source_url_.spec().c_str()));
+                 source_url_.spec().c_str()),
+             simple_url_loader->TakeResponseInfo());
     return;
   }
 
   // All OK!
   std::move(module_script_downloader_callback_)
-      .Run(std::move(body), /*error_message=*/{});
+      .Run(std::move(body), /*error_message=*/{},
+           simple_url_loader->TakeResponseInfo());
 }
 
 void ModuleScriptDownloader::OnRedirect(
@@ -172,8 +176,10 @@
   simple_url_loader_.reset();
 
   std::move(module_script_downloader_callback_)
-      .Run(/*body=*/nullptr, base::StringPrintf("Unexpected redirect on %s.",
-                                                source_url_.spec().c_str()));
+      .Run(/*body=*/nullptr,
+           base::StringPrintf("Unexpected redirect on %s.",
+                              source_url_.spec().c_str()),
+           nullptr);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/common/shared_storage/module_script_downloader_unittest.cc b/third_party/blink/common/shared_storage/module_script_downloader_unittest.cc
index f5f81fe..df9c885142 100644
--- a/third_party/blink/common/shared_storage/module_script_downloader_unittest.cc
+++ b/third_party/blink/common/shared_storage/module_script_downloader_unittest.cc
@@ -89,12 +89,15 @@
   }
 
  protected:
-  void DownloadCompleteCallback(std::unique_ptr<std::string> body,
-                                std::string error) {
+  void DownloadCompleteCallback(
+      std::unique_ptr<std::string> body,
+      std::string error,
+      network::mojom::URLResponseHeadPtr response_head) {
     DCHECK(!body_);
     DCHECK(run_loop_);
     body_ = std::move(body);
     error_ = std::move(error);
+    response_head_ = std::move(response_head);
     EXPECT_EQ(error_.empty(), !!body_);
     run_loop_->Quit();
   }
@@ -106,6 +109,7 @@
   std::unique_ptr<base::RunLoop> run_loop_;
   std::unique_ptr<std::string> body_;
   std::string error_;
+  network::mojom::URLResponseHeadPtr response_head_;
 
   network::TestURLLoaderFactory url_loader_factory_;
 };
@@ -113,12 +117,13 @@
 TEST_F(ModuleScriptDownloaderTest, NetworkError) {
   network::URLLoaderCompletionStatus status;
   status.error_code = net::ERR_FAILED;
-  url_loader_factory_.AddResponse(url_, nullptr /* head */, kAsciiResponseBody,
+  url_loader_factory_.AddResponse(url_, /*head=*/nullptr, kAsciiResponseBody,
                                   status);
   EXPECT_FALSE(RunRequest());
   EXPECT_EQ(
       "Failed to load https://url.test/script.js error = net::ERR_FAILED.",
       error_);
+  EXPECT_FALSE(response_head_);
 }
 
 // HTTP 404 responses are treated as failures.
@@ -131,6 +136,8 @@
   EXPECT_EQ(
       "Failed to load https://url.test/script.js HTTP status = 404 Not Found.",
       error_);
+  EXPECT_TRUE(response_head_);
+  EXPECT_EQ(response_head_->mime_type, kJavascriptMimeType);
 }
 
 // Redirect responses are treated as failures.
@@ -149,6 +156,7 @@
               kAsciiResponseBody, net::HTTP_OK, std::move(redirects));
   EXPECT_FALSE(RunRequest());
   EXPECT_EQ("Unexpected redirect on https://url.test/script.js.", error_);
+  EXPECT_FALSE(response_head_);
 }
 
 TEST_F(ModuleScriptDownloaderTest, Success) {
diff --git a/third_party/blink/public/common/shared_storage/module_script_downloader.h b/third_party/blink/public/common/shared_storage/module_script_downloader.h
index 5dbab222..7090177 100644
--- a/third_party/blink/public/common/shared_storage/module_script_downloader.h
+++ b/third_party/blink/public/common/shared_storage/module_script_downloader.h
@@ -9,6 +9,7 @@
 #include "net/url_request/redirect_info.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
 #include "services/network/public/mojom/url_response_head.mojom-forward.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/common_export.h"
 
 namespace network {
@@ -21,10 +22,12 @@
 // responses.
 class BLINK_COMMON_EXPORT ModuleScriptDownloader {
  public:
-  // Passes in nullptr on failure. Always invoked asynchronously.
+  // Passes in nullptr for `response_body` on failure. Always invoked
+  // asynchronously.
   using ModuleScriptDownloaderCallback =
       base::OnceCallback<void(std::unique_ptr<std::string> response_body,
-                              std::string error_message)>;
+                              std::string error_message,
+                              network::mojom::URLResponseHeadPtr)>;
 
   // Starts loading the worklet module script on construction. Callback will be
   // invoked asynchronously once the data has been fetched or an error has
diff --git a/third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom b/third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom
index 1aca7fe..0321d2c 100644
--- a/third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom
+++ b/third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom
@@ -4,11 +4,12 @@
 
 module blink.mojom;
 
-import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
-import "third_party/blink/public/mojom/origin_trial_feature/origin_trial_feature.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
-import "url/mojom/url.mojom";
+import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
+import "third_party/blink/public/mojom/loader/code_cache.mojom";
+import "third_party/blink/public/mojom/origin_trial_feature/origin_trial_feature.mojom";
 import "url/mojom/origin.mojom";
+import "url/mojom/url.mojom";
 
 // Interface implemented in the browser for the worklets to forward
 // ready-for-inspection notification.
@@ -31,5 +32,6 @@
   array<OriginTrialFeature> origin_trial_features;
   mojo_base.mojom.UnguessableToken devtools_token;
   pending_remote<WorkletDevToolsHost> devtools_host;
+  pending_remote<CodeCacheHost> code_cache_host;
   bool wait_for_debugger;
 };
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
index bef437e..94d9327 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
@@ -667,7 +667,12 @@
             classic_script->SourceUrl(), classic_script->StartPosition(),
             produce_cache_options);
       }
-      if (compile_options == v8::ScriptCompiler::kProduceCompileHints) {
+
+      // `SharedStorageWorkletGlobalScope` has a out-of-process worklet
+      // architecture that does not have a `page` associated.
+      // TODO(crbug.com/340920456): Figure out what should be done here.
+      if (compile_options == v8::ScriptCompiler::kProduceCompileHints &&
+          !execution_context->IsSharedStorageWorkletGlobalScope()) {
         CHECK(page);
         CHECK(frame);
         // We can produce both crowdsourced and local compile hints at the
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 600030c..01111bef 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3373,9 +3373,26 @@
     loading_for_print_ = loading_for_print_ || view->LoadAllLazyLoadedIframes();
   }
 
+  loading_for_print_ =
+      loading_for_print_ || InitiateStyleOrLayoutDependentLoadForPrint();
+
   return loading_for_print_;
 }
 
+bool Document::InitiateStyleOrLayoutDependentLoadForPrint() {
+  if (auto* view = View()) {
+    view->AdjustMediaTypeForPrinting(true);
+    UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
+
+    view->AdjustMediaTypeForPrinting(false);
+    UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
+
+    return !ShouldComplete();
+  }
+
+  return false;
+}
+
 void Document::SetPrinting(PrintingState state) {
   bool was_printing = Printing();
   printing_ = state;
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index cce9847..51f66da 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -2333,6 +2333,10 @@
 
   ResizeObserver& GetLazyLoadedAutoSizedImgObserver();
 
+  // Initiates data loading for print that is dependent on style or layout.
+  // Returns true if data loading has started.
+  bool InitiateStyleOrLayoutDependentLoadForPrint();
+
   // Mutable because the token is lazily-generated on demand if no token is
   // explicitly set.
   mutable std::optional<DocumentToken> token_;
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.cc b/third_party/blink/renderer/core/execution_context/execution_context.cc
index fa87ba2..bebe350 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.cc
+++ b/third_party/blink/renderer/core/execution_context/execution_context.cc
@@ -124,6 +124,13 @@
   }
 
   DCHECK(execution_context->IsWorkletGlobalScope());
+
+  if (execution_context->IsSharedStorageWorkletGlobalScope()) {
+    auto* global_scope =
+        DynamicTo<WorkerOrWorkletGlobalScope>(execution_context);
+    return global_scope->GetCodeCacheHost();
+  }
+
   return nullptr;
 }
 
diff --git a/third_party/blink/renderer/core/fetch/request.cc b/third_party/blink/renderer/core/fetch/request.cc
index 81eff71..10f8036 100644
--- a/third_party/blink/renderer/core/fetch/request.cc
+++ b/third_party/blink/renderer/core/fetch/request.cc
@@ -57,6 +57,7 @@
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 #include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
 #include "third_party/blink/renderer/platform/weborigin/referrer.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
 
 namespace blink {
@@ -579,7 +580,7 @@
           " in secure contexts.");
       return nullptr;
     }
-    if (origin->IsOpaque()) {
+    if (SecurityOrigin::Create(request->Url())->IsOpaque()) {
       exception_state.ThrowTypeError(
           "sharedStorageWritable: sharedStorage operations are not available"
           " for opaque origins.");
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index 694bb3d4..0f401bb3 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -414,12 +414,6 @@
           mojom::blink::ConsoleMessageLevel::kError,
           WebString::FromUTF8("sharedStorageWritable: sharedStorage operations "
                               "are only available in secure contexts.")));
-    } else if (GetExecutionContext()->GetSecurityOrigin()->IsOpaque()) {
-      GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
-          mojom::blink::ConsoleMessageSource::kOther,
-          mojom::blink::ConsoleMessageLevel::kError,
-          WebString::FromUTF8("sharedStorageWritable: sharedStorage operations "
-                              "are not available for opaque origins.")));
     } else if (!params.new_value.IsNull()) {
       UseCounter::Count(GetDocument(),
                         WebFeature::kSharedStorageAPI_Image_Attribute);
diff --git a/third_party/blink/renderer/core/html/html_plugin_element.cc b/third_party/blink/renderer/core/html/html_plugin_element.cc
index ab5c579..56196a6 100644
--- a/third_party/blink/renderer/core/html/html_plugin_element.cc
+++ b/third_party/blink/renderer/core/html/html_plugin_element.cc
@@ -936,9 +936,11 @@
       OriginalStyleForLayoutObject(style_recalc_context);
   if (IsImageType() && !GetLayoutObject() && style &&
       LayoutObjectIsNeeded(*style)) {
-    if (!image_loader_)
+    if (!image_loader_) {
       image_loader_ = MakeGarbageCollected<HTMLImageLoader>(this);
-    image_loader_->UpdateFromElement();
+    }
+    image_loader_->UpdateFromElement(ImageLoader::kUpdateNormal,
+                                     /* force_blocking */ true);
   }
   return style;
 }
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index b555bd3..4bc6c0c 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -3657,7 +3657,7 @@
   // these steps:
   if (EndedPlayback(LoopCondition::kIgnored)) {
     // If the media element has a loop attribute specified
-    if (Loop() && EarliestPossiblePosition() != CurrentPlaybackPosition()) {
+    if (Loop()) {
       //  then seek to the earliest possible position of the media resource and
       //  abort these steps.
       Seek(EarliestPossiblePosition());
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index 5986a8a35..7fcc11e 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -528,10 +528,7 @@
             RuntimeEnabledFeatures::SharedStorageAPIM118Enabled(
                 GetElement()->GetExecutionContext()) &&
             GetElement()->GetExecutionContext()->IsSecureContext() &&
-            !GetElement()
-                 ->GetExecutionContext()
-                 ->GetSecurityOrigin()
-                 ->IsOpaque();
+            !SecurityOrigin::Create(url)->IsOpaque();
         resource_request.SetSharedStorageWritableOptedIn(
             shared_storage_writable_opted_in);
       }
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
index 38422d6..4fec233 100644
--- a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
@@ -98,7 +98,10 @@
         client_provided_global_scope_creation_params->devtools_token,
         /*worker_settings=*/nullptr,
         /*v8_cache_options=*/mojom::blink::V8CacheOptions::kDefault,
-        /*module_responses_map=*/nullptr);
+        /*module_responses_map=*/nullptr,
+        /*browser_interface_broker=*/mojo::NullRemote(),
+        std::move(
+            client_provided_global_scope_creation_params->code_cache_host));
 
     auto devtools_params = std::make_unique<WorkerDevToolsParams>();
     devtools_params->devtools_worker_token =
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 8c805e7..da3116a7 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -6853,9 +6853,6 @@
       Scroll(action_data.action);
       return true;
     case ax::mojom::blink::Action::kStitchChildTree:
-      if (action_data.target_node_id == static_cast<int32_t>(AXID())) {
-        return false;
-      }
       if (action_data.child_tree_id == ui::AXTreeIDUnknown()) {
         return false;  // No child tree ID provided.;
       }
diff --git a/third_party/blink/renderer/modules/exported/web_shared_storage_worklet_thread_impl.cc b/third_party/blink/renderer/modules/exported/web_shared_storage_worklet_thread_impl.cc
index d42ba1a..fddb1f1 100644
--- a/third_party/blink/renderer/modules/exported/web_shared_storage_worklet_thread_impl.cc
+++ b/third_party/blink/renderer/modules/exported/web_shared_storage_worklet_thread_impl.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/exported/web_shared_storage_worklet_thread_impl.h"
 
+#include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
 #include "third_party/blink/public/mojom/origin_trial_feature/origin_trial_feature.mojom-shared.h"
 #include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom-blink.h"
 #include "third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom-blink.h"
@@ -26,6 +27,8 @@
       global_scope_creation_params->devtools_token,
       CrossVariantMojoRemote<mojom::WorkletDevToolsHostInterfaceBase>(
           std::move(global_scope_creation_params->devtools_host)),
+      CrossVariantMojoRemote<mojom::CodeCacheHostInterfaceBase>(
+          std::move(global_scope_creation_params->code_cache_host)),
       global_scope_creation_params->wait_for_debugger);
 }
 
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.cc
index 06632d0..d973a95 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.cc
@@ -32,13 +32,16 @@
 }
 
 void MediaControlTimeDisplayElement::SetCurrentValue(double time) {
+  if (current_value_ == time) {
+    return;
+  }
   current_value_ = time;
   String formatted_time = FormatTime();
   setInnerText(formatted_time);
 }
 
 double MediaControlTimeDisplayElement::CurrentValue() const {
-  return current_value_;
+  return current_value_.value_or(0);
 }
 
 gfx::Size MediaControlTimeDisplayElement::GetSizeOrDefault() const {
@@ -54,7 +57,7 @@
 }
 
 String MediaControlTimeDisplayElement::FormatTime() const {
-  return MediaControlsSharedHelpers::FormatTime(current_value_);
+  return MediaControlsSharedHelpers::FormatTime(CurrentValue());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.h
index 0735779..bcc9422 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.h
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_time_display_element.h
@@ -30,7 +30,7 @@
  private:
   void SetAriaLabel();
 
-  double current_value_ = 0;
+  std::optional<double> current_value_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/ml/webnn/features.gni b/third_party/blink/renderer/modules/ml/webnn/features.gni
index 54826759..a13d1158 100644
--- a/third_party/blink/renderer/modules/ml/webnn/features.gni
+++ b/third_party/blink/renderer/modules/ml/webnn/features.gni
@@ -4,9 +4,6 @@
 
 import("//build/config/chrome_build.gni")
 declare_args() {
-  # This enables building WebNN with XNNPACK. Currently only available for
-  # Windows and macOS on x64, x86 and arm64.
-  build_webnn_with_xnnpack =
-      (is_win || is_mac) &&
-      (current_cpu == "x64" || current_cpu == "x86" || current_cpu == "arm64")
+  # This enables building WebNN with XNNPACK.
+  build_webnn_with_xnnpack = is_mac
 }
diff --git a/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.cc b/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.cc
index c128325..034f4ea 100644
--- a/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.cc
+++ b/third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.cc
@@ -77,18 +77,15 @@
 
   connection_->Init(std::move(connection_remote),
                     std::move(connection_receiver));
-
-  resolver_->Resolve(connection_);
 #if BUILDFLAG(IS_ANDROID)
   PresentationMetrics::RecordPresentationConnectionResult(request_, true);
 #endif
+
+  resolver_->Resolve(connection_);
 }
 
 void PresentationConnectionCallbacks::OnError(
     const mojom::blink::PresentationError& error) {
-  resolver_->Reject(CreatePresentationError(
-      resolver_->GetScriptState()->GetIsolate(), error));
-  connection_ = nullptr;
 #if BUILDFLAG(IS_ANDROID)
   // These two error types are not recorded because it's likely that they don't
   // represent an actual error.
@@ -99,6 +96,10 @@
     PresentationMetrics::RecordPresentationConnectionResult(request_, false);
   }
 #endif
+
+  resolver_->Reject(CreatePresentationError(
+      resolver_->GetScriptState()->GetIsolate(), error));
+  connection_ = nullptr;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/shared_storage/BUILD.gn b/third_party/blink/renderer/modules/shared_storage/BUILD.gn
index ea8bcb69..ef7c52b 100644
--- a/third_party/blink/renderer/modules/shared_storage/BUILD.gn
+++ b/third_party/blink/renderer/modules/shared_storage/BUILD.gn
@@ -37,6 +37,7 @@
 
   deps = [
     "//components/aggregation_service",
+    "//services/network/public/cpp",
     "//third_party/blink/renderer/modules/crypto:crypto",
   ]
 
diff --git a/third_party/blink/renderer/modules/shared_storage/DEPS b/third_party/blink/renderer/modules/shared_storage/DEPS
index 062a227..a60297d 100644
--- a/third_party/blink/renderer/modules/shared_storage/DEPS
+++ b/third_party/blink/renderer/modules/shared_storage/DEPS
@@ -7,6 +7,7 @@
     "+third_party/blink/renderer/modules/crypto",
     "+third_party/blink/renderer/modules/v8",
     "+third_party/blink/renderer/core",
+    "+services/network/public/cpp",
     "+gin/converter.h",
     "+mojo/public/cpp/base/string16_mojom_traits.h",
     "+url",
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.cc b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.cc
index 46ea370..5818579 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.cc
@@ -15,6 +15,7 @@
 #include "gin/converter.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/blink/public/common/features.h"
@@ -23,6 +24,7 @@
 #include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom-blink.h"
 #include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom-blink.h"
 #include "third_party/blink/public/platform/cross_variant_mojo_util.h"
+#include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
@@ -41,6 +43,8 @@
 #include "third_party/blink/renderer/modules/shared_storage/shared_storage_operation_definition.h"
 #include "third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_thread.h"
 #include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h"
+#include "third_party/blink/renderer/platform/loader/fetch/script_cached_metadata_handler.h"
+#include "third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "v8/include/v8-context.h"
 #include "v8/include/v8-isolate.h"
@@ -356,6 +360,24 @@
       WTF::BindOnce(&SharedStorageWorkletGlobalScope::OnModuleScriptDownloaded,
                     WrapWeakPersistent(this), script_source_url,
                     std::move(callback)));
+
+  // Create a ResourceRequest and populate only the fields needed by
+  // `CodeCacheFetcher`.
+  //
+  // TODO(yaoxia): Move `code_cache_fetcher_` to `ModuleScriptDownloader` to
+  // avoid replicating the ResourceRequest here. This isn't viable today because
+  // `ModuleScriptDownloader` lives in blink/public/common, due to its use of
+  // `network::SimpleURLLoader`.
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GURL(script_source_url);
+  resource_request->destination =
+      network::mojom::RequestDestination::kSharedStorageWorklet;
+
+  CHECK(GetCodeCacheHost());
+  code_cache_fetcher_ = CodeCacheFetcher::TryCreateAndStart(
+      *resource_request, *GetCodeCacheHost(),
+      WTF::BindOnce(&SharedStorageWorkletGlobalScope::DidReceiveCachedCode,
+                    WrapWeakPersistent(this)));
 }
 
 void SharedStorageWorkletGlobalScope::RunURLSelectionOperation(
@@ -579,9 +601,31 @@
     const KURL& script_source_url,
     mojom::blink::SharedStorageWorkletService::AddModuleCallback callback,
     std::unique_ptr<std::string> response_body,
-    std::string error_message) {
+    std::string error_message,
+    network::mojom::URLResponseHeadPtr response_head) {
   module_script_downloader_.reset();
 
+  // If we haven't received the code cache data, defer handing the response.
+  if (code_cache_fetcher_ && code_cache_fetcher_->is_waiting()) {
+    handle_script_download_response_after_code_cache_response_ = WTF::BindOnce(
+        &SharedStorageWorkletGlobalScope::OnModuleScriptDownloaded,
+        WrapPersistent(this), script_source_url, std::move(callback),
+        std::move(response_body), std::move(error_message),
+        std::move(response_head));
+    return;
+  }
+
+  // Note: There's no need to check the `cached_metadata` param from
+  // `URLLoaderClient::OnReceiveResponse`. This param is only set for data
+  // fetched from ServiceWorker caches. Today, shared storage script fetch
+  // cannot be intercepted by service workers.
+
+  std::optional<mojo_base::BigBuffer> cached_metadata =
+      (code_cache_fetcher_ && response_head)
+          ? code_cache_fetcher_->TakeCodeCacheForResponse(*response_head)
+          : std::nullopt;
+  code_cache_fetcher_.reset();
+
   mojom::blink::SharedStorageWorkletService::AddModuleCallback
       add_module_finished_callback = std::move(callback).Then(WTF::BindOnce(
           &SharedStorageWorkletGlobalScope::RecordAddModuleFinished,
@@ -594,6 +638,7 @@
   }
 
   DCHECK(error_message.empty());
+  DCHECK(response_head);
 
   if (!ScriptController()) {
     std::move(add_module_finished_callback)
@@ -601,16 +646,45 @@
     return;
   }
 
-  ScriptState* script_state = ScriptController()->GetScriptState();
-  DCHECK(script_state);
+  WebURLResponse response =
+      WebURLResponse::Create(script_source_url, *response_head.get(),
+                             /*report_security_info=*/false, /*request_id=*/0);
+
+  const ResourceResponse& resource_response = response.ToResourceResponse();
+
+  // Create a `ScriptCachedMetadataHandler` for http family URLs. This
+  // replicates the core logic from `ScriptResource::ResponseReceived`,
+  // simplified since shared storage doesn't require
+  // `ScriptCachedMetadataHandlerWithHashing` which is only used for certain
+  // schemes.
+  ScriptCachedMetadataHandler* cached_metadata_handler = nullptr;
+
+  if (script_source_url.ProtocolIsInHTTPFamily()) {
+    std::unique_ptr<CachedMetadataSender> sender = CachedMetadataSender::Create(
+        resource_response, mojom::blink::CodeCacheType::kJavascript,
+        GetSecurityOrigin());
+
+    cached_metadata_handler = MakeGarbageCollected<ScriptCachedMetadataHandler>(
+        WTF::TextEncoding(response_head->charset.c_str()), std::move(sender));
+
+    if (cached_metadata) {
+      cached_metadata_handler->SetSerializedCachedMetadata(
+          std::move(*cached_metadata));
+    }
+  }
 
   // TODO(crbug.com/1419253): Using a classic script with the custom script
   // loader is tentative. Eventually, this should migrate to the blink-worklet's
   // script loading infrastructure.
-  ClassicScript* worker_script =
-      ClassicScript::Create(String(*response_body),
-                            /*source_url=*/script_source_url,
-                            /*base_url=*/KURL(), ScriptFetchOptions());
+  ClassicScript* worker_script = ClassicScript::Create(
+      String(*response_body),
+      /*source_url=*/script_source_url,
+      /*base_url=*/KURL(), ScriptFetchOptions(),
+      ScriptSourceLocationType::kUnknown, SanitizeScriptErrors::kSanitize,
+      cached_metadata_handler);
+
+  ScriptState* script_state = ScriptController()->GetScriptState();
+  DCHECK(script_state);
 
   v8::HandleScope handle_scope(script_state->GetIsolate());
   ScriptEvaluationResult result =
@@ -635,6 +709,12 @@
       .Run(true, /*error_message=*/g_empty_string);
 }
 
+void SharedStorageWorkletGlobalScope::DidReceiveCachedCode() {
+  if (handle_script_download_response_after_code_cache_response_) {
+    std::move(handle_script_download_response_after_code_cache_response_).Run();
+  }
+}
+
 void SharedStorageWorkletGlobalScope::RecordAddModuleFinished() {
   add_module_finished_ = true;
 }
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.h b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.h
index e497c01..af29597 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.h
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_global_scope.h
@@ -9,6 +9,7 @@
 
 #include "base/check.h"
 #include "base/functional/callback_forward.h"
+#include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -36,10 +37,12 @@
 namespace blink {
 
 struct GlobalScopeCreationParams;
+class CodeCacheFetcher;
 class ModuleScriptDownloader;
 class SharedStorageOperationDefinition;
 class V8NoArgumentConstructor;
 class SharedStorage;
+class ScriptCachedMetadataHandler;
 class PrivateAggregation;
 class Crypto;
 
@@ -141,7 +144,10 @@
       const KURL& script_source_url,
       mojom::blink::SharedStorageWorkletService::AddModuleCallback callback,
       std::unique_ptr<std::string> response_body,
-      std::string error_message);
+      std::string error_message,
+      network::mojom::URLResponseHeadPtr response_head);
+
+  void DidReceiveCachedCode();
 
   void RecordAddModuleFinished();
 
@@ -182,6 +188,8 @@
 
   int64_t operation_counter_ = 0;
 
+  base::OnceClosure handle_script_download_response_after_code_cache_response_;
+
   // `receiver_`'s disconnect handler explicitly deletes the worklet thread
   // object that owns this service, thus deleting `this` upon disconnect. To
   // ensure that the worklet thread object and this service are not leaked,
@@ -215,6 +223,8 @@
 
   std::unique_ptr<ModuleScriptDownloader> module_script_downloader_;
 
+  scoped_refptr<CodeCacheFetcher> code_cache_fetcher_;
+
   // This is associated because on the client side (i.e. worklet host), we want
   // the call-in methods (e.g. storage access) and the callback methods
   // (e.g. finish of a run-operation) to preserve their invocation order. This
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_unittest.cc b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_unittest.cc
index c4d5462..c48ad1a6 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_unittest.cc
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet_unittest.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom-blink.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom-blink.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
+#include "third_party/blink/public/mojom/loader/code_cache.mojom-blink.h"
 #include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom-blink.h"
 #include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-blink.h"
@@ -60,6 +61,8 @@
 
 constexpr char kModuleScriptSource[] = "https://foo.com/module_script.js";
 constexpr char kMaxChar16StringLengthPlusOneLiteral[] = "2621441";
+constexpr base::Time kScriptResponseTime =
+    base::Time::FromDeltaSinceWindowsEpoch(base::Days(100));
 
 struct VoidOperationResult {
   bool success = true;
@@ -304,6 +307,75 @@
   mojo::ReceiverSet<blink::mojom::blink::PrivateAggregationHost> receiver_set_;
 };
 
+class MockMojomCoceCacheHost : public blink::mojom::blink::CodeCacheHost {
+ public:
+  MockMojomCoceCacheHost() = default;
+
+  void FlushForTesting() { receiver_set_.FlushForTesting(); }
+
+  mojo::ReceiverSet<blink::mojom::blink::CodeCacheHost>& receiver_set() {
+    return receiver_set_;
+  }
+
+  // blink::mojom::blink::CoceCacheHost:
+  void DidGenerateCacheableMetadata(mojom::CodeCacheType cache_type,
+                                    const KURL& url,
+                                    base::Time expected_response_time,
+                                    mojo_base::BigBuffer data) override {
+    did_generate_cacheable_metadata_count_++;
+
+    // Store the time and data. This mirrors the real-world behavior.
+    response_time_ = expected_response_time;
+    data_ = std::move(data);
+  }
+
+  void FetchCachedCode(mojom::CodeCacheType cache_type,
+                       const KURL& url,
+                       FetchCachedCodeCallback callback) override {
+    fetch_cached_code_count_++;
+    std::move(callback).Run(response_time_, data_.Clone());
+  }
+
+  void ClearCodeCacheEntry(mojom::CodeCacheType cache_type,
+                           const KURL& url) override {
+    clear_code_cache_entry_count_++;
+  }
+
+  void DidGenerateCacheableMetadataInCacheStorage(
+      const KURL& url,
+      base::Time expected_response_time,
+      mojo_base::BigBuffer data,
+      const String& cache_storage_cache_name) override {
+    NOTREACHED();
+  }
+
+  void OverrideFetchCachedCodeResult(base::Time response_time,
+                                     mojo_base::BigBuffer data) {
+    response_time_ = response_time;
+    data_ = std::move(data);
+  }
+
+  size_t did_generate_cacheable_metadata_count() const {
+    return did_generate_cacheable_metadata_count_;
+  }
+
+  size_t fetch_cached_code_count() const { return fetch_cached_code_count_; }
+
+  size_t clear_code_cache_entry_count() const {
+    return clear_code_cache_entry_count_;
+  }
+
+ private:
+  base::Time response_time_;
+  mojo_base::BigBuffer data_;
+
+  size_t did_generate_cacheable_metadata_count_ = 0;
+  size_t fetch_cached_code_count_ = 0;
+  size_t clear_code_cache_entry_count_ = 0;
+
+  mojo::ReceiverSet<blink::mojom::blink::CodeCacheHost> receiver_set_;
+};
+
 std::unique_ptr<GlobalScopeCreationParams> MakeTestGlobalScopeCreationParams() {
   return std::make_unique<GlobalScopeCreationParams>(
       KURL("https://foo.com"),
@@ -332,7 +404,9 @@
 
 class SharedStorageWorkletTest : public PageTestBase {
  public:
-  SharedStorageWorkletTest() = default;
+  SharedStorageWorkletTest() {
+    mock_code_cache_host_ = std::make_unique<MockMojomCoceCacheHost>();
+  }
 
   void TearDown() override {
     // Shut down the worklet gracefully. Otherwise, there could the a data race
@@ -358,6 +432,7 @@
     auto head = network::mojom::URLResponseHead::New();
     head->mime_type = mime_type;
     head->charset = "us-ascii";
+    head->response_time = kScriptResponseTime;
 
     proxied_url_loader_factory.AddResponse(
         GURL(kModuleScriptSource), std::move(head),
@@ -451,6 +526,7 @@
   std::unique_ptr<TestWorkletDevToolsHost> test_worklet_devtools_host_;
   std::unique_ptr<MockMojomPrivateAggregationHost>
       mock_private_aggregation_host_;
+  std::unique_ptr<MockMojomCoceCacheHost> mock_code_cache_host_;
 
   base::HistogramTester histogram_tester_;
 
@@ -512,6 +588,16 @@
     test_worklet_devtools_host_ = std::make_unique<TestWorkletDevToolsHost>(
         std::move(pending_devtools_host_receiver));
 
+    mojo::PendingRemote<mojom::blink::CodeCacheHost>
+        pending_code_cache_host_remote;
+    mojo::PendingReceiver<mojom::blink::CodeCacheHost>
+        pending_code_cache_host_receiver =
+            pending_code_cache_host_remote.InitWithNewPipeAndPassReceiver();
+
+    mock_code_cache_host_->receiver_set().Add(
+        mock_code_cache_host_.get(),
+        std::move(pending_code_cache_host_receiver));
+
     messaging_proxy_ = MakeGarbageCollected<SharedStorageWorkletMessagingProxy>(
         base::SingleThreadTaskRunner::GetCurrentDefault(),
         CrossVariantMojoReceiver<
@@ -524,6 +610,7 @@
             Vector({mojom::blink::OriginTrialFeature::kSharedStorageAPI}),
             /*devtools_worker_token=*/base::UnguessableToken(),
             std::move(pending_devtools_host_remote),
+            std::move(pending_code_cache_host_remote),
             /*wait_for_debugger=*/false),
         worklet_terminated_future_.GetCallback());
 
@@ -584,6 +671,135 @@
             "unexpected MIME type.");
 }
 
+TEST_F(SharedStorageWorkletTest,
+       CodeCache_NoClearDueToEmptyCache_NoGenerateData) {
+  // Configure to return empty data, with matched response time.
+  mock_code_cache_host_->OverrideFetchCachedCodeResult(
+      /*response_time=*/kScriptResponseTime,
+      /*data=*/std::vector<uint8_t>());
+
+  AddModule(/*script_content=*/"");
+
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 1u);
+
+  // No invalidation was triggered, as `FetchCachedCode()` responded with empty
+  // data.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 0u);
+
+  // No code cache was generated, as the script size is too small.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 0u);
+}
+
+TEST_F(SharedStorageWorkletTest,
+       CodeCache_DidClearDueToUnmatchedTime_NoGenerateData) {
+  // Configure to return non-empty data, with unmatched response time.
+  mock_code_cache_host_->OverrideFetchCachedCodeResult(
+      /*response_time=*/kScriptResponseTime - base::Days(1),
+      /*data=*/std::vector<uint8_t>(1));
+
+  AddModule(/*script_content=*/"");
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 1u);
+
+  // Cache was cleared, as the response time did not match the time from the
+  // script loading.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 1u);
+
+  // No code cache was generated, as the script size is too small.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 0u);
+}
+
+TEST_F(SharedStorageWorkletTest,
+       CodeCache_NoClearDueToMatchedTime_NoGenerateData) {
+  // Configure to return non-empty data, with matched response time.
+  mock_code_cache_host_->OverrideFetchCachedCodeResult(
+      /*response_time=*/kScriptResponseTime,
+      /*data=*/std::vector<uint8_t>(1));
+
+  AddModule(/*script_content=*/"");
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 1u);
+
+  // No invalidation was triggered, as `FetchCachedCode()` responded with some
+  // data with a matched response time.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 0u);
+
+  // No code cache was generated, as the script size is too small.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 0u);
+}
+
+TEST_F(SharedStorageWorkletTest, CodeCache_DidGenerateData) {
+  // Code cache will be generated when the code length is at least 1024 bytes.
+  std::string large_script;
+  while (large_script.size() < 1024) {
+    large_script += "a=1;";
+  }
+
+  AddModule(large_script);
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 1u);
+
+  // No invalidation was triggered, as `FetchCachedCode()` responded with empty
+  // data.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 0u);
+
+  // Code cache was generated.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 1u);
+}
+
+TEST_F(SharedStorageWorkletTest, CodeCache_AddModuleTwice) {
+  // Code cache will be generated when the code length is at least 1024 bytes.
+  std::string large_script;
+  while (large_script.size() < 1024) {
+    large_script += "a=1;";
+  }
+
+  AddModule(large_script);
+  AddModule(large_script);
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 2u);
+
+  // No invalidation was triggered. The second code cache fetch returns a
+  // response time from the first result, which matches the response time from
+  // the second script loading.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 0u);
+
+  // The second script loading also triggered the code cache generation. This
+  // implies that the code cache was still not used. This is expected, as we
+  // won't store the cached code entirely for first seen URLs.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 2u);
+}
+
+TEST_F(SharedStorageWorkletTest, CodeCache_AddModuleThreeTimes) {
+  // Code cache will be generated when the code length is at least 1024 bytes.
+  std::string large_script;
+  while (large_script.size() < 1024) {
+    large_script += "a=1;";
+  }
+
+  AddModule(large_script);
+  AddModule(large_script);
+  AddModule(large_script);
+  mock_code_cache_host_->FlushForTesting();
+
+  EXPECT_EQ(mock_code_cache_host_->fetch_cached_code_count(), 3u);
+
+  // No invalidation was triggered. The second and third code cache fetch
+  // returns a response time from the first result, which matches the response
+  // time from the second and third script loading.
+  EXPECT_EQ(mock_code_cache_host_->clear_code_cache_entry_count(), 0u);
+
+  // The third script loading did not trigger the code cache generation. This
+  // implies that the cached code was used for the third script loading.
+  EXPECT_EQ(mock_code_cache_host_->did_generate_cacheable_metadata_count(), 2u);
+}
+
 TEST_F(SharedStorageWorkletTest, WorkletTerminationDueToDisconnect) {
   AddModuleResult result = AddModule(/*script_content=*/"");
 
diff --git a/third_party/blink/renderer/platform/bindings/exception_context.h b/third_party/blink/renderer/platform/bindings/exception_context.h
index 7da1982..df8b659 100644
--- a/third_party/blink/renderer/platform/bindings/exception_context.h
+++ b/third_party/blink/renderer/platform/bindings/exception_context.h
@@ -26,7 +26,6 @@
   kIndexedPropertySetter,
   kIndexedPropertyDefiner,
   kIndexedPropertyDeleter,
-  kIndexedPropertyQuery,
   kNamedPropertyGetter,
   kNamedPropertyDescriptor,
   kNamedPropertySetter,
@@ -70,7 +69,6 @@
       case ExceptionContextType::kIndexedPropertySetter:
       case ExceptionContextType::kIndexedPropertyDefiner:
       case ExceptionContextType::kIndexedPropertyDeleter:
-      case ExceptionContextType::kIndexedPropertyQuery:
       case ExceptionContextType::kNamedPropertyGetter:
       case ExceptionContextType::kNamedPropertyDescriptor:
       case ExceptionContextType::kNamedPropertySetter:
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
index 99c4902..40d175d 100644
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
@@ -132,6 +132,8 @@
     "fetch/url_loader/background_url_loader.h",
     "fetch/url_loader/cached_metadata_handler.cc",
     "fetch/url_loader/cached_metadata_handler.h",
+    "fetch/url_loader/code_cache_fetcher.cc",
+    "fetch/url_loader/code_cache_fetcher.h",
     "fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.cc",
     "fetch/url_loader/dedicated_or_shared_worker_fetch_context_impl.h",
     "fetch/url_loader/mojo_url_loader_client.cc",
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.cc
new file mode 100644
index 0000000..3d3dec3
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.cc
@@ -0,0 +1,201 @@
+// 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 "third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/blink/renderer/platform/loader/fetch/code_cache_host.h"
+#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
+
+namespace blink {
+
+namespace {
+
+bool ShouldUseIsolatedCodeCache(
+    const network::mojom::URLResponseHead& response_head,
+    const KURL& initial_url,
+    const KURL& current_url,
+    base::Time code_cache_response_time) {
+  // We only support code cache for other service worker provided
+  // resources when a direct pass-through fetch handler is used. If the service
+  // worker synthesizes a new Response or provides a Response fetched from a
+  // different URL, then do not use the code cache.
+  // Also, responses coming from cache storage use a separate code cache
+  // mechanism.
+  if (response_head.was_fetched_via_service_worker) {
+    // Do the same check as !ResourceResponse::IsServiceWorkerPassThrough().
+    if (!response_head.cache_storage_cache_name.empty()) {
+      // Responses was produced by cache_storage
+      return false;
+    }
+    if (response_head.url_list_via_service_worker.empty()) {
+      // Response was synthetically constructed.
+      return false;
+    }
+    if (KURL(response_head.url_list_via_service_worker.back()) != current_url) {
+      // Response was fetched from different URLs.
+      return false;
+    }
+  }
+  if (SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
+          initial_url.Protocol())) {
+    // This resource should use a source text hash rather than a response time
+    // comparison.
+    if (!SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
+            current_url.Protocol())) {
+      // This kind of Resource doesn't support requiring a hash, so we can't
+      // send cached code to it.
+      return false;
+    }
+  } else if (!response_head.should_use_source_hash_for_js_code_cache) {
+    // If the timestamps don't match or are null, the code cache data may be
+    // for a different response. See https://crbug.com/1099587.
+    if (code_cache_response_time.is_null() ||
+        response_head.response_time.is_null() ||
+        code_cache_response_time != response_head.response_time) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ShouldFetchCodeCache(const network::ResourceRequest& request) {
+  // Since code cache requests use a per-frame interface, don't fetch cached
+  // code for keep-alive requests. These are only used for beaconing and we
+  // don't expect code cache to help there.
+  if (request.keepalive) {
+    return false;
+  }
+
+  // Aside from http and https, the only other supported protocols are those
+  // listed in the SchemeRegistry as requiring a content equality check.
+  bool should_use_source_hash =
+      SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
+          String(request.url.scheme()));
+  if (!request.url.SchemeIsHTTPOrHTTPS() && !should_use_source_hash) {
+    return false;
+  }
+
+  // Supports script resource requests and shared storage worklet module
+  // requests.
+  // TODO(crbug.com/964467): Currently Chrome doesn't support code cache for
+  // dedicated worker, shared worker, audio worklet and paint worklet. For
+  // the service worker scripts, Blink receives the code cache via
+  // URLLoaderClient::OnReceiveResponse() IPC.
+  if (request.destination == network::mojom::RequestDestination::kScript ||
+      request.destination ==
+          network::mojom::RequestDestination::kSharedStorageWorklet) {
+    return true;
+  }
+
+  // WebAssembly module request have RequestDestination::kEmpty. Note that
+  // we always perform a code fetch for all of these requests because:
+  //
+  // * It is not easy to distinguish WebAssembly modules from other kEmpty
+  //   requests
+  // * The fetch might be handled by Service Workers, but we can't still know
+  //   if the response comes from the CacheStorage (in such cases its own
+  //   code cache will be used) or not.
+  //
+  // These fetches should be cheap, however, requiring one additional IPC and
+  // no browser process disk IO since the cache index is in memory and the
+  // resource key should not be present.
+  //
+  // The only case where it's easy to skip a kEmpty request is when a content
+  // equality check is required, because only ScriptResource supports that
+  // requirement.
+  if (request.destination == network::mojom::RequestDestination::kEmpty) {
+    return true;
+  }
+  return false;
+}
+
+mojom::blink::CodeCacheType GetCodeCacheType(
+    network::mojom::RequestDestination destination) {
+  if (destination == network::mojom::RequestDestination::kEmpty) {
+    // For requests initiated by the fetch function, we use code cache for
+    // WASM compiled code.
+    return mojom::blink::CodeCacheType::kWebAssembly;
+  } else {
+    // Otherwise, we use code cache for scripting.
+    return mojom::blink::CodeCacheType::kJavascript;
+  }
+}
+
+}  // namespace
+
+// static
+scoped_refptr<CodeCacheFetcher> CodeCacheFetcher::TryCreateAndStart(
+    const network::ResourceRequest& request,
+    CodeCacheHost& code_cache_host,
+    base::OnceClosure done_closure) {
+  if (!ShouldFetchCodeCache(request)) {
+    return nullptr;
+  }
+  auto fetcher = base::MakeRefCounted<CodeCacheFetcher>(
+      code_cache_host, GetCodeCacheType(request.destination), KURL(request.url),
+      std::move(done_closure));
+  fetcher->Start();
+  return fetcher;
+}
+
+CodeCacheFetcher::CodeCacheFetcher(CodeCacheHost& code_cache_host,
+                                   mojom::blink::CodeCacheType code_cache_type,
+                                   const KURL& url,
+                                   base::OnceClosure done_closure)
+    : code_cache_host_(code_cache_host.GetWeakPtr()),
+      code_cache_type_(code_cache_type),
+      initial_url_(url),
+      current_url_(url),
+      done_closure_(std::move(done_closure)) {}
+
+void CodeCacheFetcher::Start() {
+  CHECK(code_cache_host_);
+  (*code_cache_host_)
+      ->FetchCachedCode(code_cache_type_, initial_url_,
+                        WTF::BindOnce(&CodeCacheFetcher::DidReceiveCachedCode,
+                                      base::WrapRefCounted(this)));
+}
+
+void CodeCacheFetcher::DidReceiveCachedMetadataFromUrlLoader() {
+  did_receive_cached_metadata_from_url_loader_ = true;
+  if (!is_waiting_) {
+    ClearCodeCacheEntryIfPresent();
+  }
+}
+
+std::optional<mojo_base::BigBuffer> CodeCacheFetcher::TakeCodeCacheForResponse(
+    const network::mojom::URLResponseHead& response_head) {
+  CHECK(!is_waiting_);
+  if (!ShouldUseIsolatedCodeCache(response_head, initial_url_, current_url_,
+                                  code_cache_response_time_)) {
+    ClearCodeCacheEntryIfPresent();
+    return std::nullopt;
+  }
+  return std::move(code_cache_data_);
+}
+
+void CodeCacheFetcher::DidReceiveCachedCode(base::Time response_time,
+                                            mojo_base::BigBuffer data) {
+  is_waiting_ = false;
+  code_cache_data_ = std::move(data);
+  if (did_receive_cached_metadata_from_url_loader_) {
+    ClearCodeCacheEntryIfPresent();
+    return;
+  }
+  code_cache_response_time_ = response_time;
+  std::move(done_closure_).Run();
+}
+
+void CodeCacheFetcher::ClearCodeCacheEntryIfPresent() {
+  if (code_cache_host_ && code_cache_data_ && (code_cache_data_->size() > 0)) {
+    (*code_cache_host_)->ClearCodeCacheEntry(code_cache_type_, initial_url_);
+  }
+  code_cache_data_.reset();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h
new file mode 100644
index 0000000..78e09fd
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h
@@ -0,0 +1,84 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_CODE_CACHE_FETCHER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_CODE_CACHE_FETCHER_H_
+
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "services/network/public/mojom/url_response_head.mojom-forward.h"
+#include "third_party/blink/public/mojom/loader/code_cache.mojom-blink-forward.h"
+#include "third_party/blink/public/platform/web_common.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
+
+namespace network {
+struct ResourceRequest;
+}  // namespace network
+
+namespace blink {
+
+class CodeCacheHost;
+
+// Handles the fetching and validation of code cache entries.
+class BLINK_PLATFORM_EXPORT CodeCacheFetcher
+    : public WTF::RefCounted<CodeCacheFetcher> {
+ public:
+  static scoped_refptr<CodeCacheFetcher> TryCreateAndStart(
+      const network::ResourceRequest& request,
+      CodeCacheHost& code_cache_host,
+      base::OnceClosure done_closure);
+
+  CodeCacheFetcher(CodeCacheHost& code_cache_host,
+                   mojom::blink::CodeCacheType code_cache_type,
+                   const KURL& url,
+                   base::OnceClosure done_closure);
+
+  CodeCacheFetcher(const CodeCacheFetcher&) = delete;
+  CodeCacheFetcher& operator=(const CodeCacheFetcher&) = delete;
+
+  bool is_waiting() const { return is_waiting_; }
+
+  void SetCurrentUrl(const KURL& new_url) { current_url_ = new_url; }
+  void DidReceiveCachedMetadataFromUrlLoader();
+  std::optional<mojo_base::BigBuffer> TakeCodeCacheForResponse(
+      const network::mojom::URLResponseHead& response_head);
+
+ private:
+  friend class WTF::RefCounted<CodeCacheFetcher>;
+  ~CodeCacheFetcher() = default;
+
+  void Start();
+
+  void DidReceiveCachedCode(base::Time response_time,
+                            mojo_base::BigBuffer data);
+
+  void ClearCodeCacheEntryIfPresent();
+
+  base::WeakPtr<CodeCacheHost> code_cache_host_;
+  mojom::blink::CodeCacheType code_cache_type_;
+
+  // The initial URL used for code cache fetching, prior to redirects. This
+  // should match the initial URL for script fetching.
+  const KURL initial_url_;
+
+  // The current URL used for code cache fetching. This should match / will be
+  // updated to the current url for script fetching (either initial or
+  // redirected).
+  KURL current_url_;
+
+  base::OnceClosure done_closure_;
+
+  bool is_waiting_ = true;
+  bool did_receive_cached_metadata_from_url_loader_ = false;
+  std::optional<mojo_base::BigBuffer> code_cache_data_;
+  base::Time code_cache_response_time_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_CODE_CACHE_FETCHER_H_
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
index 5b2e0bb..00c1e5d7 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
@@ -53,6 +53,7 @@
 #include "third_party/blink/public/platform/web_url_request_util.h"
 #include "third_party/blink/renderer/platform/loader/fetch/code_cache_host.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h"
+#include "third_party/blink/renderer/platform/loader/fetch/url_loader/code_cache_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.h"
 #include "third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_client.h"
 #include "third_party/blink/renderer/platform/loader/fetch/url_loader/sync_load_context.h"
@@ -138,238 +139,8 @@
   return original_url.scheme_piece() != redirect_url.scheme_piece();
 }
 
-bool ShouldFetchCodeCache(const network::ResourceRequest& request) {
-  // Since code cache requests use a per-frame interface, don't fetch cached
-  // code for keep-alive requests. These are only used for beaconing and we
-  // don't expect code cache to help there.
-  if (request.keepalive) {
-    return false;
-  }
-
-  // Aside from http and https, the only other supported protocols are those
-  // listed in the SchemeRegistry as requiring a content equality check.
-  bool should_use_source_hash =
-      SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
-          String(request.url.scheme()));
-  if (!request.url.SchemeIsHTTPOrHTTPS() && !should_use_source_hash) {
-    return false;
-  }
-
-  // Supports script resource requests.
-  // TODO(crbug.com/964467): Currently Chrome doesn't support code cache for
-  // dedicated worker, shared worker, audio worklet and paint worklet. For
-  // the service worker scripts, Blink receives the code cache via
-  // URLLoaderClient::OnReceiveResponse() IPC.
-  if (request.destination == network::mojom::RequestDestination::kScript) {
-    return true;
-  }
-
-  // WebAssembly module request have RequestDestination::kEmpty. Note that
-  // we always perform a code fetch for all of these requests because:
-  //
-  // * It is not easy to distinguish WebAssembly modules from other kEmpty
-  //   requests
-  // * The fetch might be handled by Service Workers, but we can't still know
-  //   if the response comes from the CacheStorage (in such cases its own
-  //   code cache will be used) or not.
-  //
-  // These fetches should be cheap, however, requiring one additional IPC and
-  // no browser process disk IO since the cache index is in memory and the
-  // resource key should not be present.
-  //
-  // The only case where it's easy to skip a kEmpty request is when a content
-  // equality check is required, because only ScriptResource supports that
-  // requirement.
-  if (request.destination == network::mojom::RequestDestination::kEmpty) {
-    return true;
-  }
-  return false;
-}
-
-mojom::blink::CodeCacheType GetCodeCacheType(
-    network::mojom::RequestDestination destination) {
-  if (destination == network::mojom::RequestDestination::kEmpty) {
-    // For requests initiated by the fetch function, we use code cache for
-    // WASM compiled code.
-    return mojom::blink::CodeCacheType::kWebAssembly;
-  } else {
-    // Otherwise, we use code cache for scripting.
-    return mojom::blink::CodeCacheType::kJavascript;
-  }
-}
-
-bool ShouldUseIsolatedCodeCache(
-    const network::mojom::URLResponseHead& response_head,
-    const KURL& initial_url,
-    const KURL& current_url,
-    base::Time code_cache_response_time) {
-  // We only support code cache for other service worker provided
-  // resources when a direct pass-through fetch handler is used. If the service
-  // worker synthesizes a new Response or provides a Response fetched from a
-  // different URL, then do not use the code cache.
-  // Also, responses coming from cache storage use a separate code cache
-  // mechanism.
-  if (response_head.was_fetched_via_service_worker) {
-    // Do the same check as !ResourceResponse::IsServiceWorkerPassThrough().
-    if (!response_head.cache_storage_cache_name.empty()) {
-      // Responses was produced by cache_storage
-      return false;
-    }
-    if (response_head.url_list_via_service_worker.empty()) {
-      // Response was synthetically constructed.
-      return false;
-    }
-    if (KURL(response_head.url_list_via_service_worker.back()) != current_url) {
-      // Response was fetched from different URLs.
-      return false;
-    }
-  }
-  if (SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
-          initial_url.Protocol())) {
-    // This resource should use a source text hash rather than a response time
-    // comparison.
-    if (!SchemeRegistry::SchemeSupportsCodeCacheWithHashing(
-            current_url.Protocol())) {
-      // This kind of Resource doesn't support requiring a hash, so we can't
-      // send cached code to it.
-      return false;
-    }
-  } else if (!response_head.should_use_source_hash_for_js_code_cache) {
-    // If the timestamps don't match or are null, the code cache data may be
-    // for a different response. See https://crbug.com/1099587.
-    if (code_cache_response_time.is_null() ||
-        response_head.response_time.is_null() ||
-        code_cache_response_time != response_head.response_time) {
-      return false;
-    }
-  }
-  return true;
-}
-
 }  // namespace
 
-class ResourceRequestSender::CodeCacheFetcher
-    : public WTF::RefCounted<ResourceRequestSender::CodeCacheFetcher> {
- public:
-  static scoped_refptr<CodeCacheFetcher> TryCreateAndStart(
-      const network::ResourceRequest& request,
-      CodeCacheHost& code_cache_host,
-      base::OnceClosure done_closure);
-
-  CodeCacheFetcher(CodeCacheHost& code_cache_host,
-                   mojom::blink::CodeCacheType code_cache_type,
-                   const GURL& url,
-                   base::OnceClosure done_closure);
-
-  CodeCacheFetcher(const CodeCacheFetcher&) = delete;
-  CodeCacheFetcher& operator=(const CodeCacheFetcher&) = delete;
-
-  bool is_waiting() const { return is_waiting_; }
-
-  void SetCurrentUrl(const GURL& new_url) { current_url_ = KURL(new_url); }
-  void DidReceiveCachedMetadataFromUrlLoader();
-  std::optional<mojo_base::BigBuffer> TakeCodeCacheForResponse(
-      const network::mojom::URLResponseHead& response_head);
-
- private:
-  friend class WTF::RefCounted<CodeCacheFetcher>;
-  ~CodeCacheFetcher() = default;
-
-  void Start();
-
-  void DidReceiveCachedCode(base::Time response_time,
-                            mojo_base::BigBuffer data);
-
-  void ClearCodeCacheEntryIfPresent();
-
-  base::WeakPtr<CodeCacheHost> code_cache_host_;
-  mojom::blink::CodeCacheType code_cache_type_;
-  const KURL initial_url_;
-  KURL current_url_;
-  base::OnceClosure done_closure_;
-
-  bool is_waiting_ = true;
-  bool did_receive_cached_metadata_from_url_loader_ = false;
-  std::optional<mojo_base::BigBuffer> code_cache_data_;
-  base::Time code_cache_response_time_;
-};
-
-// static
-scoped_refptr<ResourceRequestSender::CodeCacheFetcher>
-ResourceRequestSender::CodeCacheFetcher::TryCreateAndStart(
-    const network::ResourceRequest& request,
-    CodeCacheHost& code_cache_host,
-    base::OnceClosure done_closure) {
-  if (!ShouldFetchCodeCache(request)) {
-    return nullptr;
-  }
-  auto fetcher = base::MakeRefCounted<ResourceRequestSender::CodeCacheFetcher>(
-      code_cache_host, GetCodeCacheType(request.destination), request.url,
-      std::move(done_closure));
-  fetcher->Start();
-  return fetcher;
-}
-
-ResourceRequestSender::CodeCacheFetcher::CodeCacheFetcher(
-    CodeCacheHost& code_cache_host,
-    mojom::blink::CodeCacheType code_cache_type,
-    const GURL& url,
-    base::OnceClosure done_closure)
-    : code_cache_host_(code_cache_host.GetWeakPtr()),
-      code_cache_type_(code_cache_type),
-      initial_url_(url),
-      current_url_(url),
-      done_closure_(std::move(done_closure)) {}
-
-void ResourceRequestSender::CodeCacheFetcher::Start() {
-  CHECK(code_cache_host_);
-  (*code_cache_host_)
-      ->FetchCachedCode(code_cache_type_, KURL(initial_url_),
-                        WTF::BindOnce(&CodeCacheFetcher::DidReceiveCachedCode,
-                                      base::WrapRefCounted(this)));
-}
-
-void ResourceRequestSender::CodeCacheFetcher::
-    DidReceiveCachedMetadataFromUrlLoader() {
-  did_receive_cached_metadata_from_url_loader_ = true;
-  if (!is_waiting_) {
-    ClearCodeCacheEntryIfPresent();
-  }
-}
-
-std::optional<mojo_base::BigBuffer>
-ResourceRequestSender::CodeCacheFetcher::TakeCodeCacheForResponse(
-    const network::mojom::URLResponseHead& response_head) {
-  CHECK(!is_waiting_);
-  if (!ShouldUseIsolatedCodeCache(response_head, initial_url_, current_url_,
-                                  code_cache_response_time_)) {
-    ClearCodeCacheEntryIfPresent();
-    return std::nullopt;
-  }
-  return std::move(code_cache_data_);
-}
-
-void ResourceRequestSender::CodeCacheFetcher::DidReceiveCachedCode(
-    base::Time response_time,
-    mojo_base::BigBuffer data) {
-  is_waiting_ = false;
-  code_cache_data_ = std::move(data);
-  if (did_receive_cached_metadata_from_url_loader_) {
-    ClearCodeCacheEntryIfPresent();
-    return;
-  }
-  code_cache_response_time_ = response_time;
-  std::move(done_closure_).Run();
-}
-
-void ResourceRequestSender::CodeCacheFetcher::ClearCodeCacheEntryIfPresent() {
-  if (code_cache_host_ && code_cache_data_ && (code_cache_data_->size() > 0)) {
-    (*code_cache_host_)
-        ->ClearCodeCacheEntry(code_cache_type_, KURL(initial_url_));
-  }
-  code_cache_data_.reset();
-}
-
 ResourceRequestSender::ResourceRequestSender() = default;
 
 ResourceRequestSender::~ResourceRequestSender() = default;
@@ -763,7 +534,7 @@
   CHECK(request_info_->url_loader);
 
   if (code_cache_fetcher_) {
-    code_cache_fetcher_->SetCurrentUrl(redirect_info.new_url);
+    code_cache_fetcher_->SetCurrentUrl(KURL(redirect_info.new_url));
   }
 
   request_info_->local_response_start = redirect_ipc_arrival_time;
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.h b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.h
index 9852193a..b3ed40d 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.h
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.h
@@ -28,6 +28,7 @@
 #include "services/network/public/mojom/url_response_head.mojom-forward.h"
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 #include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h"
+#include "third_party/blink/public/mojom/loader/code_cache.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
 #include "third_party/blink/public/mojom/navigation/renderer_eviction_reason.mojom-blink-forward.h"
@@ -52,6 +53,7 @@
 }  // namespace network
 
 namespace blink {
+class CodeCacheFetcher;
 class CodeCacheHost;
 class ResourceLoadInfoNotifierWrapper;
 class ThrottlingURLLoader;
@@ -162,7 +164,6 @@
   friend class URLLoaderClientImpl;
   friend class URLResponseBodyConsumer;
 
-  class CodeCacheFetcher;
   struct PendingRequestInfo {
     PendingRequestInfo(scoped_refptr<ResourceRequestClient> client,
                        network::mojom::RequestDestination request_destination,
diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
index b5314246..669ab109 100644
--- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc
+++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
@@ -1019,7 +1019,6 @@
 void WebMediaPlayerImpl::Seek(double seconds) {
   DVLOG(1) << __func__ << "(" << seconds << "s)";
   DCHECK(main_task_runner_->BelongsToCurrentThread());
-  media_log_->AddEvent<MediaLogEvent::kSeek>(seconds);
   DoSeek(base::Seconds(seconds), true);
 }
 
@@ -1032,6 +1031,26 @@
   if (ready_state_ > WebMediaPlayer::kReadyStateHaveMetadata)
     SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata);
 
+  // For zero duration video-only media, if we can elide the seek, use a large
+  // delay to avoid an expensive spin loop. Per spec we must still deliver all
+  // the requisite events, but we're not required to be timely about it.
+  //
+  // 250ms matches the max timeupdate interval used by the media element.
+  auto delay = base::TimeDelta();
+  bool is_at_eos = false;
+  if (ended_) {
+    if (time == base::Seconds(Duration())) {
+      is_at_eos = true;
+    } else if (!HasAudio()) {
+      if (auto frame = compositor_->GetCurrentFrameOnAnyThread()) {
+        if (frame->timestamp() == GetCurrentTimeInternal()) {
+          is_at_eos = true;
+          delay = base::Milliseconds(250);
+        }
+      }
+    }
+  }
+
   // When paused or ended, we know exactly what the current time is and can
   // elide seeks to it. However, there are three cases that are not elided:
   //   1) When the pipeline state is not stable.
@@ -1044,21 +1063,34 @@
   //   3) For MSE.
   //      Because the buffers may have changed between seeks, MSE seeks are
   //      never elided.
-  if (paused_ && pipeline_controller_->IsStable() &&
-      (paused_time_ == time || (ended_ && time == base::Seconds(Duration()))) &&
+  if (((paused_ && paused_time_ == time) || (ended_ && is_at_eos)) &&
+      pipeline_controller_->IsStable() &&
       GetDemuxerType() != media::DemuxerType::kChunkDemuxer) {
     if (old_state == kReadyStateHaveEnoughData) {
       // This will in turn SetReadyState() to signal the demuxer seek, followed
       // by timeChanged() to signal the renderer seek.
       should_notify_time_changed_ = true;
-      main_task_runner_->PostTask(
-          FROM_HERE, base::BindOnce(&WebMediaPlayerImpl::OnBufferingStateChange,
-                                    weak_this_, media::BUFFERING_HAVE_ENOUGH,
-                                    media::BUFFERING_CHANGE_REASON_UNKNOWN));
+
+      // Seek will always emit a new frame -- even if the it's the same frame it
+      // will be decoded again with a new frame id, so simulate that here.
+      main_task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&WebMediaPlayerImpl::OnNewFramePresentedCallback,
+                         weak_this_),
+          delay);
+
+      main_task_runner_->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&WebMediaPlayerImpl::OnBufferingStateChange,
+                         weak_this_, media::BUFFERING_HAVE_ENOUGH,
+                         media::BUFFERING_CHANGE_REASON_UNKNOWN),
+          delay);
       return;
     }
   }
 
+  media_log_->AddEvent<MediaLogEvent::kSeek>(time.InSecondsF());
+
   if (playback_events_recorder_)
     playback_events_recorder_->OnSeeking();
 
@@ -1933,7 +1965,9 @@
     return;
 
   ended_ = true;
-  client_->TimeChanged();
+  if (!paused_) {
+    client_->TimeChanged();
+  }
 
   if (playback_events_recorder_)
     playback_events_recorder_->OnEnded();
@@ -2893,7 +2927,7 @@
   auto create_demuxer_error = demuxer_manager_->CreateDemuxer(
       load_type_ == kLoadTypeMediaSource, preload_, needs_first_frame_,
       base::BindOnce(&WebMediaPlayerImpl::OnDemuxerCreated,
-                     base::Unretained(this)), 
+                     base::Unretained(this)),
       headers);
 
   if (!create_demuxer_error.is_ok()) {
diff --git a/third_party/blink/renderer/platform/network/http_parsers.cc b/third_party/blink/renderer/platform/network/http_parsers.cc
index 8182059..ead726c4 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers.cc
@@ -310,7 +310,7 @@
           ? std::make_optional(ConvertToBlink(in->content_language.value()))
           : std::nullopt,
       ConvertToBlink(in->no_vary_search_with_parse_error),
-      in->observe_browsing_topics);
+      in->observe_browsing_topics, in->allow_cross_origin_event_reporting);
 }
 
 }  // namespace mojom
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
index f009d59..e79de55 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
@@ -54,12 +54,12 @@
     # pylint: disable=import-error
     # pylint: disable=invalid-name
     # pylint: disable=redefined-outer-name
-    global SDK_ROOT, SDK_TOOLS_DIR, run_continuous_ffx_command, run_ffx_command
-    from common import SDK_ROOT, SDK_TOOLS_DIR, run_continuous_ffx_command, run_ffx_command
-    global get_ssh_prefix
-    from compatible_utils import get_ssh_prefix
-    global port_forward
-    from test_server import port_forward
+    global SDK_ROOT, SDK_TOOLS_DIR, get_ssh_address, run_continuous_ffx_command, run_ffx_command
+    from common import SDK_ROOT, SDK_TOOLS_DIR, get_ssh_address, run_continuous_ffx_command, run_ffx_command
+    global get_host_arch, get_ssh_prefix
+    from compatible_utils import get_host_arch, get_ssh_prefix
+    global ports_forward, port_forward
+    from test_server import ports_forward, port_forward
     # pylint: enable=import-error
     # pylint: enable=invalid-name
     # pylint: disable=redefined-outer-name
@@ -118,27 +118,15 @@
         # Tell SSH to forward all server ports from the Fuchsia device to
         # the host.
 
-        self._host_port_pair = run_ffx_command(
-            cmd=('target', 'get-ssh-address'),
-            target_id=self._target_id,
-            capture_output=True).stdout.strip()
-        self._proxy = self._port_forward_list(ports_to_forward)
+        self._host_port_pair = get_ssh_address(self._target_id)
+        self._port_forward_list(ports_to_forward)
 
     def _port_forward_list(self, ports):
         """Reverse forward all ports listed in |ports| to the device."""
-
-        ssh_prefix = get_ssh_prefix(self._host_port_pair)
-        forwarding_flags = [
-            '-O',
-            'forward',  # Send SSH mux control signal.
-            '-N',  # Don't execute command
-            '-T'  # Don't allocate terminal.
-        ]
+        forwarding_ports = []
         for port in ports:
-            forwarding_flags += ['-R', f'{port}:localhost:{port}']
-        return subprocess.Popen(ssh_prefix + forwarding_flags,
-                                stdout=subprocess.PIPE,
-                                stderr=open('/dev/null'))
+            forwarding_ports.append((port, port))
+        ports_forward(self._host_port_pair, forwarding_ports)
 
     def run_command(self, command):
         ssh_prefix = get_ssh_prefix(self._host_port_pair)
@@ -173,13 +161,11 @@
 
     def __init__(self, host, port_name, target_host=None, **kwargs):
         super(FuchsiaPort, self).__init__(host, port_name, **kwargs)
+        _import_fuchsia_runner()
 
         self._operating_system = 'fuchsia'
         self._version = 'fuchsia'
-        self._target_device = self.get_option('device')
-
-        self._architecture = 'x86_64' if self._target_cpu(
-        ) == 'x64' else 'arm64'
+        self._architecture = 'x86_64' if get_host_arch() == 'x64' else 'arm64'
 
         self.server_process_constructor = FuchsiaServerProcess
 
@@ -188,10 +174,10 @@
 
         self._target_host = target_host
         self._zircon_logger = None
-        _import_fuchsia_runner()
         self._symbolizer = os.path.join(SDK_TOOLS_DIR, 'symbolizer')
         self._build_id_dir = os.path.join(SDK_ROOT, '.build-id')
 
+    # TODO(b/340288531): Should use ffx debug symbolize.
     def run_symbolizer(self, input_fd, output_fd, ids_txt_paths):
         """Starts a symbolizer process.
 
@@ -225,9 +211,6 @@
         if self._zircon_logger:
             self._zircon_logger.close()
 
-    def _target_cpu(self):
-        return self.get_option('fuchsia_target_cpu')
-
     def _cpu_cores(self):
         # TODO(crbug.com/1340573): Four parallel jobs always gives reasonable
         # performance, while using larger numbers may actually slow things.
@@ -240,13 +223,11 @@
             target_id = self.get_option('fuchsia_target_id')
             self._target_host = _TargetHost(self.SERVER_PORTS, target_id)
 
-            if self.get_option('zircon_logging'):
-                klog_proc = self._target_host.run_command(['dlog', '-f'])
-                symbolized_klog_proc = self.run_symbolizer(
-                    klog_proc.stdout, subprocess.PIPE,
-                    [self.get_build_ids_path()])
-                self._zircon_logger = SubprocessOutputLogger(symbolized_klog_proc,
-                    'Zircon')
+            klog_proc = self._target_host.run_command(['dlog', '-f'])
+            symbolized_klog_proc = self.run_symbolizer(
+                klog_proc.stdout, subprocess.PIPE, [self.get_build_ids_path()])
+            self._zircon_logger = SubprocessOutputLogger(
+                symbolized_klog_proc, 'Zircon')
         except:
             return exit_codes.NO_DEVICES_EXIT_STATUS
 
@@ -313,17 +294,11 @@
             more_logging=self._port.get_option('driver_logging'))
 
     def _base_cmd_line(self):
-        cmd = []
-        if self._port._target_device == 'qemu':
-            cmd.append('--ozone-platform=headless')
-        # Use Scenic on AEMU
-        else:
-            cmd.extend([
-                '--use-vulkan', '--enable-gpu-rasterization',
-                '--force-device-scale-factor=1', '--enable-features=Vulkan',
-                '--gpu-watchdog-timeout-seconds=60'
-            ])
-        return cmd
+        return [
+            '--use-vulkan', '--enable-gpu-rasterization',
+            '--force-device-scale-factor=1', '--enable-features=Vulkan',
+            '--gpu-watchdog-timeout-seconds=60'
+        ]
 
     def _command_from_driver_input(self, driver_input):
         command = super(ChromiumFuchsiaDriver,
diff --git a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
index 067e814..15747cf 100644
--- a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
+++ b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
@@ -96,42 +96,14 @@
     printing.add_print_options_group(parser)
 
     fuchsia_group = parser.add_argument_group('Fuchsia-specific Options')
-    fuchsia_group.add_argument(
-        '--zircon-logging',
-        action='store_true',
-        default=True,
-        help='Log Zircon debug messages (enabled by default).')
-    fuchsia_group.add_argument('--no-zircon-logging',
-                               dest='zircon_logging',
-                               action='store_false',
-                               default=True,
-                               help='Do not log Zircon debug messages.')
-    fuchsia_group.add_argument(
-        '--device',
-        choices=['qemu', 'device', 'fvdl'],
-        default='fvdl',
-        help='Choose device to launch Fuchsia with. Defaults to fvdl.')
-    fuchsia_group.add_argument(
-        '--fuchsia-target-cpu',
-        choices=['x64', 'arm64'],
-        default='x64',
-        help='cpu architecture of the device. Defaults to x64.')
     fuchsia_group.add_argument('--fuchsia-out-dir',
                                help='Path to Fuchsia build output directory.')
     fuchsia_group.add_argument(
         '--custom-image',
         help='Specify an image used for booting up the emulator.')
     fuchsia_group.add_argument(
-        '--fuchsia-ssh-config',
-        help=('The path to the SSH configuration used for '
-              'connecting to the target device.'))
-    fuchsia_group.add_argument(
         '--fuchsia-target-id',
         help='The node-name of the device to boot or deploy to.')
-    fuchsia_group.add_argument(
-        '--fuchsia-host-ip',
-        help=('The IP address of the test host observed by the Fuchsia '
-              'device. Required if running on hardware devices.'))
     fuchsia_group.add_argument('--logs-dir',
                                help='Location of diagnostics logs')
 
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index b868b2d..9e154f0f 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2704,6 +2704,9 @@
 crbug.com/40278771 [ Mac ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/buffer.https.any.worker.html?gpu [ Skip ]
 crbug.com/40278771 [ Linux ] virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/buffer.https.any.html?cpu [ Skip ]
 crbug.com/40278771 [ Linux ] virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/buffer.https.any.worker.html?cpu [ Skip ]
+crbug.com/40278771 [ Win ] virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/buffer.https.any.html?cpu [ Skip ]
+crbug.com/40278771 [ Win ] virtual/webnn-service-on-cpu/external/wpt/webnn/conformance_tests/buffer.https.any.worker.html?cpu [ Skip ]
+
 
 ######## Unload Deprecation
 # This is for tests in the "unload-allowed" virtual suite that
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0768615..2c1f211 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6032,6 +6032,8 @@
 crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_canplaythrough.html [ Failure Skip ]
 crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_loadeddata.html [ Failure Skip ]
 crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_playing.html [ Failure Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-no-infinite-loop-playing.html [ Failure Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-no-infinite-loop-playing-rvfc.html [ Failure Skip ]
 
 # crbug.com/1356128 Disable flaky tests for fetch keepalive migration.
 # Some of the following tests are both flaky in non-virtual and virtual versions.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index c314d31..dfb6722 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -2579,7 +2579,7 @@
   "Run the WebNN WPTs with a CPU context with the WebNN service enabled.",
   {
     "prefix": "webnn-service-on-cpu",
-    "platforms": ["Linux"],
+    "platforms": ["Linux", "Win"],
     "bases": [
       "external/wpt/webnn/validation_tests",
       "external/wpt/webnn/conformance_tests/arg_min_max.https.any.html?cpu",
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index f32d0df7..679c714f 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -122342,6 +122342,19 @@
        {}
       ]
      ],
+     "downloadable-font-scoped-to-document.html": [
+      "2dbc350069cd9c61925967655f83217b800fc9eb",
+      [
+       null,
+       [
+        [
+         "/css/css-fonts/downloadable-font-scoped-to-document-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "first-available-font-001.html": [
       "5eb88f7bf6713b80e0adb4728681e62c3f2dc2bc",
       [
@@ -160220,6 +160233,214 @@
        {}
       ]
      ],
+     "line-clamp-auto-001.tentative.html": [
+      "02d8479736d4d6fd7e26df53611624e7a75d0989",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-002.tentative.html": [
+      "ff9e802f0f9e376635230707972611ab5a0ec7d6",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/line-clamp-auto-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-003.tentative.html": [
+      "a74704dd3b0765434c27935c944fac010415b13c",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/line-clamp-auto-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-004.tentative.html": [
+      "2dbf9d54084034f2a1b83eccf370e281af24dc8c",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-005.tentative.html": [
+      "1c9148e2647f310522103d1b5c6c2765a6c6644c",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/line-clamp-auto-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-006.tentative.html": [
+      "ab6915f52163f9cbd1c4c2f2fa4e9d8bf90a3a88",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-007.tentative.html": [
+      "f7d56bfa6557413e33bbb2422c5234437ac302b4",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-008.tentative.html": [
+      "9e7f38ab7ca09debf7628505d8d63ce473dc355e",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-009.tentative.html": [
+      "44d111056c6b021c796ea694d744816125314def",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-036-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-010.tentative.html": [
+      "cb706bba08e2c9dd6ff05b926be23eb22c9c25b7",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-037-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-011.tentative.html": [
+      "00076a5336e89004b6a2143e9bd6c72ec9fe95e0",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/line-clamp-auto-011-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-012.tentative.html": [
+      "56957b15c9223466ddd6f06a45bb854cab18fda6",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-013.tentative.html": [
+      "dd864fbdcb3fbd3cfeb11a385df61a99e739aba9",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-014.tentative.html": [
+      "6738c708706e778dd4ee307b8532ca32e8b7b881",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-015.tentative.html": [
+      "cdb1ed18c0d7a913fc3b9e446dbff15add266300",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/webkit-line-clamp-005-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "line-clamp-auto-016.tentative.html": [
+      "372213983b7a753037616485bfc2aae7524abfd5",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/reference/line-clamp-auto-016-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "line-clamp-with-abspos-001.tentative.html": [
       "79667f23fbdc3d941484c343b2cf0a04ec34363f",
       [
@@ -170816,7 +171037,7 @@
       ]
      ],
      "ruby-no-transform.html": [
-      "1cdd4f3bbf5af81309805285a25622f7e444a3ee",
+      "56b2fc8380957743ddad8b6de6787c6110fae353",
       [
        null,
        [
@@ -313495,6 +313716,10 @@
       "28a92a86dcaf6bc9c45bb75fce4869bc0ae21c37",
       []
      ],
+     "downloadable-font-scoped-to-document-ref.html": [
+      "4d7da060cbf1a161aa1366f0bcb00b94e09502ad",
+      []
+     ],
      "first-available-font-001-ref.html": [
       "0acbd338e0ce9f558d2eaa2e48ad4be0524fb0ae",
       []
@@ -320670,6 +320895,18 @@
        "6ed4aa506e95a35a065318f597547653bda52eb5",
        []
       ],
+      "iframe-missing-font-face-rule.html": [
+       "da97b781e8072923138e0160320e76d3013c3e53",
+       []
+      ],
+      "iframe-using-ahem-as-web-font.html": [
+       "b21066df8f57a8b11432a1168a62e7a4fbbe07b1",
+       []
+      ],
+      "iframe-without-web-font.html": [
+       "85e7fef282889894016088e082b623b92a436784",
+       []
+      ],
       "js": {
        "font-variant-features.js": [
         "4b56fee193956710b847ba79c5f9c3a5a7d15a33",
@@ -327314,6 +327551,22 @@
        "d794c76e3c9653dd94b2bad73cdf2a4574db5f50",
        []
       ],
+      "line-clamp-auto-002-ref.html": [
+       "fe0a8dbd588a8a56c8ac0488713a061ef83474f9",
+       []
+      ],
+      "line-clamp-auto-005-ref.html": [
+       "01eea67a0da77dc7a99bcd0b8677abdd3a4033f5",
+       []
+      ],
+      "line-clamp-auto-011-ref.html": [
+       "5f7120ee39a2014f75f2f700ebedcc0b0e6e6275",
+       []
+      ],
+      "line-clamp-auto-016-ref.html": [
+       "4a5f3536cc62c33e1a30ed9fce9a21cecda341bc",
+       []
+      ],
       "line-clamp-with-abspos-001-ref.html": [
        "d756162dde0c54bd52646597b01bbff8a80f5fd8",
        []
@@ -330358,7 +330611,7 @@
       []
      ],
      "ruby-no-transform-ref.html": [
-      "56afd9366fe787670771a82874f1016335f1b7c6",
+      "1c075de18fab1951de3a809e2d85cd2f2d6c3827",
       []
      ],
      "ruby-position-alternate-expected.txt": [
@@ -392203,7 +392456,7 @@
        []
       ],
       "utils.js": [
-       "bc1bc5911eb8552efd689f84a0e61d2f74aa268a",
+       "9cd47b69dd75128e6c522833325e1538c35a1f78",
        []
       ],
       "wake-lock.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html
index 1746ea9f..2c60c3d 100644
--- a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta http-equiv="Delegate-CH" content="Sec-CH-Width">
+<meta name="Delegate-CH" content="Sec-CH-Width">
 <title>Tests Sec-CH-Width with auto sizes and a single srcset value</title>
 <link rel="help" href="https://wicg.github.io/responsive-image-client-hints/#sec-ch-width">
 <link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html.headers b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html.headers
new file mode 100644
index 0000000..8a6a65bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-001.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-width
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html
index 0dfaf2e..c45b8308 100644
--- a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta http-equiv="Delegate-CH" content="Sec-CH-Width">
+<meta name="Delegate-CH" content="Sec-CH-Width">
 <title>Tests Sec-CH-Width with auto sizes and multiple srcset values</title>
 <link rel="help" href="https://wicg.github.io/responsive-image-client-hints/#sec-ch-width">
 <link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html.headers b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html.headers
new file mode 100644
index 0000000..8a6a65bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-002.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-width
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html
index 235529d..3675af8b 100644
--- a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta http-equiv="Delegate-CH" content="Sec-CH-Width">
+<meta name="Delegate-CH" content="Sec-CH-Width">
 <title>Tests Sec-CH-Width with auto sizes and picture</title>
 <link rel="help" href="https://wicg.github.io/responsive-image-client-hints/#sec-ch-width">
 <link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html.headers b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html.headers
new file mode 100644
index 0000000..8a6a65bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width-auto-sizes-003.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-width
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html
index ec13ff96..af0fa53 100644
--- a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta http-equiv="Delegate-CH" content="Sec-CH-Width">
+<meta name="Delegate-CH" content="Sec-CH-Width">
 <title>Tests Sec-CH-Width</title>
 <link rel="help" href="https://wicg.github.io/responsive-image-client-hints/#sec-ch-width">
 <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html.headers b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html.headers
new file mode 100644
index 0000000..8a6a65bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/client-hints/sec-ch-width.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-width
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document-ref.html
new file mode 100644
index 0000000..4d7da060
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document-ref.html
@@ -0,0 +1,17 @@
+ <!DOCTYPE html>
+
+<html>
+    <head>
+        <title>CSS fonts: Web fonts loaded in a document are not available in other documents</title>
+        <link rel="author" title="Martin Robinson" href="mrobinson@igalia.com">
+        <link rel="author" title="Mukilan Thiyagarajan" href="mukilan@igalia.com">
+    </head>
+
+    <body>
+        <p>Test passes if Ahem is only used in the first iframe.</p>
+        <iframe src="support/iframe-using-ahem-as-web-font.html"></iframe>
+        <iframe src="support/iframe-without-web-font.html"></iframe>
+    </body>
+
+</html>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document.html
new file mode 100644
index 0000000..2dbc350
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/downloadable-font-scoped-to-document.html
@@ -0,0 +1,28 @@
+ <!DOCTYPE html>
+
+<html class="reftest-wait">
+    <head>
+        <title>CSS fonts: Web fonts loaded in a document are not available in other documents</title>
+        <link rel="author" title="Martin Robinson" href="mrobinson@igalia.com">
+        <link rel="author" title="Mukilan Thiyagarajan" href="mukilan@igalia.com">
+        <link rel="match" href="downloadable-font-scoped-to-document-ref.html">
+        <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-face-rule">
+    </head>
+
+    <body>
+        <p>Test passes if Ahem is only used in the first iframe.</p>
+        <iframe id="iframe1" src="support/iframe-using-ahem-as-web-font.html"></iframe>
+        <iframe id="iframe2" src=""></iframe>
+
+        <script>
+            // Delay the loading of the second iframe to make it more likely that the font
+            // has loaded properly into the first iframe.
+            iframe1.onload = () => {
+                iframe2.src ="support/iframe-missing-font-face-rule.html";
+                document.documentElement.classList.remove('reftest-wait');
+            };
+        </script>
+    </body>
+
+</html>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-missing-font-face-rule.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-missing-font-face-rule.html
new file mode 100644
index 0000000..da97b781
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-missing-font-face-rule.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="font-size: 30px; font-family: CustomFontFamily">Hello!</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-using-ahem-as-web-font.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-using-ahem-as-web-font.html
new file mode 100644
index 0000000..b21066d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-using-ahem-as-web-font.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<style>
+@font-face {
+    font-family: CustomFontFamily;
+    src: url(/fonts/Ahem.ttf);
+}
+</style>
+<div style="font-size: 30px; font-family: CustomFontFamily">Hello!</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-without-web-font.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-without-web-font.html
new file mode 100644
index 0000000..85e7fef2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/iframe-without-web-font.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="font-size: 30px;">Hello!</div>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-content-initiated.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-content-initiated.https.html
index 9c1d47d..bdd5d4c 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-content-initiated.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-content-initiated.https.html
@@ -14,7 +14,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true,
     origin: get_host_info().HTTPS_ORIGIN
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested-urn-iframe.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested-urn-iframe.https.html
index 7d0544a5..ea4a4c1 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested-urn-iframe.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested-urn-iframe.https.html
@@ -24,7 +24,7 @@
   const fencedframe = await attachIFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested.https.html
index 4d1262f..8b8070e 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-nested.https.html
@@ -24,7 +24,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-embedder-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-embedder-opt-in.https.html
index d8fa513..954de24 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-embedder-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-embedder-opt-in.https.html
@@ -14,7 +14,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'false'
+      'Allow-Cross-Origin-Event-Reporting', '?0'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-subframe-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-subframe-opt-in.https.html
index 2b054c18..ea5d0cd 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-subframe-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-no-subframe-opt-in.https.html
@@ -14,7 +14,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-content-initiated.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-content-initiated.https.html
index 21c9ea1..e4971dc 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-content-initiated.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-content-initiated.https.html
@@ -14,7 +14,7 @@
   const iframe = await attachIFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true,
     origin: get_host_info().HTTPS_ORIGIN
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-embedder-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-embedder-opt-in.https.html
index 5d368fe7..6f6835b 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-embedder-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-embedder-opt-in.https.html
@@ -14,7 +14,7 @@
   const iframe = await attachIFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'false'
+      'Allow-Cross-Origin-Event-Reporting', '?0'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-subframe-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-subframe-opt-in.https.html
index df22749..855ad1f 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-subframe-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe-no-subframe-opt-in.https.html
@@ -14,7 +14,7 @@
   const outer_iframe = await attachIFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe.https.html
index b37fec81..d62a46d 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin-urn-iframe.https.html
@@ -14,7 +14,7 @@
   const outer_iframe = await attachIFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin.https.html
index df7ae77..f198ea4 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-cross-origin.https.html
@@ -14,7 +14,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-sub-fencedframe.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-sub-fencedframe.https.html
index 0b3231ca..558125d 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-sub-fencedframe.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/fence-report-event-sub-fencedframe.https.html
@@ -14,7 +14,7 @@
   const fencedframe = await attachFencedFrameContext({
     generator_api: 'fledge',
     headers: [[
-      'Allow-Cross-Origin-Event-Reporting', 'true'
+      'Allow-Cross-Origin-Event-Reporting', '?1'
     ]],
     register_beacon: true
   });
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print-ref.html
new file mode 100644
index 0000000..a6bf424
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<p>
+  Object image not displayed should not load.
+</p>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print.html
new file mode 100644
index 0000000..40158cb3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading-for-print.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Test loading of 'display: none' image for print</title>
+
+<link rel="help" href="https://crbug.com/41477900">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element">
+<!--
+  Based on step 2 of the spec algorithm:
+  "... if the element is not being rendered,
+  then jump to the step below labeled fallback."
+-->
+
+<link rel="match" href="object-image-display-none-loading-for-print-ref.html">
+
+<style>
+  #target {
+    display: none;
+  }
+</style>
+
+<script>
+  function obj_onload() {
+    const p = document.createElement('p');
+    p.innerHTML = `FAIL: Object image was loaded.`;
+    p.style.color = 'red';
+    document.body.appendChild(p);
+  }
+</script>
+
+<p>
+  Object image not displayed should not load.
+</p>
+
+<div>
+<object
+  data="/images/red.png"
+  id="target"
+  onload="obj_onload();"
+  type="image/png"
+></object>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading.html
new file mode 100644
index 0000000..de95aef4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-display-none-loading.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/41477900">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element">
+<!--
+  Based on step 2 of the spec algorithm:
+  "... if the element is not being rendered,
+  then jump to the step below labeled fallback."
+-->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+  const t = async_test(
+    "Test that object image not displayed is not loaded unnecessarily."
+  );
+
+  function obj_onload() {
+    t.unreached_func(
+      "Object image not displayed on screen should not load."
+    )();
+  }
+
+  t.step_timeout(() => {
+    t.done();
+  }, 2000);
+</script>
+
+<style>
+  #target {
+    display: none;
+  }
+</style>
+
+<object
+  data="/images/red.png"
+  id="target"
+  onload="obj_onload();"
+  type="image/png"
+>
+  Fallback Text
+</object>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print-ref.html
new file mode 100644
index 0000000..bee4a57
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<p>
+  Should print a green rectangle rectangle but not display it on screen.
+</p>
+<img src="/images/green.png" alt="A green rectangle">
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print.html
new file mode 100644
index 0000000..938ebdc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-object-element/object-image-only-for-print.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test print result of image not displayed on screen</title>
+
+<link rel="help" href="https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element">
+<link rel="help" href="https://crbug.com/41477900">
+
+<link rel="match" href="object-image-only-for-print-ref.html">
+
+<style>
+  @media not print {
+    .print-only {
+      display: none;
+    }
+  }
+</style>
+
+<p>
+  Should print a green rectangle rectangle but not display it on screen.
+</p>
+
+<div>
+<object
+  class="print-only"
+  data="/images/green.png"
+  type="image/png"
+></object>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within-expected.txt
deleted file mode 100644
index a1acfb2c..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Scrolling within a popover should not close the popover
-  assert_equals: popover should be scrolled expected 50 but got 0
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within.html
index 2329aea..053ea03 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss-scroll-within.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8" />
 <title>Popover light dismiss behavior when scrolled within</title>
-<meta name="timeout" content="long">
 <link rel="author" href="mailto:masonf@chromium.org">
 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
 <script src="/resources/testharness.js"></script>
@@ -26,11 +25,11 @@
 
 <div popover id=p>Inside popover
   <div style="height:2000px;background:lightgreen"></div>
-  Bottom of popover6
+  Bottom of popover
 </div>
 <button popovertarget=p>Popover</button>
 <style>
-  #p6 {
+  #p {
     width: 300px;
     height: 300px;
     overflow-y: scroll;
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-for-data-url.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-for-data-url.tentative.https.sub.html
new file mode 100644
index 0000000..51283579
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-for-data-url.tentative.https.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script>
+    'use strict';
+
+    promise_test(async t => {
+       const innerCode =
+         `window.parent.postMessage({fetchStatus: "success"}, '*');`;
+       const dataURL = 'data:text/javascript;base64,'
+         + btoa(unescape(encodeURIComponent(innerCode)));
+        await promise_rejects_js(t, TypeError,
+                                 fetch(dataURL, {sharedStorageWritable: true}));
+
+    }, 'shared storage fetch request disallowed for data URL');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-data-url.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-data-url.tentative.https.sub.html
new file mode 100644
index 0000000..1ebfdbc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-data-url.tentative.https.sub.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script>
+    'use strict';
+    const origin = window.location.origin;
+    const rawSetHeader = 'set;key=hello;value=world';
+    const setHeader = encodeURIComponent(rawSetHeader);
+
+    promise_test(async t => {
+      let frame = document.createElement('iframe');
+      const promise = new Promise((resolve, reject) => {
+        window.addEventListener('message', async function handler(evt) {
+          if (evt.source === frame.contentWindow &&
+              evt.data.sharedStorageFetchStatus) {
+            document.body.removeChild(frame);
+            window.removeEventListener('message', handler);
+            resolve(evt.data.sharedStorageFetchStatus);
+          }
+        });
+        window.addEventListener('error', (error) => {
+          reject(error);
+        });
+      });
+
+      const fetchUrl =
+        `${origin}\\/shared-storage\\/resources\\/shared-storage-write.py`
+        + `?write=${setHeader}`;
+      const fetchCode = `
+let parentOrOpener = window.opener || window.parent;
+let innerFrame = document.createElement('iframe');
+window.addEventListener('message', async (evt) => {
+  if (evt.source === innerFrame.contentWindow) {
+    parentOrOpener.postMessage({sharedStorageFetchStatus: "success"}, '*');
+  }
+});
+window.addEventListener('error', (error) => {
+  parentOrOpener.postMessage({sharedStorageFetchStatus: error.message}, '*');
+});
+fetch('${fetchUrl}', {sharedStorageWritable: true})
+  .then(response => response.text())
+  .then(htmlContent => {
+    innerFrame.srcdoc = htmlContent;
+    document.body.appendChild(innerFrame);
+  })
+  .catch(error => {
+    parentOrOpener.postMessage({sharedStorageFetchStatus: error.name},
+                                "*");
+  });
+`;
+
+      const dataURL = 'data:text/html;base64,'
+        + btoa(unescape('%3Chtml%3E%3Cbody%3E%3Cscript%3E'
+                        + encodeURIComponent(fetchCode) +
+                        '%3C%2Fscript%3E%3C%2Fbody%3E%3C%2Fhtml%3E'));
+      frame.src = dataURL;
+      document.body.appendChild(frame);
+
+      const result = await promise;
+      assert_equals(result, "TypeError");
+      await verifyKeyNotFoundForOrigin('hello', origin);
+
+    }, 'shared storage fetch request disallowed in opaque origin from data URL');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-sandboxed-frame.tentative.https.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-sandboxed-frame.tentative.https.html
index de935b2..cb0f8fb 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-sandboxed-frame.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-fetch-request-in-sandboxed-frame.tentative.https.html
@@ -79,7 +79,7 @@
         /*key=*/'c',
         /*value=*/'d',
         /*sandbox_flags=*/'allow-scripts',
-        /*expect_success=*/false);
+        /*expect_success=*/true);
     }, 'test sharedStorageWritable fetch request in sandboxed iframe without '
          + '"allow-same-origin"');
   </script>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-data-url.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-data-url.tentative.https.sub.html
new file mode 100644
index 0000000..1833e842
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-iframe-request-in-data-url.tentative.https.sub.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script>
+    'use strict';
+    const origin = window.location.origin;
+    const rawSetHeader = 'set;key=hello;value=world';
+    const setHeader = encodeURIComponent(rawSetHeader);
+
+    promise_test(async t => {
+      let frame = document.createElement('iframe');
+      const promise = new Promise((resolve, reject) => {
+        window.addEventListener('message', async function handler(evt) {
+          if (evt.source === frame.contentWindow &&
+              evt.data.sharedStorageWritableHeader) {
+            document.body.removeChild(frame);
+            window.removeEventListener('message', handler);
+            resolve(evt.data.sharedStorageWritableHeader);
+          }
+        });
+        window.addEventListener('error', (error) => {
+          reject(error);
+        });
+      });
+
+      const innerUrl =
+        `${origin}\\/shared-storage\\/resources\\/shared-storage-write-`
+        + `notify-parent.py?write=${setHeader}`;
+      const innerCode = `
+let parentOrOpener = window.opener || window.parent;
+let innerFrame = document.createElement('iframe');
+window.addEventListener('message', async (evt) => {
+  if (evt.source === innerFrame.contentWindow &&
+      evt.data.sharedStorageWritableHeader) {
+    parentOrOpener.postMessage({sharedStorageWritableHeader:
+                                evt.data.sharedStorageWritableHeader}, '*');
+  }
+});
+window.addEventListener('error', (error) => {
+  parentOrOpener.postMessage({sharedStorageWritableHeader: error.message}, '*');
+});
+innerFrame.src = '${innerUrl}';
+innerFrame.sharedStorageWritable = true;
+document.body.appendChild(innerFrame);
+`;
+
+      const dataURL = 'data:text/html;base64,'
+        + btoa(unescape('%3Chtml%3E%3Cbody%3E%3Cscript%3E'
+                        + encodeURIComponent(innerCode) +
+                        '%3C%2Fscript%3E%3C%2Fbody%3E%3C%2Fhtml%3E'));
+      frame.src = dataURL;
+      document.body.appendChild(frame);
+
+      const result = await promise;
+      assert_equals(result, "NO_SHARED_STORAGE_WRITABLE_HEADER");
+      await verifyKeyNotFoundForOrigin('hello', origin);
+
+    }, 'shared storage iframe request disallowed in opaque origin from data URL');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-data-url.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-data-url.tentative.https.sub.html
new file mode 100644
index 0000000..75d6b51
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-data-url.tentative.https.sub.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script>
+    'use strict';
+    const origin = window.location.origin;
+    const rawSetHeader = 'set;key=hello;value=world';
+    const setHeader = encodeURIComponent(rawSetHeader);
+
+    promise_test(async t => {
+      let frame = document.createElement('iframe');
+      const promise = new Promise((resolve, reject) => {
+        window.addEventListener('message', async function handler(evt) {
+          if (evt.source === frame.contentWindow &&
+              evt.data.sharedStorageWritableLoadStatus) {
+            document.body.removeChild(frame);
+            window.removeEventListener('message', handler);
+            resolve(evt.data.sharedStorageWritableLoadStatus);
+          }
+        });
+        window.addEventListener('error', (error) => {
+          reject(error);
+        });
+      });
+
+      const imageUrl =
+        `${origin}\\/shared-storage\\/resources\\/shared-storage-writable-`
+        + `pixel-write.png?write=${setHeader}`;
+      const innerCode = `
+let parentOrOpener = window.opener || window.parent;
+let image = document.createElement('img');
+window.addEventListener('load', async (evt) => {
+  parentOrOpener.postMessage({sharedStorageWritableLoadStatus:
+                              'loaded'}, '*');
+});
+window.addEventListener('error', (error) => {
+  parentOrOpener.postMessage({sharedStorageWritableLoadStatus: error.message},
+                             '*');
+});
+image.src = '${imageUrl}';
+image.sharedStorageWritable = true;
+document.body.appendChild(image);
+`;
+
+      const dataURL = 'data:text/html;base64,'
+        + btoa(unescape('%3Chtml%3E%3Cbody%3E%3Cscript%3E'
+                        + encodeURIComponent(innerCode) +
+                        '%3C%2Fscript%3E%3C%2Fbody%3E%3C%2Fhtml%3E'));
+      frame.src = dataURL;
+      document.body.appendChild(frame);
+
+      const result = await promise;
+      assert_equals(result, "loaded");
+      await verifyKeyNotFoundForOrigin('hello', origin);
+
+    }, 'shared storage iframe request disallowed in opaque origin from data URL');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-sandboxed-frame.tentative.https.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-sandboxed-frame.tentative.https.html
index a901500d..8e33c68 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-sandboxed-frame.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-img-request-in-sandboxed-frame.tentative.https.html
@@ -72,7 +72,7 @@
         /*key=*/'c',
         /*value=*/'d',
         /*sandbox_flags=*/'allow-scripts',
-        /*expect_success=*/false);
+        /*expect_success=*/true);
     }, 'test sharedStorageWritable img request in sandboxed iframe without '
          + '"allow-same-origin"');
   </script>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-opaque-origin.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-opaque-origin.tentative.https.sub.html
deleted file mode 100644
index 4829af0..0000000
--- a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-opaque-origin.tentative.https.sub.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!doctype html>
-<body>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script>
-    'use strict';
-    const rawSetHeader = 'set;key=hello;value=world';
-    const setHeader = encodeURIComponent(rawSetHeader);
-
-    promise_test(async t => {
-      const fetchUrl =
-        `/shared-storage/resources/shared-storage-write.py?write=${setHeader}`;
-      const fetchCode =
-        `try {
-           new Request(fetchUrl,{sharedStorageWritable: true});
-         } catch (e) {
-           assert_equals(e.name, 'TypeError');
-           assert_equals(e.message, "Failed to construct 'Request': "
-                      + "sharedStorageWritable: sharedStorage operations "
-                      + "are not available for opaque origins.");
-           return;
-         }
-         assert_unreached("did not catch an error");`
-
-      const dataURL = 'data:text/javascript;base64,'
-        + btoa(unescape(encodeURIComponent(fetchCode)));
-      let frame = document.createElement('iframe');
-      frame.src = dataURL;
-      const promise = new Promise((resolve, reject) => {
-       frame.addEventListener('load', () => {
-          resolve();
-        });
-        frame.addEventListener('error', () => {
-          reject(new Error('Navigation failed'));
-        });
-      });
-      document.body.appendChild(frame);
-      await promise;
-    }, 'shared storage fetch request disallowed for opaque origin');
-  </script>
-</body>
diff --git a/third_party/blink/web_tests/media/resources/one_frame.webm b/third_party/blink/web_tests/media/resources/one_frame.webm
new file mode 100644
index 0000000..e343106a
--- /dev/null
+++ b/third_party/blink/web_tests/media/resources/one_frame.webm
Binary files differ
diff --git a/third_party/blink/web_tests/media/video-no-infinite-loop-paused.html b/third_party/blink/web_tests/media/video-no-infinite-loop-paused.html
new file mode 100644
index 0000000..61c193555
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-no-infinite-loop-paused.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Test a single frame video doesn't loop too quickly</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<video loop paused muted></video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    var count = 0;
+    video.onseeked = t.unreached_func("Paused video shouldn't loop");
+    video.onended = t.unreached_func("Paused video shouldn't end");
+    video.oncanplaythrough = t.step_func(_ => {
+        // 275ms chosen since 250ms is the max timeupdate frequency.
+        t.step_timeout(t.step_func_done(), 275);
+    });
+    video.src = "resources/one_frame.webm";
+});
+</script>
diff --git a/third_party/blink/web_tests/media/video-no-infinite-loop-playing-rvfc.html b/third_party/blink/web_tests/media/video-no-infinite-loop-playing-rvfc.html
new file mode 100644
index 0000000..c950ffd
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-no-infinite-loop-playing-rvfc.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test a single frame looping video generates rVFCs properly</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<video loop autoplay muted></video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    var start = 0;
+    var count = 0;
+    var rvfc = 0;
+
+    video.onseeking = t.step_func(_ => {
+      video.requestVideoFrameCallback(_ => {
+        ++rvfc;
+      });
+    });
+    video.onseeked = t.step_func(_ => {
+      if (++count == 3) {
+            // Approximate since ordering is not guaranteed.
+            assert_approx_equals(rvfc, count, 1);
+            t.done();
+      }
+    });
+    video.src = "resources/one_frame.webm";
+});
+</script>
diff --git a/third_party/blink/web_tests/media/video-no-infinite-loop-playing.html b/third_party/blink/web_tests/media/video-no-infinite-loop-playing.html
new file mode 100644
index 0000000..ab71860
--- /dev/null
+++ b/third_party/blink/web_tests/media/video-no-infinite-loop-playing.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Test a single frame video doesn't loop too quickly</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<video loop autoplay muted></video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    var start = 0;
+    var count = 0;
+    video.onseeked = t.step_func(_ => {
+        if (++count == 1) {
+            start = performance.now();
+        } else if (count == 3) {
+            let dur = performance.now() - start;
+            assert_greater_than_equal(dur / (count - 1), 250);
+            t.done();
+        }
+    });
+    video.src = "resources/one_frame.webm";
+});
+</script>
diff --git a/third_party/blink/web_tests/media/video-seek-to-duration-with-playbackrate-zero.html b/third_party/blink/web_tests/media/video-seek-to-duration-with-playbackrate-zero.html
index 454ebb81..605ffc48 100644
--- a/third_party/blink/web_tests/media/video-seek-to-duration-with-playbackrate-zero.html
+++ b/third_party/blink/web_tests/media/video-seek-to-duration-with-playbackrate-zero.html
@@ -6,31 +6,29 @@
 <script>
 async_test(function(t) {
     var video = document.querySelector("video");
-
-    video.src = "content/test.ogv";
-    video.load();
-    video.onseeking = t.step_func(function() {});
-
     video.onloadedmetadata = t.step_func(function() {
         video.onloadedmetadata = null;
-        video.currentTime = video.duration;
         video.playbackRate = 0;
-        video.onended = t.step_func(function() {
+        video.currentTime = video.duration;
+        video.onseeked = t.step_func(function() {
             assert_equals(video.currentTime, video.duration);
-            // Seeking to the middle of the video.
-            video.currentTime = video.duration / 2;
             video.onseeked = t.step_func(function() {
-                // Setting loop to true and seeking to duration.
-                video.loop = true;
-                video.currentTime = video.duration;
                 video.onseeked = t.step_func(function() {
                     // Seek to duration completed. Waiting for a seek to the beginning.
                     video.onseeked = t.step_func_done(function() {
                         assert_equals(video.currentTime, 0);
                     });
+                    video.play();
                 });
+                // Setting loop to true and seeking to duration.
+                video.loop = true;
+                video.currentTime = video.duration;
             });
+
+            // Seeking to the middle of the video.
+            video.currentTime = video.duration / 2;
         });
     });
+    video.src = "content/test.ogv";
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any-expected.txt
new file mode 100644
index 0000000..552532e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any-expected.txt
@@ -0,0 +1,32 @@
+This is a testharness.js-based test.
+Found 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
+[FAIL] idl_test setup
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': Not implemented"
+[FAIL] NavigatorML must be primary interface of navigator
+  assert_own_property: self does not have own property "NavigatorML" expected property "NavigatorML" missing
+[FAIL] Stringification of navigator
+  assert_class_string: class string of navigator expected "[object NavigatorML]" but got "[object Navigator]"
+[FAIL] MLGraph must be primary interface of graph
+  assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: graph is not defined"
+[FAIL] Stringification of graph
+  assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: graph is not defined"
+[FAIL] MLGraphBuilder interface: operation constant(MLOperandDescriptor, ArrayBufferView)
+  assert_equals: property has wrong .length expected 1 but got 2
+[FAIL] MLGraphBuilder interface: operation constant(double, optional MLOperandDataType)
+  assert_equals: property has wrong .length expected 1 but got 2
+[FAIL] MLGraphBuilder interface: operation not(MLOperand)
+  assert_own_property: interface prototype object missing non-static operation expected property "not" missing
+[FAIL] MLGraphBuilder interface: operation softmax(MLOperand, unsigned long)
+  assert_equals: property has wrong .length expected 1 but got 0
+[FAIL] MLGraphBuilder interface: operation softmax(unsigned long)
+  assert_equals: property has wrong .length expected 1 but got 0
+[FAIL] MLGraphBuilder interface: builder must inherit property "not(MLOperand)" with the proper type
+  assert_inherits: property "not" not found in prototype chain
+[FAIL] MLGraphBuilder interface: calling not(MLOperand) on builder with too few arguments must throw TypeError
+  assert_inherits: property "not" not found in prototype chain
+[FAIL] MLGraphBuilder interface: calling softmax(MLOperand, unsigned long) on builder with too few arguments must throw TypeError
+  assert_throws_js: Called with 0 arguments function "function() {\n            fn.apply(obj, args);\n        }" did not throw
+[FAIL] MLGraphBuilder interface: calling softmax(unsigned long) on builder with too few arguments must throw TypeError
+  assert_throws_js: Called with 0 arguments function "function() {\n            fn.apply(obj, args);\n        }" did not throw
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any.worker-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any.worker-expected.txt
new file mode 100644
index 0000000..70c9d9e1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/external/wpt/webnn/idlharness.https.any.worker-expected.txt
@@ -0,0 +1,34 @@
+This is a testharness.js-based test.
+Found 15 FAIL, 0 TIMEOUT, 0 NOTRUN.
+[FAIL] idl_test setup
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': Not implemented"
+[FAIL] NavigatorML must be primary interface of navigator
+  assert_own_property: self does not have own property "NavigatorML" expected property "NavigatorML" missing
+[FAIL] Stringification of navigator
+  assert_class_string: class string of navigator expected "[object NavigatorML]" but got "[object WorkerNavigator]"
+[FAIL] NavigatorML interface: navigator must not have property "ml"
+  assert_false: expected false got true
+[FAIL] MLGraph must be primary interface of graph
+  assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: graph is not defined"
+[FAIL] Stringification of graph
+  assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: graph is not defined"
+[FAIL] MLGraphBuilder interface: operation constant(MLOperandDescriptor, ArrayBufferView)
+  assert_equals: property has wrong .length expected 1 but got 2
+[FAIL] MLGraphBuilder interface: operation constant(double, optional MLOperandDataType)
+  assert_equals: property has wrong .length expected 1 but got 2
+[FAIL] MLGraphBuilder interface: operation not(MLOperand)
+  assert_own_property: interface prototype object missing non-static operation expected property "not" missing
+[FAIL] MLGraphBuilder interface: operation softmax(MLOperand, unsigned long)
+  assert_equals: property has wrong .length expected 1 but got 0
+[FAIL] MLGraphBuilder interface: operation softmax(unsigned long)
+  assert_equals: property has wrong .length expected 1 but got 0
+[FAIL] MLGraphBuilder interface: builder must inherit property "not(MLOperand)" with the proper type
+  assert_inherits: property "not" not found in prototype chain
+[FAIL] MLGraphBuilder interface: calling not(MLOperand) on builder with too few arguments must throw TypeError
+  assert_inherits: property "not" not found in prototype chain
+[FAIL] MLGraphBuilder interface: calling softmax(MLOperand, unsigned long) on builder with too few arguments must throw TypeError
+  assert_throws_js: Called with 0 arguments function "function() {\n            fn.apply(obj, args);\n        }" did not throw
+[FAIL] MLGraphBuilder interface: calling softmax(unsigned long) on builder with too few arguments must throw TypeError
+  assert_throws_js: Called with 0 arguments function "function() {\n            fn.apply(obj, args);\n        }" did not throw
+Harness: the test ran to completion.
+
diff --git a/third_party/chromite b/third_party/chromite
index 8a31c4e..8199e2b 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 8a31c4e2f3416b669a31b6e6b8be77bf4ec12532
+Subproject commit 8199e2bbaef867e258148454b1ee7a472fe3a638
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 9e9b012..9485c21 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 9e9b01285b3b1febec28c95040e7ed89e3b86bc8
+Subproject commit 9485c21d7b149d1fd9a663762182a7838095913e
diff --git a/third_party/dawn b/third_party/dawn
index 7ffb6e9..538f02e 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 7ffb6e9573216af00374a8dd474598ce94f2d97a
+Subproject commit 538f02eecf1a80738f1ece234bbf6c947d127add
diff --git a/third_party/depot_tools b/third_party/depot_tools
index d32e1cb..0dd5028 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit d32e1cb5717853a1837347884abc85149813c398
+Subproject commit 0dd502813398cb44477df2c246ed4495c4a0c46d
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 701f478..454bc68 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 701f4784e9e7d73927dcf4d60bc0f0db552b79b7
+Subproject commit 454bc68b84610f49e617ab997e056e7cb2429ad5
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index c1d688b..ecbae84 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit c1d688bcb2e1d1ebcebbe07e48ddcfa90228b138
+Subproject commit ecbae84d39c9a39365dfbcb791c6b78d4df6e373
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index 7463dd66..b6ce0758 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -1,7 +1,7 @@
 Name: libvpx
 URL: https://chromium.googlesource.com/webm/libvpx
 Version: N/A
-Revision: 611d9ba0a55df154ff5cb7a97d41a41103265f5e
+Revision: 5b4cfe88e45a42fbdf22f7210ed253faec9c0fe6
 CPEPrefix: cpe:/a:webmproject:libvpx:1.14.0
 License: BSD
 License File: source/libvpx/LICENSE
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 9b8a656..5672df6 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,8 +2,8 @@
 #define VERSION_MAJOR 1
 #define VERSION_MINOR 14
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "276-g611d9ba0a"
+#define VERSION_EXTRA "277-g5b4cfe88e"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.14.0-276-g611d9ba0a"
-#define VERSION_STRING " v1.14.0-276-g611d9ba0a"
+#define VERSION_STRING_NOSP "v1.14.0-277-g5b4cfe88e"
+#define VERSION_STRING " v1.14.0-277-g5b4cfe88e"
diff --git a/third_party/libvpx/source/libvpx b/third_party/libvpx/source/libvpx
index 611d9ba..5b4cfe8 160000
--- a/third_party/libvpx/source/libvpx
+++ b/third_party/libvpx/source/libvpx
@@ -1 +1 @@
-Subproject commit 611d9ba0a55df154ff5cb7a97d41a41103265f5e
+Subproject commit 5b4cfe88e45a42fbdf22f7210ed253faec9c0fe6
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index 1766121..d77735d 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit 17661211decceb52c9597a8bed5c5bdfcc44a2a2
+Subproject commit d77735d9e5ef30477139b0fc1ca3db6706ec8651
diff --git a/third_party/re2/BUILD.gn b/third_party/re2/BUILD.gn
index 046bf29..da39856 100644
--- a/third_party/re2/BUILD.gn
+++ b/third_party/re2/BUILD.gn
@@ -44,7 +44,6 @@
     "src/re2/unicode_groups.cc",
     "src/re2/unicode_groups.h",
     "src/re2/walker-inl.h",
-    "src/util/logging.h",
     "src/util/rune.cc",
     "src/util/strutil.cc",
     "src/util/strutil.h",
diff --git a/third_party/re2/src b/third_party/re2/src
index a67d6c1..a771d3f 160000
--- a/third_party/re2/src
+++ b/third_party/re2/src
@@ -1 +1 @@
-Subproject commit a67d6c1d5308b0950a91fed8e03e5c048d22d5cd
+Subproject commit a771d3fbe7c432dc4db68360c6c0004fdde5646b
diff --git a/third_party/skia b/third_party/skia
index 3d3d12b..62f369c 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 3d3d12b3fed33cf7d43f69204c3b91656f2f59d9
+Subproject commit 62f369c759947272dfdd2d8f060afadbcc361e79
diff --git a/third_party/webrtc b/third_party/webrtc
index f317f71..fa4128e 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit f317f7106a7a15a04da7cd30c2e2ddb1b3025bc6
+Subproject commit fa4128eedbe427eecd28988f2585753645514296
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 9e5b8d7..2db5ad40 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -890,6 +890,10 @@
     "META": {"sizes": {"includes": [50],}},
     "includes": [6180],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/ash/webui/sanitize_ui/resources/resources.grd": {
+    "META": {"sizes": {"includes": [50],}},
+    "includes": [6190],
+  },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/scanning/resources/resources.grd": {
     "META": {"sizes": {"includes": [100],}},
     "includes": [6200],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4e0344e..1d46e76 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -35440,14 +35440,19 @@
   <int value="-2025866230" label="chrome://usb-internals/"/>
   <int value="-2024468885" label="chrome://ntp-tiles-internals/"/>
   <int value="-2020874093" label="chrome://lock-reauth/"/>
+  <int value="-2008798042" label="chrome://extended-updates-dialog/"/>
   <int value="-1977692791" label="chrome://crash/"/>
   <int value="-1970917350" label="chrome-untrusted://compose/"/>
   <int value="-1949142440" label="chrome://smb-credentials-dialog/"/>
+  <int value="-1941995226" label="chrome://attribution-internals/"/>
   <int value="-1911971715" label="chrome://history/"/>
+  <int value="-1899361569" label="chrome-untrusted://eche-app/"/>
   <int value="-1887206190" label="chrome://nfc-debug/"/>
+  <int value="-1837941403" label="chrome://launcher-internals/"/>
   <int value="-1825876526" label="chrome://delayeduithreadhang/"/>
   <int value="-1808179141" label="chrome://password-manager-internals/"/>
   <int value="-1777503123" label="chrome://thumb2/"/>
+  <int value="-1774623401" label="chrome://os-print/"/>
   <int value="-1743341803" label="chrome://bookmarks/"/>
   <int value="-1737358104" label="chrome://account-migration-welcome/"/>
   <int value="-1729122580" label="chrome://chrome-urls/"/>
@@ -35458,29 +35463,39 @@
   <int value="-1697077152" label="chrome://device-log/"/>
   <int value="-1690355375" label="chrome://mobilesetup/"/>
   <int value="-1681544613" label="chrome://print/"/>
+  <int value="-1667471433" label="chrome://vc-background/"/>
   <int value="-1649429396" label="chrome://chrome-signin/edu-coexistence/"/>
   <int value="-1647734092" label="chrome://net-export/"/>
   <int value="-1638649228" label="chrome://webapks/"/>
   <int value="-1636191836"
       label="chrome://history-clusters-side-panel.top-chrome/"/>
   <int value="-1634933990" label="chrome://web-footer-experiment/"/>
+  <int value="-1631133219" label="chrome://help-app/"/>
+  <int value="-1618666743" label="chrome://office-fallback/"/>
   <int value="-1591890659" label="chrome://syncfs-internals/"/>
   <int value="-1564946100" label="chrome://tab-strip.top-chrome/"/>
   <int value="-1532727290" label="chrome://whats-new/"/>
+  <int value="-1500737448" label="chrome://manage-mirrorsync/"/>
   <int value="-1493822642" label="chrome://invalidations/"/>
   <int value="-1493762713" label="chrome://settings/cookies/"/>
   <int value="-1475777739" label="chrome://physical-web-diagnostics/"/>
+  <int value="-1471376798" label="chrome-untrusted://print/"/>
   <int value="-1440785181" label="chrome://profile-signin-confirmation/"/>
   <int value="-1423026122" label="chrome://md-user-manager/"/>
   <int value="-1401057585" label="chrome://account-manager-error/"/>
   <int value="-1398642514" label="chrome://gesture/"/>
+  <int value="-1384888924" label="chrome-untrusted://file-manager/"/>
   <int value="-1382906531" label="chrome://managed-user-passphrase/"/>
   <int value="-1379571130" label="chrome://newtab/"/>
   <int value="-1378421581" label="chrome://media-router/"/>
   <int value="-1346911023" label="chrome://signin-dice-web-intercept/"/>
   <int value="-1331415629" label="chrome://os-settings/"/>
   <int value="-1313432739" label="chrome://plugins/"/>
+  <int value="-1296182448" label="chrome://vc-tray-tester/"/>
+  <int value="-1293039192" label="chrome://os-feedback/"/>
   <int value="-1282128285" label="chrome://suggest-internals/"/>
+  <int value="-1267771224" label="chrome://sensor-info/"/>
+  <int value="-1265265027" label="chrome-untrusted://help-app/"/>
   <int value="-1259871417" label="chrome://browser-switch/"/>
   <int value="-1239015343" label="chrome://kill/"/>
   <int value="-1224398205" label="chrome://prefs-internals/"/>
@@ -35489,13 +35504,18 @@
   <int value="-1177126000" label="chrome://profile-internals/"/>
   <int value="-1176451566" label="chrome://access-code-cast/"/>
   <int value="-1169621742" label="chrome://linux-proxy-config/"/>
+  <int value="-1147478891" label="chrome-untrusted://sample-system-web-app/"/>
   <int value="-1137946367" label="chrome://webrt-logs/"/>
   <int value="-1108319739" label="chrome://sync-internals/"/>
   <int value="-1087461470" label="chrome://management/"/>
   <int value="-1082746738" label="chrome://credits/"/>
   <int value="-1078583150" label="chrome://view-cert-dialog/"/>
   <int value="-1043041749" label="chrome://offline-internals/"/>
+  <int value="-1033927589" label="chrome://files-internals/"/>
+  <int value="-1013183905" label="chrome://media-app/"/>
+  <int value="-992950394" label="chrome://compare/"/>
   <int value="-992301003" label="chrome://bookmarks-side-panel.top-chrome/"/>
+  <int value="-986956132" label="chrome://app-install-dialog/"/>
   <int value="-986409800" label="chrome://identity-internals/"/>
   <int value="-969979971" label="chrome://sandbox/"/>
   <int value="-949665427"
@@ -35503,21 +35523,27 @@
   <int value="-946383082" label="chrome://translate-internals/"/>
   <int value="-940484840" label="chrome://arc-power-control/"/>
   <int value="-936786926" label="chrome://settings/"/>
+  <int value="-933829627" label="chrome-untrusted://mako/"/>
   <int value="-907485548" label="chrome://policy-tool/"/>
   <int value="-905972126" label="chrome://signin-email-confirmation/"/>
   <int value="-892484505" label="chrome://sys-internals/"/>
   <int value="-891260101" label="chrome://inspect/"/>
   <int value="-853789543" label="chrome://bluetooth-pairing/"/>
+  <int value="-833683433" label="chrome://on-device-internals/"/>
   <int value="-823585847" label="chrome://chrome-signin/"/>
   <int value="-756514973" label="chrome://version/"/>
   <int value="-753390931" label="chrome://interstitials/"/>
   <int value="-734627230" label="chrome://extension-icon/"/>
   <int value="-727952446" label="chrome://nacl/"/>
   <int value="-707542503" label="chrome://choose-mobile-network/"/>
+  <int value="-680649366" label="chrome-untrusted://hats/"/>
+  <int value="-675896339" label="chrome://healthd-internals/"/>
   <int value="-672072143" label="chrome://site-engagement/"/>
   <int value="-661305899" label="chrome://set-time/"/>
   <int value="-652517395" label="chrome://signin-reauth/"/>
   <int value="-644310325" label="chrome://os-credits/"/>
+  <int value="-643868788"
+      label="chrome-untrusted://companion-side-panel.top-chrome/"/>
   <int value="-634350465" label="chrome://ukm/"/>
   <int value="-631384656" label="chrome://assistant-optin/"/>
   <int value="-624584089"
@@ -35526,12 +35552,16 @@
   <int value="-623121238" label="chrome://arc-graphics-tracing/"/>
   <int value="-614294009" label="chrome://crostini-credits/"/>
   <int value="-606058756" label="chrome://accessibility/"/>
+  <int value="-601472337" label="chrome://shimless-rma/"/>
   <int value="-566908909" label="chrome://suggestions/"/>
   <int value="-548156607" label="chrome://feedback/"/>
   <int value="-528306235" label="chrome://sync/"/>
+  <int value="-525954994" label="chrome-untrusted://lens/"/>
   <int value="-516792794" label="chrome://downloads/"/>
+  <int value="-515586256" label="chrome-untrusted://media-app/"/>
   <int value="-512782085" label="chrome://salsa/"/>
   <int value="-511253776" label="chrome://snippets-internals/"/>
+  <int value="-492330636" label="chrome://connectivity-diagnostics/"/>
   <int value="-485494550" label="chrome://interventions-internals/"/>
   <int value="-476825727" label="chrome://user-notes-side-panel.top-chrome/"/>
   <int value="-451371558" label="chrome://serviceworker-internals/"/>
@@ -35539,11 +35569,15 @@
   <int value="-394586044" label="chrome://confirm-password-change/"/>
   <int value="-377028639" label="chrome://copresence/"/>
   <int value="-359703631" label="chrome://slow_trace/"/>
+  <int value="-336117866" label="chrome://focus-mode-media/"/>
   <int value="-245337017" label="chrome://user-actions/"/>
+  <int value="-233401490" label="chrome://accessory-update/"/>
   <int value="-222190329" label="chrome://dino/"/>
+  <int value="-207369615" label="chrome://location-internals/"/>
   <int value="-206925255" label="chrome://popular-sites-internals/"/>
   <int value="-176639700" label="chrome://omnibox/"/>
   <int value="-106823253" label="chrome://conflicts/"/>
+  <int value="-103805443" label="chrome://nearby-internals/"/>
   <int value="-90956977" label="chrome://dns/"/>
   <int value="-89853739" label="chrome://thumbnails/"/>
   <int value="-88246682" label="chrome://uithreadhang/"/>
@@ -35556,16 +35590,19 @@
   <int value="6575726" label="chrome://app-settings/"/>
   <int value="9727114" label="chrome://favicon/"/>
   <int value="14651785" label="chrome://download-internals/"/>
+  <int value="30059183" label="chrome://kerberos-in-browser/"/>
   <int value="34035361" label="chrome://apps/"/>
   <int value="43923599" label="chrome://tracing/"/>
   <int value="47779771" label="chrome://media-router-internals/"/>
   <int value="48333657" label="chrome://about/"/>
+  <int value="57900378" label="chrome://conch/"/>
   <int value="58807865" label="chrome://local-state/"/>
   <int value="84832110" label="chrome://fileicon/"/>
   <int value="112165789" label="chrome://device-emulator/"/>
   <int value="114748825" label="chrome-devtools://devtools/"/>
   <int value="164517522" label="chrome://discards/"/>
   <int value="171659041" label="chrome://webrtc-device-provider/"/>
+  <int value="182993612" label="chrome-untrusted://help-app-kids-magazine/"/>
   <int value="253258497" label="chrome://components/"/>
   <int value="260253931" label="chrome://devices/"/>
   <int value="275260142" label="chrome://eoc-internals/"/>
@@ -35574,14 +35611,21 @@
   <int value="361621847" label="chrome://make-metro/"/>
   <int value="372516928" label="chrome://histograms/"/>
   <int value="399773145" label="chrome://commander/"/>
+  <int value="402408777" label="chrome-untrusted://crosh/"/>
+  <int value="405280734" label="chrome://data-sharing-internals/"/>
+  <int value="435700552" label="chrome-untrusted://scalable-iph-debug/"/>
   <int value="436626245" label="chrome://settings/content/cookies/"/>
   <int value="454041716" label="chrome://new-tab-page-third-party/"/>
+  <int value="459058510" label="chrome://status-area-internals/"/>
+  <int value="460232055" label="chrome://lens-search-bubble/"/>
   <int value="461287992" label="chrome://help/"/>
   <int value="507734656" label="chrome://profile-customization/"/>
   <int value="520299179" label="chrome://media-engagement/"/>
+  <int value="543723090" label="chrome://traces-internals/"/>
   <int value="554090510" label="chrome://first-run/"/>
   <int value="581234974" label="chrome://tcmalloc/"/>
   <int value="584098160" label="chrome://profile-picker/"/>
+  <int value="592330525" label="chrome://metrics-internals/"/>
   <int value="607659257" label="chrome://cast/"/>
   <int value="634002171" label="chrome://projector-selfie-cam/"/>
   <int value="646837226" label="chrome://crashes/"/>
@@ -35590,53 +35634,73 @@
   <int value="689991895" label="chrome://supervised-user-internals/"/>
   <int value="701883986" label="chrome://privacy-sandbox-dialog/"/>
   <int value="703814809" label="chrome://intro/"/>
+  <int value="705531796" label="chrome://webxr-internals/"/>
   <int value="709341402" label="chrome://omnibox-popup.top-chrome/"/>
   <int value="742272091" label="chrome://extensions/"/>
+  <int value="745470397" label="chrome://parent-access/"/>
   <int value="748142712" label="chrome://crostini-upgrader/"/>
   <int value="749881882" label="chrome://webuijserror/"/>
   <int value="754315733" label="chrome://theme/"/>
+  <int value="770641362" label="chrome://shortcut-customization/"/>
   <int value="784140714" label="chrome://predictors/"/>
   <int value="812624791" label="chrome://dom-distiller/"/>
   <int value="821023334" label="chrome://screenlock-icon/"/>
+  <int value="858923205" label="chrome://cloud-upload/"/>
   <int value="864711974" label="chrome://emoji-picker/"/>
+  <int value="890996135" label="chrome://enterprise-reporting/"/>
   <int value="894368493" label="chrome://internet-config-dialog/"/>
   <int value="911597664" label="chrome://multidevice-setup/"/>
   <int value="918960088" label="chrome://instant/"/>
   <int value="943766809" label="chrome://view-cert/"/>
+  <int value="962490408" label="chrome-untrusted://camera-app/"/>
+  <int value="971254590"
+      label="chrome://shopping-insights-side-panel.top-chrome/"/>
   <int value="977219287" label="chrome://memory/"/>
   <int value="985294599" label="chrome://webrtc-logs/"/>
   <int value="1015167335" label="chrome://lens/"/>
+  <int value="1044107189" label="chrome://sample-system-web-app/"/>
   <int value="1050328415" label="chrome://diagnostics/"/>
+  <int value="1050817548" label="chrome://camera-app/"/>
   <int value="1055519744" label="chrome://webrtc-internals/"/>
+  <int value="1058168190" label="chrome://color-internals/"/>
+  <int value="1059833308" label="chrome://hps-internals/"/>
   <int value="1066907868" label="chrome://physical-web/"/>
   <int value="1076364348" label="chrome://web-app-internals/"/>
+  <int value="1084212752" label="chrome://personalization/"/>
   <int value="1109407387" label="chrome://keyboardoverlay/"/>
   <int value="1131405887" label="chrome://taskscheduler-internals/"/>
   <int value="1134924982" label="chrome://app-icon/"/>
   <int value="1145000455" label="chrome://arc-overview-tracing/"/>
   <int value="1145963223" label="chrome://process-internals/"/>
   <int value="1148733422" label="chrome://restart/"/>
+  <int value="1181155999" label="chrome://audio/"/>
+  <int value="1206367958" label="chrome://private-aggregation-internals/"/>
   <int value="1211642987" label="chrome://voicesearch/"/>
   <int value="1213428635" label="chrome://memory-redirect/"/>
   <int value="1214710734" label="chrome://welcome/"/>
   <int value="1221097359" label="chrome://constrained-test/"/>
+  <int value="1222534887" label="chrome://multidevice-internals/"/>
   <int value="1265583806" label="chrome://password-manager/"/>
   <int value="1272316136" label="chrome://image/"/>
   <int value="1281801197" label="chrome://terms/"/>
   <int value="1286620182" label="chrome://reset-password/"/>
   <int value="1306215264" label="chrome://activationmessage/"/>
   <int value="1326111536" label="chrome://new-tab-page/"/>
+  <int value="1332050252" label="chrome://borealis-installer/"/>
   <int value="1340926535" label="chrome://network/"/>
   <int value="1354146226" label="chrome://tab-modal-confirm-dialog/"/>
   <int value="1356087814" label="chrome://add-supervision/"/>
   <int value="1361577626" label="chrome://connectors-internals/"/>
   <int value="1371905827" label="chrome://flags/"/>
+  <int value="1384721582" label="chrome-untrusted://demo-mode-app/"/>
   <int value="1396129399" label="chrome://flash/"/>
   <int value="1397728473" label="chrome://crostini-installer/"/>
   <int value="1399992914" label="chrome://java-crash/"/>
   <int value="1403605293" label="chrome://internet-detail-dialog/ (duplicate)"/>
   <int value="1411665301" label="chrome://lock-network/"/>
   <int value="1427179406" label="chrome://gcm-internals/"/>
+  <int value="1437913193" label="chrome://file-manager/"/>
+  <int value="1452136061" label="chrome-untrusted://terminal/"/>
   <int value="1454088830" label="chrome://uber-frame/"/>
   <int value="1457759734" label="chrome://user-manager/"/>
   <int value="1463418339" label="chrome://proximity-auth/"/>
@@ -35644,6 +35708,7 @@
   <int value="1467342679" label="chrome://memory-internals/"/>
   <int value="1476643520" label="chrome://internet-detail-dialog/"/>
   <int value="1506419762" label="devtools://devtools/"/>
+  <int value="1527200748" label="chrome://security-curtain/"/>
   <int value="1531448147"
       label="chrome://urgent-password-expiry-notification/"/>
   <int value="1540230808" label="chrome://power/"/>
@@ -35655,6 +35720,8 @@
   <int value="1615939424" label="chrome://chrome/"/>
   <int value="1628875649" label="chrome://quota-internals/"/>
   <int value="1631369147" label="chrome://domain-reliability-internals/"/>
+  <int value="1645027983" label="chrome://notification-tester/"/>
+  <int value="1648729965" label="chrome://vm/"/>
   <int value="1662453825" label="chrome://indexeddb-internals/"/>
   <int value="1673299360" label="chrome://tab-search.top-chrome/"/>
   <int value="1677735296" label="chrome://sync-confirmation/"/>
@@ -35664,18 +35731,22 @@
   <int value="1725906496" label="chrome://imageburner/"/>
   <int value="1765337366" label="chrome://certificate-manager/"/>
   <int value="1776179126" label="chrome://thumb/"/>
+  <int value="1780987498" label="chrome://eche-app/"/>
   <int value="1797107942" label="chrome://md-settings/"/>
   <int value="1834776370" label="chrome://password-change/"/>
   <int value="1887784693" label="chrome://policy/"/>
   <int value="1906728906" label="chrome://privacy-sandbox-internals/"/>
+  <int value="1918190389" label="chrome-untrusted://projector-annotator/"/>
   <int value="1926101309" label="chrome://cryptohome/"/>
   <int value="1928106753" label="chrome://app-disabled/"/>
   <int value="1961263039" label="chrome://profiler/"/>
   <int value="1963964324" label="chrome://read-later.top-chrome/"/>
   <int value="1965127181" label="chrome://cast-feedback/"/>
   <int value="1975618905" label="chrome://app-list/"/>
+  <int value="2033331720" label="chrome-untrusted://os-feedback/"/>
   <int value="2040878656" label="chrome://bluetooth-internals/"/>
   <int value="2052019697" label="chrome://userimage/"/>
+  <int value="2108016874" label="chrome-untrusted://projector/"/>
   <int value="2111953935" label="chrome://topics-internals/"/>
   <int value="2114840772" label="chrome://drive-internals/"/>
   <int value="2120829362" label="chrome://account-manager-welcome/"/>
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 3823ce7..4c2b541a 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -28,6 +28,7 @@
   <variant name="CursorHighlight"/>
   <variant name="Dictation"/>
   <variant name="DockedMagnifier"/>
+  <variant name="FaceGaze"/>
   <variant name="FocusHighlight"/>
   <variant name="HighContrast"/>
   <variant name="LargeCursor"/>
@@ -994,7 +995,6 @@
   </summary>
   <token key="FeatureName" variants="BaseCrosAccessibilityFeatures">
     <variant name="Autoclick"/>
-    <variant name="FaceGaze"/>
     <variant name="LiveCaption"/>
     <variant name="MonoAudio"/>
     <variant name="SpokenFeedback"/>
@@ -1094,7 +1094,6 @@
   <token key="FeatureName" variants="BaseCrosAccessibilityFeatures">
     <variant name="Autoclick"/>
     <variant name="CursorColor"/>
-    <variant name="FaceGaze"/>
     <variant name="LiveCaption"/>
     <variant name="MonoAudio"/>
     <variant name="MouseKeys"/>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index a6e32df..c763cbeb 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -1105,7 +1105,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.FastPair.PairingMethod"
-    enum="FastPairPairingMethod" expires_after="2024-06-28">
+    enum="FastPairPairingMethod" expires_after="2025-06-28">
   <owner>jackshira@google.com</owner>
   <owner>dclasson@google.com</owner>
   <owner>brandosocarras@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index 834768b..d1914e0 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -219,7 +219,7 @@
 </histogram>
 
 <histogram name="Cookie.CorruptMetaTableRecovered" enum="Boolean"
-    expires_after="2024-07-01">
+    expires_after="2024-12-01">
   <owner>arichiv@chromium.org</owner>
   <owner>bingler@chromium.org</owner>
   <owner>src/net/cookies/OWNERS</owner>
@@ -389,7 +389,7 @@
 </histogram>
 
 <histogram name="Cookie.ExpirationDuration400DaysGT" units="days"
-    expires_after="2024-07-01">
+    expires_after="2024-12-01">
   <owner>arichiv@chromium.org</owner>
   <owner>bingler@chromium.org</owner>
   <summary>
@@ -399,7 +399,7 @@
 </histogram>
 
 <histogram name="Cookie.ExpirationDuration400DaysLTE" units="days"
-    expires_after="2024-09-01">
+    expires_after="2024-12-01">
   <owner>arichiv@chromium.org</owner>
   <owner>bingler@chromium.org</owner>
   <summary>
@@ -409,7 +409,7 @@
 </histogram>
 
 <histogram name="Cookie.ExpirationDurationMinutesNonSecure" units="minutes"
-    expires_after="2024-07-01">
+    expires_after="2024-12-01">
   <owner>arichiv@chromium.org</owner>
   <owner>kaustubhag@chromium.org</owner>
   <owner>bingler@chromium.org</owner>
@@ -1104,7 +1104,7 @@
 </histogram>
 
 <histogram name="Cookie.TimeDatabaseMigrationToV18" units="ms"
-    expires_after="2024-07-01">
+    expires_after="2024-12-01">
   <owner>arichiv@chromium.org</owner>
   <owner>src/net/cookies/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/download/histograms.xml b/tools/metrics/histograms/metadata/download/histograms.xml
index d01a3b1..de9d4e1 100644
--- a/tools/metrics/histograms/metadata/download/histograms.xml
+++ b/tools/metrics/histograms/metadata/download/histograms.xml
@@ -383,21 +383,6 @@
   <summary>The size of successfully completed downloads.</summary>
 </histogram>
 
-<histogram name="Download.DownloadsPageDangerousWarningWasShownBefore"
-    enum="BooleanShown" expires_after="2024-06-30">
-  <owner>chlily@chromium.org</owner>
-  <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <summary>
-    Records whether a dangerous download warning has been shown previously, when
-    it is shown on the chrome://downloads page. Logged when a dangerous download
-    item is sent to the chrome://downloads page for display, either as a new
-    item or as an update. &quot;Not shown&quot; may be logged only once per
-    download, since afterwards it will have been shown. &quot;Shown&quot; may be
-    logged more than once per download, because a given download can be shown
-    and updated multiple times.
-  </summary>
-</histogram>
-
 <histogram name="Download.HttpResponseCode" enum="HttpResponseCode"
     expires_after="never">
 <!-- expires-never: Used to monitor download system health. -->
diff --git a/tools/metrics/histograms/metadata/history/enums.xml b/tools/metrics/histograms/metadata/history/enums.xml
index edf184a3..e8227cf 100644
--- a/tools/metrics/histograms/metadata/history/enums.xml
+++ b/tools/metrics/histograms/metadata/history/enums.xml
@@ -133,6 +133,15 @@
   <int value="3" label="Both"/>
 </enum>
 
+<enum name="EmbeddingsModelInfoStatus">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Model info is valid"/>
+  <int value="2" label="Model info is empty"/>
+  <int value="3" label="Model info does not contain model metadata"/>
+  <int value="4" label="Model info has invalid model metadata"/>
+  <int value="5" label="Model info has invalid data for additional files"/>
+</enum>
+
 <enum name="EmbeddingsUserActions">
   <int value="0" label="Non-empty query History search"/>
   <int value="1" label="Embeddings search"/>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 1cb819b..34a7f9be 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1792,6 +1792,17 @@
   </summary>
 </histogram>
 
+<histogram name="History.Embeddings.Embedder.ModelInfoStatus"
+    enum="EmbeddingsModelInfoStatus" expires_after="2024-12-31">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>zekunjiang@google.com</owner>
+  <summary>
+    Whether the model metadata received from optimization guide model provider
+    is valid. Logged each time the embedder receives an model update from
+    optimization guide.
+  </summary>
+</histogram>
+
 <histogram name="History.Embeddings.Embedder.PassageTokenCount" units="tokens"
     expires_after="2024-12-31">
   <owner>sophiechang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 0ae668de..1dd7b2c1 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -1118,7 +1118,7 @@
 </histogram>
 
 <histogram name="Navigation.MainFrameHasRTLDomain2" enum="Boolean"
-    expires_after="2024-05-19">
+    expires_after="2024-11-19">
   <owner>cthomp@chromium.org</owner>
   <owner>trusty-transport@chromium.org</owner>
   <summary>
@@ -1129,7 +1129,7 @@
 </histogram>
 
 <histogram name="Navigation.MainFrameHasRTLDomainDifferentPage2" enum="Boolean"
-    expires_after="2024-05-19">
+    expires_after="2024-11-19">
   <owner>cthomp@chromium.org</owner>
   <owner>trusty-transport@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/nearby/enums.xml b/tools/metrics/histograms/metadata/nearby/enums.xml
index 6000801..5aa098d 100644
--- a/tools/metrics/histograms/metadata/nearby/enums.xml
+++ b/tools/metrics/histograms/metadata/nearby/enums.xml
@@ -33,7 +33,7 @@
 
 <enum name="NearbyConnectionsMedium">
   <int value="0" label="Unknown"/>
-  <int value="1" label="mDNS"/>
+  <int value="1" label="mDNS (deprecated)"/>
   <int value="2" label="Bluetooth"/>
   <int value="3" label="WiFi hotspot"/>
   <int value="4" label="BLE"/>
@@ -42,6 +42,8 @@
   <int value="7" label="NFC"/>
   <int value="8" label="WiFi Direct"/>
   <int value="9" label="WebRTC"/>
+  <int value="10" label="BLE_L2CAP"/>
+  <int value="11" label="USB"/>
 </enum>
 
 <enum name="NearbyConnectionsStartAdvertisingFailureReason">
diff --git a/tools/metrics/histograms/metadata/nearby/histograms.xml b/tools/metrics/histograms/metadata/nearby/histograms.xml
index c2bb379b..9159d22 100644
--- a/tools/metrics/histograms/metadata/nearby/histograms.xml
+++ b/tools/metrics/histograms/metadata/nearby/histograms.xml
@@ -315,6 +315,17 @@
   </summary>
 </histogram>
 
+<histogram name="Nearby.Connections.V3.Medium.ChangedToMedium"
+    enum="NearbyConnectionsMedium" expires_after="2025-05-01">
+  <owner>crisrael@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records the data-transfer medium that the Nearby Connections library
+    successfully switches to for V3 connections. Emitted when the Nearby
+    Connections library notifies of a bandwidth upgrade.
+  </summary>
+</histogram>
+
 <histogram name="Nearby.Connections.WifiLan.ConnectResult"
     enum="NearbyConnectionsWifiLanConnectResult" expires_after="2024-07-10">
   <owner>hansenmichael@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/quota/histograms.xml b/tools/metrics/histograms/metadata/quota/histograms.xml
index c39cba0f..f8fe7ec 100644
--- a/tools/metrics/histograms/metadata/quota/histograms.xml
+++ b/tools/metrics/histograms/metadata/quota/histograms.xml
@@ -347,7 +347,7 @@
 </histogram>
 
 <histogram name="Quota.TimeSpentToAEvictionRound" units="units"
-    expires_after="2024-05-19">
+    expires_after="2025-05-19">
   <owner>ayui@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/readaloud/histograms.xml b/tools/metrics/histograms/metadata/readaloud/histograms.xml
index cf39e99..3a53fdf 100644
--- a/tools/metrics/histograms/metadata/readaloud/histograms.xml
+++ b/tools/metrics/histograms/metadata/readaloud/histograms.xml
@@ -299,31 +299,6 @@
   </summary>
 </histogram>
 
-<histogram name="ReadAloud.VoiceChanged.{VoiceID}" enum="Boolean"
-    expires_after="2024-06-02">
-  <owner>andreaxg@google.com</owner>
-  <owner>basiaz@google.com</owner>
-  <owner>iwells@chromium.org</owner>
-  <summary>
-    Histogram for recording what voice a user changes to. Emitted when the
-    ReadAloud pref for voice is changed by the user. This only ever logs the
-    value &quot;true&quot; because we only care about the total count.
-  </summary>
-</histogram>
-
-<histogram name="ReadAloud.VoicePreviewed.{VoiceID}" enum="Boolean"
-    expires_after="2024-06-02">
-  <owner>andreaxg@google.com</owner>
-  <owner>basiaz@google.com</owner>
-  <owner>iwells@chromium.org</owner>
-  <summary>
-    Histogram for recording what voice a user previews. Emitted when a voice
-    preview playback is created successfully in ReadAloudController. This only
-    ever logs the value &quot;true&quot; because we only care about the total
-    count.
-  </summary>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/ui/accessibility/ax_action_data.h b/ui/accessibility/ax_action_data.h
index 9e7643f..e16b138c 100644
--- a/ui/accessibility/ax_action_data.h
+++ b/ui/accessibility/ax_action_data.h
@@ -44,14 +44,14 @@
   //
   // Note that `target_role` should not be set if `target_node_id` is set, or
   // vice versa.
-  AXNodeID target_node_id = -1;
+  AXNodeID target_node_id = kInvalidAXNodeID;
 
   // Searches for the first node with the given role and performs the action on
   // that node.
   //
   // Note that `target_role` should not be set if `target_node_id` is set, or
   // vice versa.
-  ax::mojom::Role target_role = ax::mojom::Role::kNone;
+  ax::mojom::Role target_role = ax::mojom::Role::kUnknown;
 
   // The request id of this action tracked by the client.
   int request_id = -1;
diff --git a/ui/accessibility/mojom/ax_action_data.mojom b/ui/accessibility/mojom/ax_action_data.mojom
index f88b613..189477af 100644
--- a/ui/accessibility/mojom/ax_action_data.mojom
+++ b/ui/accessibility/mojom/ax_action_data.mojom
@@ -36,5 +36,5 @@
   [MinVersion=1] ax.mojom.AXTreeID? child_tree_id;
   // Note that `target_role` should not be set if `target_node_id` is set, or
   // vice versa.
-  [MinVersion=2] ax.mojom.Role target_role;
+  [MinVersion=2] ax.mojom.Role target_role = ax.mojom.Role.kUnknown;
 };
diff --git a/ui/accessibility/mojom/ax_action_data_mojom_traits.cc b/ui/accessibility/mojom/ax_action_data_mojom_traits.cc
index 9c7e685..291425b 100644
--- a/ui/accessibility/mojom/ax_action_data_mojom_traits.cc
+++ b/ui/accessibility/mojom/ax_action_data_mojom_traits.cc
@@ -4,6 +4,7 @@
 
 #include "ui/accessibility/mojom/ax_action_data_mojom_traits.h"
 
+#include "ui/accessibility/ax_node_id_forward.h"
 #include "ui/accessibility/mojom/ax_tree_id_mojom_traits.h"
 #include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
 
@@ -28,8 +29,10 @@
   if (!data.ReadSourceExtensionId(&out->source_extension_id)) {
     return false;
   }
-  if (data.target_node_id() != -1 &&
-      data.target_role() != ax::mojom::Role::kNone) {
+  if (data.target_node_id() != ui::kInvalidAXNodeID &&
+      data.target_role() != ax::mojom::Role::kUnknown) {
+    // The target could either be found by ID, or by role. Having both set makes
+    // no sense.
     return false;
   }
   out->target_node_id = data.target_node_id();
diff --git a/ui/color/mac/native_color_mixers_mac.mm b/ui/color/mac/native_color_mixers_mac.mm
index ed3e28a..f5f2975 100644
--- a/ui/color/mac/native_color_mixers_mac.mm
+++ b/ui/color/mac/native_color_mixers_mac.mm
@@ -8,7 +8,6 @@
 
 #include "base/containers/fixed_flat_set.h"
 #import "skia/ext/skia_utils_mac.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
@@ -112,15 +111,7 @@
       mixer[kColorSysStateFocusRing] = PickGoogleColor(
           skia::NSSystemColorToSkColor(NSColor.keyboardFocusIndicatorColor),
           kColorSysBase, color_utils::kMinimumVisibleContrastRatio);
-    }
-    if (!features::IsChromeRefresh2023()) {
-      SkColor menu_separator_color =
-          properties.dark ? SkColorSetA(gfx::kGoogleGrey800, 0xCC)
-                          : SkColorSetA(SK_ColorBLACK, 0x26);
-      mixer[kColorMenuSeparator] = {menu_separator_color};
-    }
 
-    if (!features::IsChromeRefresh2023() || !key.user_color.has_value()) {
       const SkColor system_highlight_color =
           skia::NSSystemColorToSkColor(NSColor.selectedTextBackgroundColor);
       mixer[kColorTextSelectionBackground] = {system_highlight_color};
@@ -156,7 +147,7 @@
   // Ensure the system tint is applied by default for pre-refresh browsers. For
   // post-refresh only apply the tint if running old design system themes or the
   // color source is explicitly configured for grayscale.
-  if (features::IsChromeRefresh2023() && !key.custom_theme &&
+  if (!key.custom_theme &&
       key.user_color_source != ColorProviderKey::UserColorSource::kGrayscale) {
     return;
   }
diff --git a/ui/display/manager/BUILD.gn b/ui/display/manager/BUILD.gn
index 1dd366d9..805c7e44 100644
--- a/ui/display/manager/BUILD.gn
+++ b/ui/display/manager/BUILD.gn
@@ -5,13 +5,49 @@
 import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/ui.gni")
 
-assert(is_chromeos_ash || is_castos || is_cast_android)
+assert(is_chromeos_ash)
 
 component("manager") {
   sources = [
+    "apply_content_protection_task.cc",
+    "apply_content_protection_task.h",
+    "configure_displays_task.cc",
+    "configure_displays_task.h",
+    "content_protection_key_manager.cc",
+    "content_protection_key_manager.h",
+    "content_protection_manager.cc",
+    "content_protection_manager.h",
+    "default_touch_transform_setter.cc",
+    "default_touch_transform_setter.h",
+    "display_change_observer.cc",
+    "display_change_observer.h",
+    "display_configurator.cc",
+    "display_configurator.h",
+    "display_layout_manager.h",
+    "display_layout_store.cc",
+    "display_layout_store.h",
+    "display_manager.cc",
+    "display_manager.h",
     "display_manager_export.h",
+    "display_manager_observer.cc",
+    "display_manager_observer.h",
+    "display_port_observer.cc",
+    "display_port_observer.h",
+    "display_properties_parser.cc",
+    "display_properties_parser.h",
+    "json_converter.cc",
+    "json_converter.h",
     "managed_display_info.cc",
     "managed_display_info.h",
+    "query_content_protection_task.cc",
+    "query_content_protection_task.h",
+    "touch_device_manager.cc",
+    "touch_device_manager.h",
+    "touch_transform_controller.cc",
+    "touch_transform_controller.h",
+    "touch_transform_setter.h",
+    "update_display_configuration_task.cc",
+    "update_display_configuration_task.h",
     "util/display_manager_test_util.cc",
     "util/display_manager_test_util.h",
     "util/display_manager_util.cc",
@@ -25,6 +61,10 @@
   deps = [
     "//base",
     "//build:chromeos_buildflags",
+    "//chromeos/ash/components/system",
+    "//chromeos/ui/base",
+    "//components/device_event_log",
+    "//third_party/re2:re2",
     "//ui/base",
     "//ui/display/mojom:mojom",
     "//ui/display/util",
@@ -34,51 +74,4 @@
   ]
 
   defines = [ "DISPLAY_MANAGER_IMPLEMENTATION" ]
-
-  if (is_chromeos_ash) {
-    sources += [
-      "apply_content_protection_task.cc",
-      "apply_content_protection_task.h",
-      "configure_displays_task.cc",
-      "configure_displays_task.h",
-      "content_protection_key_manager.cc",
-      "content_protection_key_manager.h",
-      "content_protection_manager.cc",
-      "content_protection_manager.h",
-      "default_touch_transform_setter.cc",
-      "default_touch_transform_setter.h",
-      "display_change_observer.cc",
-      "display_change_observer.h",
-      "display_configurator.cc",
-      "display_configurator.h",
-      "display_layout_manager.h",
-      "display_layout_store.cc",
-      "display_layout_store.h",
-      "display_manager.cc",
-      "display_manager.h",
-      "display_manager_observer.cc",
-      "display_manager_observer.h",
-      "display_port_observer.cc",
-      "display_port_observer.h",
-      "display_properties_parser.cc",
-      "display_properties_parser.h",
-      "json_converter.cc",
-      "json_converter.h",
-      "query_content_protection_task.cc",
-      "query_content_protection_task.h",
-      "touch_device_manager.cc",
-      "touch_device_manager.h",
-      "touch_transform_controller.cc",
-      "touch_transform_controller.h",
-      "touch_transform_setter.h",
-      "update_display_configuration_task.cc",
-      "update_display_configuration_task.h",
-    ]
-    deps += [
-      "//chromeos/ash/components/system",
-      "//chromeos/ui/base",
-      "//components/device_event_log",
-      "//third_party/re2:re2",
-    ]
-  }
 }
diff --git a/ui/gfx/font_render_params_mac.cc b/ui/gfx/font_render_params_mac.cc
index 86111a9..4e2f7b9 100644
--- a/ui/gfx/font_render_params_mac.cc
+++ b/ui/gfx/font_render_params_mac.cc
@@ -20,8 +20,7 @@
   params.use_bitmaps = true;
   params.subpixel_positioning = true;
 
-  if (features::IsChromeRefresh2023() &&
-      !base::FeatureList::IsEnabled(features::kCr2023MacFontSmoothing)) {
+  if (!base::FeatureList::IsEnabled(features::kCr2023MacFontSmoothing)) {
     params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
     params.hinting = FontRenderParams::HINTING_NONE;
   } else {
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index a619e2c6..b14490a 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -7725,8 +7725,7 @@
 #if !BUILDFLAG(IS_MAC)
   EXPECT_EQ(GetRendererFont().getEdging(), SkFont::Edging::kSubpixelAntiAlias);
 #else
-  if (features::IsChromeRefresh2023() &&
-      !base::FeatureList::IsEnabled(features::kCr2023MacFontSmoothing)) {
+  if (!base::FeatureList::IsEnabled(features::kCr2023MacFontSmoothing)) {
     EXPECT_EQ(GetRendererFont().getEdging(), SkFont::Edging::kAntiAlias);
   } else {
     EXPECT_EQ(GetRendererFont().getEdging(),
diff --git a/ui/gl/egl_bindings_autogen_mock.cc b/ui/gl/egl_bindings_autogen_mock.cc
index 73a2ed1..aff9ff9 100644
--- a/ui/gl/egl_bindings_autogen_mock.cc
+++ b/ui/gl/egl_bindings_autogen_mock.cc
@@ -672,12 +672,6 @@
   interface_->SetBlobCacheFuncsANDROID(dpy, set, get);
 }
 
-void GL_BINDING_CALL MockEGLInterface::Mock_eglSetValidationEnabledANGLE(
-    EGLBoolean validationState) {
-  MakeEglMockFunctionUnique("eglSetValidationEnabledANGLE");
-  interface_->SetValidationEnabledANGLE(validationState);
-}
-
 EGLBoolean GL_BINDING_CALL
 MockEGLInterface::Mock_eglStreamAttribKHR(EGLDisplay dpy,
                                           EGLStreamKHR stream,
@@ -998,10 +992,6 @@
   if (strcmp(name, "eglSetBlobCacheFuncsANDROID") == 0)
     return reinterpret_cast<GLFunctionPointerType>(
         Mock_eglSetBlobCacheFuncsANDROID);
-  if (strcmp(name, "eglSetValidationEnabledANGLE") == 0) {
-    return reinterpret_cast<GLFunctionPointerType>(
-        Mock_eglSetValidationEnabledANGLE);
-  }
   if (strcmp(name, "eglStreamAttribKHR") == 0)
     return reinterpret_cast<GLFunctionPointerType>(Mock_eglStreamAttribKHR);
   if (strcmp(name, "eglStreamConsumerAcquireKHR") == 0)
diff --git a/ui/gl/egl_bindings_autogen_mock.h b/ui/gl/egl_bindings_autogen_mock.h
index 576785b7..c831999b 100644
--- a/ui/gl/egl_bindings_autogen_mock.h
+++ b/ui/gl/egl_bindings_autogen_mock.h
@@ -291,8 +291,6 @@
 Mock_eglSetBlobCacheFuncsANDROID(EGLDisplay dpy,
                                  EGLSetBlobFuncANDROID set,
                                  EGLGetBlobFuncANDROID get);
-static void GL_BINDING_CALL
-Mock_eglSetValidationEnabledANGLE(EGLBoolean validationState);
 static EGLBoolean GL_BINDING_CALL Mock_eglStreamAttribKHR(EGLDisplay dpy,
                                                           EGLStreamKHR stream,
                                                           EGLenum attribute,
diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py
index ab2dcd0..e6f0bc94 100755
--- a/ui/gl/generate_bindings.py
+++ b/ui/gl/generate_bindings.py
@@ -2844,11 +2844,6 @@
                  'extensions': ['EGL_ANDROID_blob_cache'] }],
   'arguments':
       'EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get' },
-{ 'return_type': 'void',
-  'versions': [{ 'name': 'eglSetValidationEnabledANGLE',
-                 'extensions': ['EGL_ANGLE_no_error'] }],
-  'arguments':
-      'EGLBoolean validationState' },
 { 'return_type': 'EGLBoolean',
   'versions': [{ 'name': 'eglStreamAttribKHR',
                  'extensions': ['EGL_KHR_stream'] }],
@@ -2923,7 +2918,6 @@
 # EGL client extensions that may not add a function but are still queried.
 EGL_CLIENT_EXTENSIONS_EXTRA = [
   'EGL_ANGLE_display_power_preference',
-  'EGL_ANGLE_no_error',
   'EGL_ANGLE_platform_angle',
   'EGL_ANGLE_platform_angle_d3d',
   'EGL_ANGLE_platform_angle_device_id',
diff --git a/ui/gl/gl_bindings_api_autogen_egl.h b/ui/gl/gl_bindings_api_autogen_egl.h
index c1c59cf..ef0ec5e 100644
--- a/ui/gl/gl_bindings_api_autogen_egl.h
+++ b/ui/gl/gl_bindings_api_autogen_egl.h
@@ -250,7 +250,6 @@
 void eglSetBlobCacheFuncsANDROIDFn(EGLDisplay dpy,
                                    EGLSetBlobFuncANDROID set,
                                    EGLGetBlobFuncANDROID get) override;
-void eglSetValidationEnabledANGLEFn(EGLBoolean validationState) override;
 EGLBoolean eglStreamAttribKHRFn(EGLDisplay dpy,
                                 EGLStreamKHR stream,
                                 EGLenum attribute,
diff --git a/ui/gl/gl_bindings_autogen_egl.cc b/ui/gl/gl_bindings_autogen_egl.cc
index cc0770f7..ce1a889 100644
--- a/ui/gl/gl_bindings_autogen_egl.cc
+++ b/ui/gl/gl_bindings_autogen_egl.cc
@@ -215,9 +215,6 @@
   fn.eglSetBlobCacheFuncsANDROIDFn =
       reinterpret_cast<eglSetBlobCacheFuncsANDROIDProc>(
           GetGLProcAddress("eglSetBlobCacheFuncsANDROID"));
-  fn.eglSetValidationEnabledANGLEFn =
-      reinterpret_cast<eglSetValidationEnabledANGLEProc>(
-          GetGLProcAddress("eglSetValidationEnabledANGLE"));
   fn.eglStreamAttribKHRFn = reinterpret_cast<eglStreamAttribKHRProc>(
       GetGLProcAddress("eglStreamAttribKHR"));
   fn.eglStreamConsumerAcquireKHRFn =
@@ -270,7 +267,6 @@
       gfx::HasExtension(extensions, "EGL_ANGLE_display_power_preference");
   b_EGL_ANGLE_feature_control =
       gfx::HasExtension(extensions, "EGL_ANGLE_feature_control");
-  b_EGL_ANGLE_no_error = gfx::HasExtension(extensions, "EGL_ANGLE_no_error");
   b_EGL_ANGLE_platform_angle =
       gfx::HasExtension(extensions, "EGL_ANGLE_platform_angle");
   b_EGL_ANGLE_platform_angle_d3d =
@@ -342,7 +338,6 @@
       gfx::HasExtension(extensions, "EGL_ANGLE_keyed_mutex");
   b_EGL_ANGLE_metal_shared_event_sync =
       gfx::HasExtension(extensions, "EGL_ANGLE_metal_shared_event_sync");
-  b_EGL_ANGLE_no_error = gfx::HasExtension(extensions, "EGL_ANGLE_no_error");
   b_EGL_ANGLE_power_preference =
       gfx::HasExtension(extensions, "EGL_ANGLE_power_preference");
   b_EGL_ANGLE_query_surface_pointer =
@@ -927,10 +922,6 @@
   driver_->fn.eglSetBlobCacheFuncsANDROIDFn(dpy, set, get);
 }
 
-void EGLApiBase::eglSetValidationEnabledANGLEFn(EGLBoolean validationState) {
-  driver_->fn.eglSetValidationEnabledANGLEFn(validationState);
-}
-
 EGLBoolean EGLApiBase::eglStreamAttribKHRFn(EGLDisplay dpy,
                                             EGLStreamKHR stream,
                                             EGLenum attribute,
@@ -1632,12 +1623,6 @@
   egl_api_->eglSetBlobCacheFuncsANDROIDFn(dpy, set, get);
 }
 
-void TraceEGLApi::eglSetValidationEnabledANGLEFn(EGLBoolean validationState) {
-  TRACE_EVENT_BINARY_EFFICIENT0("gpu",
-                                "TraceEGLAPI::eglSetValidationEnabledANGLE");
-  egl_api_->eglSetValidationEnabledANGLEFn(validationState);
-}
-
 EGLBoolean TraceEGLApi::eglStreamAttribKHRFn(EGLDisplay dpy,
                                              EGLStreamKHR stream,
                                              EGLenum attribute,
@@ -2634,12 +2619,6 @@
   egl_api_->eglSetBlobCacheFuncsANDROIDFn(dpy, set, get);
 }
 
-void LogEGLApi::eglSetValidationEnabledANGLEFn(EGLBoolean validationState) {
-  GL_SERVICE_LOG("eglSetValidationEnabledANGLE" << "(" << validationState
-                                                << ")");
-  egl_api_->eglSetValidationEnabledANGLEFn(validationState);
-}
-
 EGLBoolean LogEGLApi::eglStreamAttribKHRFn(EGLDisplay dpy,
                                            EGLStreamKHR stream,
                                            EGLenum attribute,
diff --git a/ui/gl/gl_bindings_autogen_egl.h b/ui/gl/gl_bindings_autogen_egl.h
index 60ee3e2..c84a446 100644
--- a/ui/gl/gl_bindings_autogen_egl.h
+++ b/ui/gl/gl_bindings_autogen_egl.h
@@ -308,8 +308,6 @@
     EGLDisplay dpy,
     EGLSetBlobFuncANDROID set,
     EGLGetBlobFuncANDROID get);
-typedef void(GL_BINDING_CALL* eglSetValidationEnabledANGLEProc)(
-    EGLBoolean validationState);
 typedef EGLBoolean(GL_BINDING_CALL* eglStreamAttribKHRProc)(EGLDisplay dpy,
                                                             EGLStreamKHR stream,
                                                             EGLenum attribute,
@@ -362,7 +360,6 @@
 struct GL_EXPORT ClientExtensionsEGL {
   bool b_EGL_ANGLE_display_power_preference;
   bool b_EGL_ANGLE_feature_control;
-  bool b_EGL_ANGLE_no_error;
   bool b_EGL_ANGLE_platform_angle;
   bool b_EGL_ANGLE_platform_angle_d3d;
   bool b_EGL_ANGLE_platform_angle_device_id;
@@ -404,7 +401,6 @@
   bool b_EGL_ANGLE_iosurface_client_buffer;
   bool b_EGL_ANGLE_keyed_mutex;
   bool b_EGL_ANGLE_metal_shared_event_sync;
-  bool b_EGL_ANGLE_no_error;
   bool b_EGL_ANGLE_power_preference;
   bool b_EGL_ANGLE_query_surface_pointer;
   bool b_EGL_ANGLE_robust_resource_initialization;
@@ -536,7 +532,6 @@
   eglReleaseTexImageProc eglReleaseTexImageFn;
   eglReleaseThreadProc eglReleaseThreadFn;
   eglSetBlobCacheFuncsANDROIDProc eglSetBlobCacheFuncsANDROIDFn;
-  eglSetValidationEnabledANGLEProc eglSetValidationEnabledANGLEFn;
   eglStreamAttribKHRProc eglStreamAttribKHRFn;
   eglStreamConsumerAcquireKHRProc eglStreamConsumerAcquireKHRFn;
   eglStreamConsumerGLTextureExternalAttribsNVProc
@@ -816,7 +811,6 @@
   virtual void eglSetBlobCacheFuncsANDROIDFn(EGLDisplay dpy,
                                              EGLSetBlobFuncANDROID set,
                                              EGLGetBlobFuncANDROID get) = 0;
-  virtual void eglSetValidationEnabledANGLEFn(EGLBoolean validationState) = 0;
   virtual EGLBoolean eglStreamAttribKHRFn(EGLDisplay dpy,
                                           EGLStreamKHR stream,
                                           EGLenum attribute,
@@ -975,8 +969,6 @@
 #define eglReleaseThread ::gl::g_current_egl_context->eglReleaseThreadFn
 #define eglSetBlobCacheFuncsANDROID \
   ::gl::g_current_egl_context->eglSetBlobCacheFuncsANDROIDFn
-#define eglSetValidationEnabledANGLE \
-  ::gl::g_current_egl_context->eglSetValidationEnabledANGLEFn
 #define eglStreamAttribKHR ::gl::g_current_egl_context->eglStreamAttribKHRFn
 #define eglStreamConsumerAcquireKHR \
   ::gl::g_current_egl_context->eglStreamConsumerAcquireKHRFn
diff --git a/ui/gl/gl_display.cc b/ui/gl/gl_display.cc
index 78f5f1e..8bb85fd 100644
--- a/ui/gl/gl_display.cc
+++ b/ui/gl/gl_display.cc
@@ -724,11 +724,6 @@
     SetEglDebugMessageControl();
   }
 
-  if (g_driver_egl.client_ext.b_EGL_ANGLE_no_error &&
-      !features::IsANGLEValidationEnabled()) {
-    eglSetValidationEnabledANGLE(EGL_FALSE);
-  }
-
   std::vector<std::string> enabled_angle_features;
   std::vector<std::string> disabled_angle_features;
   features::GetANGLEFeaturesFromCommandLineAndFinch(
diff --git a/ui/gl/gl_features.cc b/ui/gl/gl_features.cc
index 65c085670..e682928 100644
--- a/ui/gl/gl_features.cc
+++ b/ui/gl/gl_features.cc
@@ -188,22 +188,6 @@
 #endif  // defined(PASSTHROUGH_COMMAND_DECODER_LAUNCHED)
 }
 
-#if DCHECK_IS_ON()
-bool IsANGLEValidationEnabled() {
-  return true;
-}
-#else
-// Enables the use of ANGLE validation for EGL and GL (non-WebGL) contexts.
-BASE_FEATURE(kDefaultEnableANGLEValidation,
-             "DefaultEnableANGLEValidation",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsANGLEValidationEnabled() {
-  return base::FeatureList::IsEnabled(kDefaultEnableANGLEValidation) &&
-         UsePassthroughCommandDecoder();
-}
-#endif
-
 void GetANGLEFeaturesFromCommandLineAndFinch(
     const base::CommandLine* command_line,
     std::vector<std::string>& enabled_angle_features,
diff --git a/ui/gl/gl_features.h b/ui/gl/gl_features.h
index 35e4884..31b3aaa8 100644
--- a/ui/gl/gl_features.h
+++ b/ui/gl/gl_features.h
@@ -36,7 +36,6 @@
 GL_EXPORT bool IsAndroidFrameDeadlineEnabled();
 
 GL_EXPORT bool UsePassthroughCommandDecoder();
-GL_EXPORT bool IsANGLEValidationEnabled();
 
 GL_EXPORT void GetANGLEFeaturesFromCommandLineAndFinch(
     const base::CommandLine* command_line,
diff --git a/ui/gl/gl_mock_autogen_egl.h b/ui/gl/gl_mock_autogen_egl.h
index 82cc829..7ff6959 100644
--- a/ui/gl/gl_mock_autogen_egl.h
+++ b/ui/gl/gl_mock_autogen_egl.h
@@ -277,7 +277,6 @@
              void(EGLDisplay dpy,
                   EGLSetBlobFuncANDROID set,
                   EGLGetBlobFuncANDROID get));
-MOCK_METHOD1(SetValidationEnabledANGLE, void(EGLBoolean validationState));
 MOCK_METHOD4(StreamAttribKHR,
              EGLBoolean(EGLDisplay dpy,
                         EGLStreamKHR stream,
diff --git a/ui/gtk/window_frame_provider_gtk.cc b/ui/gtk/window_frame_provider_gtk.cc
index 0b8fa0ec..e24cce8 100644
--- a/ui/gtk/window_frame_provider_gtk.cc
+++ b/ui/gtk/window_frame_provider_gtk.cc
@@ -7,7 +7,6 @@
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 #include "third_party/skia/include/core/SkRRect.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
@@ -69,9 +68,7 @@
   if (!focused) {
     gtk_style_context_set_state(context, GTK_STATE_FLAG_BACKDROP);
   }
-  if (features::IsChromeRefresh2023()) {
-    ApplyCssToContext(context, "* { border-bottom-style: none; }");
-  }
+  ApplyCssToContext(context, "* { border-bottom-style: none; }");
   return context;
 }
 
diff --git a/ui/message_center/views/notification_view_unittest.cc b/ui/message_center/views/notification_view_unittest.cc
index d9d27baa..f463c3ec4 100644
--- a/ui/message_center/views/notification_view_unittest.cc
+++ b/ui/message_center/views/notification_view_unittest.cc
@@ -463,11 +463,8 @@
   EXPECT_TRUE(delegate_->disable_notification_called());
 }
 
-// TODO (crbug/1521442): Test fails under ChromeRefresh2023. Fix and re-enable.
-TEST_F(NotificationViewTest, TestAccentColor) {
-  if (features::IsChromeRefresh2023()) {
-    GTEST_SKIP();
-  }
+// TODO (crbug/1521442): Test fails post-ChromeRefresh2023. Fix and re-enable.
+TEST_F(NotificationViewTest, DISABLED_TestAccentColor) {
   std::unique_ptr<Notification> notification = CreateSimpleNotification();
   notification->set_buttons(CreateButtons(2));
 
diff --git a/ui/views/controls/button/checkbox.cc b/ui/views/controls/button/checkbox.cc
index e87ddca5..6f6cf2e4 100644
--- a/ui/views/controls/button/checkbox.cc
+++ b/ui/views/controls/button/checkbox.cc
@@ -107,46 +107,43 @@
   // the checkbox view (otherwise it gets clipped which looks weird).
   views::InstallEmptyHighlightPathGenerator(this);
 
-  if (features::IsChromeRefresh2023()) {
-    InkDrop::Install(image_container_view(),
-                     std::make_unique<InkDropHost>(image_container_view()));
-    SetInkDropView(image_container_view());
-    InkDrop::Get(image_container_view())->SetMode(InkDropHost::InkDropMode::ON);
+  InkDrop::Install(image_container_view(),
+                   std::make_unique<InkDropHost>(image_container_view()));
+  SetInkDropView(image_container_view());
+  InkDrop::Get(image_container_view())->SetMode(InkDropHost::InkDropMode::ON);
 
-    // Allow ImageView to capture mouse events in order for InkDrop effects to
-    // trigger.
-    image_container_view()->SetCanProcessEventsWithinSubtree(true);
+  // Allow ImageView to capture mouse events in order for InkDrop effects to
+  // trigger.
+  image_container_view()->SetCanProcessEventsWithinSubtree(true);
 
-    // Avoid the default ink-drop mask to allow the InkDrop effect to extend
-    // beyond the image view (otherwise it gets clipped which looks weird).
-    views::InstallEmptyHighlightPathGenerator(image_container_view());
+  // Avoid the default ink-drop mask to allow the InkDrop effect to extend
+  // beyond the image view (otherwise it gets clipped which looks weird).
+  views::InstallEmptyHighlightPathGenerator(image_container_view());
 
-    InkDrop::Get(image_container_view())
-        ->SetCreateHighlightCallback(base::BindRepeating(
-            [](View* host) {
-              int radius =
-                  InkDropHost::GetLargeSize(kCheckboxInkDropSize).width() / 2;
-              return std::make_unique<views::InkDropHighlight>(
-                  gfx::PointF(host->GetContentsBounds().CenterPoint()),
-                  std::make_unique<CircleLayerDelegate>(
-                      views::InkDrop::Get(host)->GetBaseColor(), radius));
-            },
-            image_container_view()));
+  InkDrop::Get(image_container_view())
+      ->SetCreateHighlightCallback(base::BindRepeating(
+          [](View* host) {
+            int radius =
+                InkDropHost::GetLargeSize(kCheckboxInkDropSize).width() / 2;
+            return std::make_unique<views::InkDropHighlight>(
+                gfx::PointF(host->GetContentsBounds().CenterPoint()),
+                std::make_unique<CircleLayerDelegate>(
+                    views::InkDrop::Get(host)->GetBaseColor(), radius));
+          },
+          image_container_view()));
 
-    InkDrop::Get(image_container_view())
-        ->SetCreateRippleCallback(base::BindRepeating(
-            [](View* host) {
-              return InkDrop::Get(host)->CreateSquareRipple(
-                  host->GetContentsBounds().CenterPoint(),
-                  kCheckboxInkDropSize);
-            },
-            image_container_view()));
+  InkDrop::Get(image_container_view())
+      ->SetCreateRippleCallback(base::BindRepeating(
+          [](View* host) {
+            return InkDrop::Get(host)->CreateSquareRipple(
+                host->GetContentsBounds().CenterPoint(), kCheckboxInkDropSize);
+          },
+          image_container_view()));
 
-    // Usually ink-drop ripples match the text color. Checkboxes use the
-    // color of the unchecked, enabled icon.
-    InkDrop::Get(image_container_view())
-        ->SetBaseColorId(ui::kColorCheckboxForegroundUnchecked);
-  }
+  // Usually ink-drop ripples match the text color. Checkboxes use the
+  // color of the unchecked, enabled icon.
+  InkDrop::Get(image_container_view())
+      ->SetBaseColorId(ui::kColorCheckboxForegroundUnchecked);
 }
 
 Checkbox::~Checkbox() = default;
@@ -204,22 +201,17 @@
 gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
   const int icon_state = GetIconState(for_state);
 
-  if (features::IsChromeRefresh2023()) {
-    const SkColor container_color = GetIconImageColor(icon_state);
-    if (GetChecked()) {
-      const gfx::ImageSkia check_icon = gfx::CreateVectorIcon(
-          GetVectorIcon(), kCheckboxIconDipSize, GetIconCheckColor(icon_state));
+  const SkColor container_color = GetIconImageColor(icon_state);
+  if (GetChecked()) {
+    const gfx::ImageSkia check_icon = gfx::CreateVectorIcon(
+        GetVectorIcon(), kCheckboxIconDipSize, GetIconCheckColor(icon_state));
 
-      return gfx::ImageSkiaOperations::CreateImageWithRoundRectBackground(
-          gfx::SizeF(kCheckboxIconDipSize, kCheckboxIconDipSize),
-          kCheckboxIconCornerRadius, container_color, check_icon);
-    }
-    return gfx::CreateVectorIcon(GetVectorIcon(), kCheckboxIconDipSize,
-                                 container_color);
+    return gfx::ImageSkiaOperations::CreateImageWithRoundRectBackground(
+        gfx::SizeF(kCheckboxIconDipSize, kCheckboxIconDipSize),
+        kCheckboxIconCornerRadius, container_color, check_icon);
   }
-
   return gfx::CreateVectorIcon(GetVectorIcon(), kCheckboxIconDipSize,
-                               GetIconImageColor(icon_state));
+                               container_color);
 }
 
 std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const {
@@ -241,43 +233,20 @@
 SkPath Checkbox::GetFocusRingPath() const {
   SkPath path;
   gfx::Rect bounds = image_container_view()->GetMirroredContentsBounds();
-  // Don't add extra insets in the ChromeRefresh case so that the focus ring can
-  // be drawn in the ChromeRefresh style.
-  if (!features::IsChromeRefresh2023()) {
-    bounds.Inset(1);
-  }
   path.addRect(RectToSkRect(bounds));
   return path;
 }
 
 SkColor Checkbox::GetIconImageColor(int icon_state) const {
-  if (features::IsChromeRefresh2023()) {
-    if (icon_state & IconState::CHECKED) {
-      return GetColorProvider()->GetColor(
-          (icon_state & IconState::ENABLED)
-              ? ui::kColorCheckboxContainer
-              : ui::kColorCheckboxContainerDisabled);
-    }
+  if (icon_state & IconState::CHECKED) {
     return GetColorProvider()->GetColor(
-        (icon_state & IconState::ENABLED) ? ui::kColorCheckboxOutline
+        (icon_state & IconState::ENABLED)
+            ? ui::kColorCheckboxContainer
+            : ui::kColorCheckboxContainerDisabled);
+  }
+  return GetColorProvider()->GetColor((icon_state & IconState::ENABLED)
+                                          ? ui::kColorCheckboxOutline
                                           : ui::kColorCheckboxOutlineDisabled);
-  }
-
-  SkColor active_color =
-      GetColorProvider()->GetColor((icon_state & IconState::CHECKED)
-                                       ? ui::kColorCheckboxForegroundChecked
-                                       : ui::kColorCheckboxForegroundUnchecked);
-
-  // Use the overridden checked icon image color instead if set.
-  if (icon_state & IconState::CHECKED &&
-      checked_icon_image_color_.has_value()) {
-    active_color = checked_icon_image_color_.value();
-  }
-
-  return (icon_state & IconState::ENABLED)
-             ? active_color
-             : color_utils::BlendTowardMaxContrast(active_color,
-                                                   gfx::kDisabledControlAlpha);
 }
 
 SkColor Checkbox::GetIconCheckColor(int icon_state) const {
@@ -294,11 +263,7 @@
 }
 
 const gfx::VectorIcon& Checkbox::GetVectorIcon() const {
-  if (features::IsChromeRefresh2023()) {
-    return GetChecked() ? kCheckboxCheckCr2023Icon : kCheckboxNormalCr2023Icon;
-  }
-
-  return GetChecked() ? kCheckboxActiveIcon : kCheckboxNormalIcon;
+  return GetChecked() ? kCheckboxCheckCr2023Icon : kCheckboxNormalCr2023Icon;
 }
 
 int Checkbox::GetIconState(ButtonState for_state) const {
diff --git a/ui/views/controls/button/md_text_button.cc b/ui/views/controls/button/md_text_button.cc
index d886c50..5734c51 100644
--- a/ui/views/controls/button/md_text_button.cc
+++ b/ui/views/controls/button/md_text_button.cc
@@ -58,16 +58,11 @@
       [](MdTextButton* host) { return host->GetHoverColor(host->GetStyle()); },
       this));
 
-  if (features::IsChromeRefresh2023()) {
-    constexpr int kImageSpacing = 8;
-    SetImageLabelSpacing(kImageSpacing);
-    // Highlight button colors already have opacity applied.
-    // Set the opacity to 1 so the two values do not compound.
-    InkDrop::Get(this)->SetHighlightOpacity(1);
-  } else {
-    SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(
-        ShapeContextTokens::kButtonRadius));
-  }
+  constexpr int kImageSpacing = 8;
+  SetImageLabelSpacing(kImageSpacing);
+  // Highlight button colors already have opacity applied.
+  // Set the opacity to 1 so the two values do not compound.
+  InkDrop::Get(this)->SetHighlightOpacity(1);
 
   SetHorizontalAlignment(gfx::ALIGN_CENTER);
 
@@ -240,7 +235,7 @@
   int right_padding = LayoutProvider::Get()->GetDistanceMetric(
       DISTANCE_BUTTON_HORIZONTAL_PADDING);
   int left_padding = right_padding;
-  if (HasImage(GetVisualState()) && features::IsChromeRefresh2023()) {
+  if (HasImage(GetVisualState())) {
     constexpr int kLeftPadding = 12;
     left_padding = kLeftPadding;
   }
@@ -316,13 +311,11 @@
   SetBackground(
       CreateBackgroundFromPainter(Painter::CreateRoundRectWith1PxBorderPainter(
           bg_color, stroke_color, GetCornerRadiusValue(), SkBlendMode::kSrcOver,
-          true /* antialias */,
-          features::IsChromeRefresh2023() /* should_border_scale */)));
+          true /* antialias */, true /* should_border_scale */)));
 }
 
 void MdTextButton::UpdateIconColor() {
-  if (features::IsChromeRefresh2023() && use_text_color_for_icon_ &&
-      HasImage(ButtonState::STATE_NORMAL)) {
+  if (use_text_color_for_icon_ && HasImage(ButtonState::STATE_NORMAL)) {
     const std::optional<ui::ImageModel>& image_model =
         GetImageModel(ButtonState::STATE_NORMAL);
     if (image_model.has_value() && image_model->IsVectorIcon()) {
@@ -346,10 +339,6 @@
 }
 
 SkColor MdTextButton::GetHoverColor(ui::ButtonStyle button_style) {
-  if (!features::IsChromeRefresh2023()) {
-    return color_utils::DeriveDefaultIconColor(label()->GetEnabledColor());
-  }
-
   switch (button_style) {
     case ui::ButtonStyle::kProminent:
       return GetColorProvider()->GetColor(ui::kColorSysStateHoverOnProminent);
diff --git a/ui/views/controls/button/radio_button.cc b/ui/views/controls/button/radio_button.cc
index cc279d1a..2cba2f9 100644
--- a/ui/views/controls/button/radio_button.cc
+++ b/ui/views/controls/button/radio_button.cc
@@ -29,7 +29,6 @@
 
 namespace {
 constexpr int kFocusRingRadius = 16;
-constexpr int kRadioButtonIconDipSize = 16;
 constexpr int kRadioButtonIconDipSizeCr2023 = 20;
 }  // namespace
 
@@ -133,10 +132,7 @@
 }
 
 gfx::ImageSkia RadioButton::GetImage(ButtonState for_state) const {
-  return gfx::CreateVectorIcon(GetVectorIcon(),
-                               features::IsChromeRefresh2023()
-                                   ? kRadioButtonIconDipSizeCr2023
-                                   : kRadioButtonIconDipSize,
+  return gfx::CreateVectorIcon(GetVectorIcon(), kRadioButtonIconDipSizeCr2023,
                                GetIconImageColor(GetIconState(for_state)));
 }
 
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 15cc189..2ba08d5 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -80,10 +80,8 @@
     button_controller()->set_notify_action(
         ButtonController::NotifyAction::kOnPress);
 
-    if (features::IsChromeRefresh2023()) {
-      views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
-                                                    GetCornerRadius());
-    }
+    views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                  GetCornerRadius());
     ConfigureComboboxButtonInkDrop(this);
   }
   TransparentButton(const TransparentButton&) = delete;
@@ -141,9 +139,7 @@
   SetFocusBehavior(FocusBehavior::ALWAYS);
 #endif
 
-  SetBackgroundColorId(features::IsChromeRefresh2023()
-                           ? ui::kColorComboboxBackground
-                           : ui::kColorTextfieldBackground);
+  SetBackgroundColorId(ui::kColorComboboxBackground);
 
   UpdateBorder();
 
@@ -154,31 +150,25 @@
       AddChildView(std::make_unique<TransparentButton>(base::BindRepeating(
           &Combobox::ArrowButtonPressed, base::Unretained(this))));
 
-  if (features::IsChromeRefresh2023()) {
-    // TODO(crbug.com/40250124): This setter should be removed and the behavior
-    // made default when ChromeRefresh2023 is finalized.
-    SetEventHighlighting(true);
-    enabled_changed_subscription_ =
-        AddEnabledChangedCallback(base::BindRepeating(
-            [](Combobox* combobox) {
-              combobox->SetBackgroundColorId(
-                  combobox->GetEnabled()
-                      ? ui::kColorComboboxBackground
-                      : ui::kColorComboboxBackgroundDisabled);
-              combobox->UpdateBorder();
-            },
-            base::Unretained(this)));
-  }
+  // TODO(crbug.com/40250124): This setter should be removed and the behavior
+  // made default when ChromeRefresh2023 is finalized.
+  SetEventHighlighting(true);
+  enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
+      [](Combobox* combobox) {
+        combobox->SetBackgroundColorId(
+            combobox->GetEnabled() ? ui::kColorComboboxBackground
+                                   : ui::kColorComboboxBackgroundDisabled);
+        combobox->UpdateBorder();
+      },
+      base::Unretained(this)));
 
   // A layer is applied to make sure that canvas bounds are snapped to pixel
   // boundaries (for the sake of drawing the arrow).
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
 
-  if (features::IsChromeRefresh2023()) {
-    views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
-                                                  GetCornerRadius());
-  }
+  views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                GetCornerRadius());
 
   SetAccessibilityProperties(ax::mojom::Role::kComboBoxSelect);
 }
@@ -568,23 +558,15 @@
 }
 
 void Combobox::UpdateBorder() {
-  if (features::IsChromeRefresh2023()) {
-    if (!GetEnabled()) {
-      SetBorder(nullptr);
-      return;
-    }
-    SetBorder(CreateThemedRoundedRectBorder(
-        kBorderThickness, GetCornerRadius(),
-        invalid_
-            ? ui::kColorAlertHighSeverity
-            : border_color_id_.value_or(ui::kColorComboboxContainerOutline)));
-  } else {
-    auto border = std::make_unique<FocusableBorder>();
-    border->SetColorId(invalid_ ? ui::kColorAlertHighSeverity
-                                : border_color_id_.value_or(
-                                      ui::kColorFocusableBorderUnfocused));
-    SetBorder(std::move(border));
+  if (!GetEnabled()) {
+    SetBorder(nullptr);
+    return;
   }
+  SetBorder(CreateThemedRoundedRectBorder(
+      kBorderThickness, GetCornerRadius(),
+      invalid_
+          ? ui::kColorAlertHighSeverity
+          : border_color_id_.value_or(ui::kColorComboboxContainerOutline)));
 }
 
 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
diff --git a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
index 7e4d033..92f99ff 100644
--- a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
@@ -371,11 +371,6 @@
   EXPECT_TRUE(IsTextfieldFocused());
   SendKeyEvent(ui::VKEY_TAB);
   EXPECT_FALSE(IsTextfieldFocused());
-  // In Chrome Refresh the drop down arrow will behave more like a normal button
-  // and therefore will be focusable.
-  if (!features::IsChromeRefresh2023()) {
-    EXPECT_TRUE(dummy_focusable_view_->HasFocus());
-  }
   WaitForMenuClosureAnimation();
   EXPECT_FALSE(IsMenuOpen());
 }
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index c2db104..8799d7a 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -374,7 +374,7 @@
   // move the focus ring away from the host. If those places want to outset the
   // focus ring in the chrome refresh style, they need to be audited separately
   // with UX.
-  return features::IsChromeRefresh2023() && !outset_focus_ring_disabled_ &&
+  return !outset_focus_ring_disabled_ &&
          halo_inset_ == FocusRing::kDefaultHaloInset;
 }
 
diff --git a/ui/views/controls/focusable_border.cc b/ui/views/controls/focusable_border.cc
index 6479325e..bd98936 100644
--- a/ui/views/controls/focusable_border.cc
+++ b/ui/views/controls/focusable_border.cc
@@ -26,10 +26,8 @@
 
 namespace views {
 
-FocusableBorder::FocusableBorder(bool should_scale)
-    : insets_(kInsetSize),
-      corner_radius_(FocusRing::kDefaultCornerRadiusDp),
-      should_scale_(should_scale) {}
+FocusableBorder::FocusableBorder()
+    : insets_(kInsetSize), corner_radius_(FocusRing::kDefaultCornerRadiusDp) {}
 
 FocusableBorder::~FocusableBorder() = default;
 
@@ -45,7 +43,7 @@
   gfx::ScopedCanvas scoped(canvas);
   const float dsf = canvas->UndoDeviceScaleFactor();
 
-  const float kStrokeWidth = should_scale_ ? dsf : 1.0f;
+  const float kStrokeWidth = dsf;
   flags.setStrokeWidth(kStrokeWidth);
 
   // Scale the rect and snap to pixel boundaries.
diff --git a/ui/views/controls/focusable_border.h b/ui/views/controls/focusable_border.h
index 0f5958f1..e43df7c 100644
--- a/ui/views/controls/focusable_border.h
+++ b/ui/views/controls/focusable_border.h
@@ -21,7 +21,7 @@
 // A Border class to draw a focused border around a field (e.g textfield).
 class VIEWS_EXPORT FocusableBorder : public Border {
  public:
-  explicit FocusableBorder(bool should_scale = false);
+  FocusableBorder();
 
   FocusableBorder(const FocusableBorder&) = delete;
   FocusableBorder& operator=(const FocusableBorder&) = delete;
@@ -50,7 +50,6 @@
   gfx::Insets insets_;
   float corner_radius_;
   std::optional<ui::ColorId> override_color_id_;
-  bool should_scale_;
 };
 
 }  // namespace views
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 7bc2882..75eb167 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -1443,10 +1443,7 @@
   const Colors colors = CalculateColors(paint_as_selected);
   if (submenu_arrow_image_view_) {
     submenu_arrow_image_view_->SetImage(ui::ImageModel::FromVectorIcon(
-        features::IsChromeRefresh2023()
-            ? vector_icons::kSubmenuArrowChromeRefreshIcon
-            : vector_icons::kSubmenuArrowIcon,
-        colors.icon_color));
+        vector_icons::kSubmenuArrowChromeRefreshIcon, colors.icon_color));
   }
   MenuDelegate* delegate = GetDelegate();
   if (type_ == Type::kCheckbox && delegate &&
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index c8a4f58..af34317 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -231,20 +231,18 @@
                                                 GetCornerRadius());
   FocusRing::Install(this);
   FocusRing::Get(this)->SetOutsetFocusRingDisabled(true);
-  if (::features::IsChromeRefresh2023()) {
-    InkDropHost* ink_drop_host =
-        InkDrop::Install(this, std::make_unique<views::InkDropHost>(this));
-    ink_drop_host->SetMode(InkDropHost::InkDropMode::ON);
-    ink_drop_host->SetLayerRegion(LayerRegion::kAbove);
-    ink_drop_host->SetHighlightOpacity(1.0f);
-    ink_drop_host->SetBaseColorCallback(base::BindRepeating(
-        [](Textfield* host) {
-          return host->HasFocus() ? SK_ColorTRANSPARENT
-                                  : host->GetColorProvider()->GetColor(
-                                        ui::kColorTextfieldHover);
-        },
-        this));
-  }
+  InkDropHost* ink_drop_host =
+      InkDrop::Install(this, std::make_unique<views::InkDropHost>(this));
+  ink_drop_host->SetMode(InkDropHost::InkDropMode::ON);
+  ink_drop_host->SetLayerRegion(LayerRegion::kAbove);
+  ink_drop_host->SetHighlightOpacity(1.0f);
+  ink_drop_host->SetBaseColorCallback(base::BindRepeating(
+      [](Textfield* host) {
+        return host->HasFocus() ? SK_ColorTRANSPARENT
+                                : host->GetColorProvider()->GetColor(
+                                      ui::kColorTextfieldHover);
+      },
+      this));
 
 #if !BUILDFLAG(IS_MAC)
   // Do not map accelerators on Mac. E.g. They might not reflect custom
@@ -673,7 +671,7 @@
 bool Textfield::OnMousePressed(const ui::MouseEvent& event) {
   const bool had_focus = HasFocus();
   bool handled = controller_ && controller_->HandleMouseEvent(this, event);
-  if (::features::IsChromeRefresh2023() && InkDrop::Get(this)) {
+  if (InkDrop::Get(this)) {
     // When a textfield is pressed, the hover state should be off and the
     // background color should no longer have a mask.
     InkDrop::Get(this)->GetInkDrop()->SetHovered(false);
@@ -2625,8 +2623,7 @@
   if (!use_default_border_) {
     return;
   }
-  auto border = std::make_unique<views::FocusableBorder>(
-      ::features::IsChromeRefresh2023());
+  auto border = std::make_unique<views::FocusableBorder>();
   const LayoutProvider* provider = LayoutProvider::Get();
   border->SetColorId(ui::kColorTextfieldOutline);
   border->SetInsets(gfx::Insets::TLBR(
diff --git a/ui/views/controls/tree/tree_view_unittest.cc b/ui/views/controls/tree/tree_view_unittest.cc
index 0cad5eb5..96188d9 100644
--- a/ui/views/controls/tree/tree_view_unittest.cc
+++ b/ui/views/controls/tree/tree_view_unittest.cc
@@ -17,6 +17,7 @@
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_id_forward.h"
 #include "ui/accessibility/platform/ax_platform_node_delegate.h"
 #include "ui/base/models/tree_node_model.h"
 #include "ui/compositor/canvas_painter.h"
@@ -1027,7 +1028,7 @@
   // Do nothing when a valid node id is not provided. This can happen if the
   // actions target the owner view itself.
   tree()->SetSelectedNode(GetNodeByTitle("b"));
-  data.target_node_id = -1;
+  data.target_node_id = ui::kInvalidAXNodeID;
   data.action = ax::mojom::Action::kDoDefault;
   EXPECT_FALSE(tree()->HandleAccessibleAction(data));
   EXPECT_EQ("b", GetActiveNodeTitle());
@@ -1047,7 +1048,7 @@
 
   // Do not handle accessible actions when no node is selected.
   tree()->SetSelectedNode(nullptr);
-  data.target_node_id = -1;
+  data.target_node_id = ui::kInvalidAXNodeID;
   data.action = ax::mojom::Action::kDoDefault;
   EXPECT_FALSE(tree()->HandleAccessibleAction(data));
   EXPECT_EQ(std::string(), GetActiveNodeTitle());
@@ -1156,7 +1157,7 @@
   ClearAccessibilityEvents();
   tree()->GetFocusManager()->ClearFocus();
   tree()->SetSelectedNode(GetNodeByTitle("b"));
-  data.target_node_id = -1;
+  data.target_node_id = ui::kInvalidAXNodeID;
   data.action = ax::mojom::Action::kFocus;
   EXPECT_FALSE(tree()->HandleAccessibleAction(data));
   EXPECT_FALSE(tree()->HasFocus());
@@ -1177,7 +1178,7 @@
   static_cast<TestNode*>(empty_model.GetRoot())->SetTitle(u"root");
   tree()->SetModel(&empty_model);
   tree()->SetRootShown(false);
-  data.target_node_id = -1;
+  data.target_node_id = ui::kInvalidAXNodeID;
   data.action = ax::mojom::Action::kFocus;
   EXPECT_TRUE(tree()->HandleAccessibleAction(data));
   EXPECT_TRUE(tree()->HasFocus());
diff --git a/ui/webui/resources/cr_components/commerce/shopping_service.mojom b/ui/webui/resources/cr_components/commerce/shopping_service.mojom
index a8954a9..696114d 100644
--- a/ui/webui/resources/cr_components/commerce/shopping_service.mojom
+++ b/ui/webui/resources/cr_components/commerce/shopping_service.mojom
@@ -25,9 +25,7 @@
   // Direct link to the product image.
   url.mojom.Url image_url;
 
-  // Direct link to the product page. Right now this is
-  // only used to fetch site favicon in case image_url
-  // is not available.
+  // Direct link to the product page.
   url.mojom.Url product_url;
 
   // Price as shown in the page. This should include
diff --git a/ui/webui/resources/tools/codemods/lit_migration_templates.mjs b/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
index c5dd93e..8a03d5f 100644
--- a/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
+++ b/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
@@ -27,11 +27,22 @@
 const LISTENER_BINDING_REGEX =
     /on-(?<eventName>[a-zA-Z-]+)="(?<listenerName>[a-zA-Z_]+)"/g;
 
+// Regular expression to parse 2-way bindings like value="{{myValue_}}",
+// and extract 'value' and 'myValue_' into captured groups for further
+// processing.
+const LISTENER_BIDNING_TWO_WAY_REGEX =
+    /(?<childProp>[a-z-]+)="\{\{(?<parentProp>[a-zA-Z_]+)\}\}"/g;
+
 // Regular expression to extract any "${this.foo}" ocurrences in the HTML
 // template, referring to TS methods or member variables.
 const TS_REFERENCE_REGEX =
     /"\$\{this\.(?<reference>[a-zA-Z_]+)\}"/g;
 
+// Replaces part of a string with a the provided replacement string.
+function replaceRange(string, start, end, replacement) {
+  return string.substring(0, start) + replacement + string.substring(end);
+}
+
 function processFile(file) {
   const basename = path.basename(file, '.ts');
   const tsFile = path.join(path.dirname(file), basename + '.ts');
@@ -56,17 +67,42 @@
         return `@${groups.eventName}="\${this.${groups.listenerName}}"`;
       });
 
-  // Step 4: Update property access syntax in HTML template
+  // Step 4: Update property access syntax in HTML template (1-way bindings)
   htmlContent = htmlContent.replaceAll(/\[\[!item/g, () => '${!item');
   htmlContent = htmlContent.replaceAll(/\[\[item/g, () => '${item');
   htmlContent = htmlContent.replaceAll(/\[\[!/g, () => '${!this.');
   htmlContent = htmlContent.replaceAll(/\[\[/g, () => '${this.');
   htmlContent = htmlContent.replaceAll(/\]\]/g, () => '}');
 
-  // Step 5: Write updated HTML content to disk
+  // Step 5: Update property access syntax in HTML template (2-way bindings)
+  const matches = Array.from(htmlContent.matchAll(LISTENER_BIDNING_TWO_WAY_REGEX));
+  // Reverse the order so that the character indices don't get messed up after
+  // modifying the original string, effectively processing the matches from the
+  // end of the string to the start.
+  matches.reverse();
+
+  // For each match, change
+  // value="{{myValue_}}"
+  // to
+  // value="${this.myValue_}" @value-changed="${this.onMyValueChanged_}"
+  for (let i = 0; i < matches.length; i++) {
+    const g = matches[i].groups;
+    let listenerPart =
+        g['parentProp'].charAt(0).toUpperCase() + g['parentProp'].slice(1);
+    listenerPart = listenerPart.replace('_', '');
+    const listener =
+        `@${g['childProp']}-changed="\${this.on${listenerPart}Changed_}"`
+    const binding = matches[i][0].replace("{{", "${this.").replace("}}", "}");
+    const start = matches[i].index;
+    const end = matches[i].index + matches[i][0].length;
+    htmlContent =
+        replaceRange(htmlContent, start, end, `${binding} ${listener}`);
+  }
+
+  // Step 6: Write updated HTML content to disk
   fs.writeFileSync(htmlFile, htmlContent, 'utf8');
 
-  // Step 6: Extract all methods/variables being referenced from the template
+  // Step 7: Extract all methods/variables being referenced from the template
   //         and if they are 'private' change them to 'protected'.
   const references = Array.from(
       htmlContent.matchAll(TS_REFERENCE_REGEX)).map(m => m[1]);
@@ -75,7 +111,7 @@
     for (const ref of references) {
       tsContent = tsContent.replace(`private ${ref}`, `protected ${ref}`);
     }
-    // Step 7: Write updated TS content to disk
+    // Step 7b: Write updated TS content to disk.
     fs.writeFileSync(tsFile, tsContent, 'utf8');
   }
 }
diff --git a/v8 b/v8
index 2a06d4e..dbccf56 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 2a06d4e3534debe1294744c208e85d8c4f06d579
+Subproject commit dbccf5638033c2f65fa8726313050ddecadda427